Java基础核心知识点全总结:面试高频考点与实战细节
如果你在搜索引擎里敲下“Java 基础核心知识点全总结”这个标题十有八九是两种状态要么是刚入行想找一条清晰的自学路径要么是准备面试想用最短时间把八股文吃透。我在这个行业摸爬滚打了十几年带过不少新人也当过很多场面试的考官很确定一件事——所谓“基础”从来不是把教材目录抄一遍而是那些在面试和日常开发里反复出现、换着花样考你的核心概念。这篇文章我就按自己的理解把Java基础高频考点、实际开发里真正用得上的核心细节、常见误区和排查思路串起来每个点都会讲原理和实操经验尽量让你看完能直接用上。我特意翻了一下近期的搜索热词“java面试题”“java基础”“java八股文”“java集合容器”“java动态代理”“java StringBuilder”“java环境变量配置”这些词基本霸榜。这个组合很有意思它恰好暴露了大多数人学Java的两个关键诉求系统建立知识体系以及应对面试时的定向突破。所以下面这份总结我不按教科书顺序平铺直叙而是按业务流程和面试考点两条线交叉组织覆盖从环境配置、面向对象、集合框架到异常IO、并发、反射动态代理和JVM基础最后附上高频面试题速查和自学路线建议。1. 先看热搜词再定学习重点1.1 热搜词里藏着的高频考点你仔细看“java基础核心知识点全总结”这个标题相关的热搜词会发现一个有趣的现象排名靠前的永远是“java面试题”“java面试八股文”“java基础面试题”后面才跟着“java环境变量配置”“java面向对象”“java集合”“java动态代理”“java StringBuilder”。这说明什么说明搜索这些词的绝大多数人不是想从头到尾读一遍语言规范而是希望在有限时间内抓住真正高频、真正能产生区分度的考点。这些热搜词替我标出的一条主线是这样的先解决环境问题java下载、java安装、java环境变量配置再啃面向对象思想封装、继承、多态接着是语言核心类String、StringBuilder、包装类然后是容器集合框架进阶到反射、动态代理、并发最后是JVM基础。这个顺序其实就是一条合理的Java学习路线也是绝大多数面试题出题的底层逻辑。环境配置是所有零基础同学的第一道坎。很多人卡在JDK安装后命令行敲java -version报错其实理解了原理就很简单需要配置JAVA_HOME指向JDK安装目录然后在PATH里加上%JAVA_HOME%\bin这样系统才能在任意目录找到java和javac命令。至于CLASSPATH在JDK 9模块化之后基本不需要手动设置了老教程里让你配.;%JAVA_HOME%\lib\dt.jar之类的内容现在完全可以略过照抄反而容易出问题。1.2 基础学习的三个常见误区我面试过很多候选人也带过不少新人发现基础这块踩坑的姿势高度统一。第一个误区是只背不写。八股文背得滚瓜烂熟能完整背诵HashMap在JDK 8之后的put流程但让他手写一个线程安全的单例模式三分钟写不出完整代码。基础的东西最终是要落到代码上的背概念属于输入写代码才是输出面试官分分钟看穿你是不是只会背。第二个误区正好相反只写不背。有些同学工作两三年业务代码写得很溜但被问到“ArrayList扩容为什么是1.5倍而不是2倍”“HashMap为什么不是线程安全的”就卡住了。这类问题看起来像八股文实际上考察的是你对底层原理的理解程度业务代码写得多不代表基础扎实。第三个误区是轻易跳过“笨功夫”。比如环境变量懒得自己配直接用IDE内置JDK异常打印完e.printStackTrace()就当处理完了字符串拼接无脑用。这些看似省事后面都会变成埋在自己代码里的坑。我见过线上故障就是循环里用拼接字符串导致GC频繁也见过吞异常导致问题排查两天找不到根因。基础阶段省下的力气后面都会加倍还回来。2. 面向对象核心封装、继承、多态2.1 封装不只是private封装是面向对象三大特性里最容易被低估的一个。很多人以为封装就是“属性私有化加getter/setter”其实封装的核心思想是隐藏实现细节对外暴露稳定接口。你自己开车的时候不用关心发动机怎么点火、变速箱怎么换挡只需要踩油门、打方向盘、踩刹车这就是封装。在Java代码里封装的落地靠四个访问修饰符private、default包私有、protected、public。我的建议是类的成员变量一律用private通过getter/setter访问对外提供的API方法用public同一个包内协作的工具方法用default需要子类重写的钩子方法用protected。这样做的直接好处是降低耦合后面改内部实现不影响外部调用。实操中还有一个很常见的坑很多刚毕业的同学写DTO、VO时手写几百行getter/setter效率低还容易漏。现在主流做法是Data这类注解帮我们生成但注意这只是省去样板代码封装的思想没有变。我见过有人为了图省事把属性直接设成public结果一个字段改名牵动十几个类这就是没有封装的代价。2.2 继承与组合怎么选继承表达的是“is-a”关系比如Dog extends Animal子类天然拥有父类的属性和方法。组合表达的是“has-a”关系比如Car里面有一个Engine对象是整体与部分的关系。很多教材推崇继承但实际工程里我更推荐“组合优先于继承”。为什么因为继承有几个被很多人忽略的缺点。第一继承破坏封装子类依赖父类实现细节父类一改子类可能全崩。第二继承是强耦合extends一旦写出去想在子类里改变行为就得重写而重写本身就容易破坏里氏替换原则。第三继承层次一旦深了类的关系网会复杂到没人敢动。我举一个实际例子。有个业务系统里的基类BaseEntity被几十个类继承某天产品要求给“创建时间”字段加自动填充逻辑你改了基类结果所有子类行为全部变化测试爆炸。如果用组合把通用的审计字段逻辑抽成一个AuditSupport组件谁需要谁持有影响范围就可控得多。所以我的习惯是除非确实是明确的is-a关系且父类稳定否则优先组合。如果确实要用继承必须遵守几个规则用Override标注重写方法防止方法签名写错子类构造器会隐式调用父类无参构造器所以父类没有无参构造器时子类第一行必须显式super(参数)调用顺序是先父类静态代码块、再子类静态代码块、然后父类实例代码块和构造器、最后子类实例代码块和构造器这个顺序在阅读别人代码时也经常用到。2.3 多态动态绑定与重载重写多态是Java里最让初学者迷糊的概念但理解之后又觉得很简单。简单说多态就是同一类型的不同对象对同一消息做出不同的响应。它的实现有三个层次方法重载是编译期多态方法重写是运行期多态而“父类引用指向子类对象”则是重写多态的经典形态。这里必须讲清楚动态绑定原理。Animal d new Dog(); d.makeSound();这行代码编译时d的类型是Animal所以编译器只检查Animal有没有makeSound()方法运行时JVM去查实际对象类型是Dog调用的是Dog重写后的方法。这就是那句著名的“编译看左边运行看右边”。实战里最常见的坑是向下转型。Animal类型的引用被强制转回Dog如果实际对象是Cat运行时会抛ClassCastException。所以向下转型前要用instanceof判断一下。重写还有几条硬规则方法名和参数列表必须一致返回类型可以协变子类返回类型是父类返回类型的子类型访问权限不能比父类更小抛出的受检异常不能比父类更宽泛。这几条规则面试问答中被问到的概率极高。3. 集合框架最常用的武器库3.1 集合体系梳理集合框架是Java日常开发中使用频率最高的工具库也是面试题的重灾区。先建立起体系根接口有两个Collection和Map。Collection下面又有三个子接口List有序可重复、Set无序不可重复、Queue队列。Map保存键值对映射关系。常用实现类我整理成一张表方便对照接口实现类底层结构特点适用场景ListArrayList动态数组随机访问快插入删除慢读多写少的列表ListLinkedList双向链表头尾操作快随机访问慢队列、栈、频繁头尾增删SetHashSetHashMap无序去重去重集合SetLinkedHashSetHashMap链表保持插入顺序去重需要保序的去重集合SetTreeSet红黑树有序按自然顺序或Comparator排序去重MapHashMap数组链表红黑树无序查询快最常用先考虑它MapLinkedHashMapHashMap双向链表保持插入或访问顺序LRU缓存MapTreeMap红黑树键有序需要按key排序你不需要把每个类都精读但至少要知道默认首选ArrayList和HashMap需要保持插入顺序就选LinkedHashMap需要排序就选TreeMap/TreeSet并发场景优先ConcurrentHashMap而不是给HashMap加锁。3.2 HashMap源码级理解HashMap是面试八股文里的核心几乎每场面试都会问到。要拿下面试需要从数据结构、哈希定位、扩容机制三个层面理解。数据结构上JDK 8之后的HashMap是“数组链表红黑树”。数组的每个槽位叫bucket哈希冲突的元素以链表形式挂在槽位上链表长度超过阈值8且数组长度达到64时链表转为红黑树查询时间复杂度从O(n)降到O(logn)。初始容量是16负载因子是0.75。为什么是0.75这是时间和空间的折中0.75意味着数组用到四分之三时触发扩容扩容太早浪费空间太晚冲突加剧影响查询效率。哈希定位过程是这样的先计算key.hashCode()再用(n-1) hash定位到数组索引这里的n是数组长度。因为容量是2的幂n-1的二进制低位全是1与运算等价于取模同时用高位参与扰动减少碰撞。这也是长度必须保持2的幂的原因——扩容后元素要么留在原索引要么移动到“原索引旧容量”这样rehash的效率极高。扩容过程里有个著名的问题JDK 7的HashMap扩容时采用头插法多线程并发扩容会出现环形链表导致get时死循环JDK 8改成了尾插法修掉了死循环但在并发场景下依然有数据覆盖问题。所以结论还是那句并发场景别用HashMap请用ConcurrentHashMap。同时记住几个关键数字默认容量16、负载因子0.75、链表转红黑树阈值8、红黑树退化为链表阈值6、数组长度转树的最小值64。3.3 ArrayList、LinkedList与fail-fastArrayList和LinkedList是面试对比题里的常客。ArrayList底层是Object数组通过下标访问的时间复杂度是O(1)但中间插入删除需要移动后续元素是O(n)扩容发生在add时容量不足的情况下新容量是oldCapacity (oldCapacity 1)也就是1.5倍。LinkedList底层是双向链表头尾插入删除是O(1)但随机访问需要从头遍历是O(n)。实际开发里我90%以上的场景直接用ArrayList。很多人以为LinkedList插入快所以更适合业务这是误解——你要找到插入位置本身就要遍历综合成本并不低。LinkedList真正的价值在于需要频繁在头部插入删除或者要做队列、栈这种数据结构比如实现LRU的最近最少使用策略时双向链表很好用。还有一个高频考点是fail-fast机制。ArrayList内部维护一个modCount字段记录结构性修改次数迭代器每次next()都会检查modCount和预期值是否一致不一致就抛ConcurrentModificationException。这就是为什么你在foreach循环里remove元素会报错的根本原因。如果需要遍历时删除要么用迭代器的remove()要么用CopyOnWriteArrayList等并发集合。4. 字符串与常用类细节决定成败4.1 String不可变性与字面量池String是final类不可被继承里面的字符数组也是final的一旦赋值不可修改。设计成不可变有三个关键好处线程安全、字符串常量池复用、hash值可以缓存。如果String可变常量池里同一个“abc”可能被一个线程改了其他线程全部错乱这是不可接受的风险。“每次修改String都会创建新对象”——这句话很多人体会不深。看这段代码String s hello; s s world;第二行执行时实际上创建了三个对象“hello”“ world”以及拼接结果“hello world”原来的“hello”还在常量池里等着被GC回收。如果你在一个循环里做这种拼接比如拼一万次SQL中间会创建上万个临时字符串GC压力巨大这是线上性能问题的常见来源。讲常量池时必然会提到intern()方法和比较的坑。String a abc;这种写法如果常量池里没有“abc”会入池有就直接复用而new String(abc)一定在堆里创建新对象。所以abc new String(abc)结果是false但abc.intern() new String(abc).intern()结果是true因为intern会去常量池里找相同内容的引用。4.2 StringBuilder/StringBuffer抉择如果需要频繁修改字符串正确工具是StringBuilder或StringBuffer。两者API几乎一样核心区别是StringBuffer的大部分方法用synchronized修饰线程安全但性能较差StringBuilder是线程不安全的但单线程下性能更好。我的建议非常简单方法内局部变量拼接一律StringBuilder因为局部变量不存在线程共享问题如果是static工具方法可能被多线程调用就用StringBuffer。另外如果预先知道要拼接的长度最好指定初始容量new StringBuilder(int capacity)避免扩容。StringBuilder默认容量是16扩容时新容量约为旧容量的两倍加2即oldCapacity * 2 2。反复扩容的代价是数组复制大数据量拼接时提前预估容量能显著提升性能。4.3 包装类与自动装箱缓存自动装箱的原理是调用valueOf()拆箱是调用intValue()这类方法。比如Integer a 128; int b a;这行代码第一句自动装箱第二句自动拆箱编译器帮你都做完了。但自动装箱带来一个经典面试陷阱——Integer缓存机制。Integer a 127; Integer b 127; System.out.println(a b); // true Integer c 128; Integer d 128; System.out.println(c d); // false为什么两个结果不一样因为Integer.valueOf()方法内部有一个缓存数组缓存了-128~127之间的对象。a和b在valueOf时都命中缓存返回的是同一个对象所以比较truec和d超出缓存范围各自new新对象就false了。这个细节在面试里特别容易踩中很多人会选“两个都是true”。正确做法是包装类型之间比较值一律用equals()跟基本类型比较时才可以用因为会触发拆箱。另外注意包装类参与算术运算也会自动拆箱比如Integer x null; int y x 1;会直接抛NullPointerException这是线上空指针的常见来源之一。使用包装类做equals比较时还要当心null.equals()的问题。5. 异常、IO与并发基础5.1 异常体系与try-with-resourcesJava异常体系顶层是Throwable下面分Error和Exception。Error是JVM级别的严重错误比如OutOfMemoryError、StackOverflowError程序本身无法处理不要catch它继续跑。Exception分受检异常和非受检异常受检异常是编译器强制要求你处理或抛出的比如IOException、SQLException非受检异常是RuntimeException及其子类比如NullPointerException、ArrayIndexOutOfBoundsException编译器不强制处理。处理异常的原则我归纳成三条。第一能处理就处理处理不了就抛绝不吞异常——那句经典的catch (Exception e) { e.printStackTrace(); }是反面教材生产环境里日志一堆堆栈但程序还继续往下跑结果数据错乱。第二catch的粒度要合适不要一把catch (Exception)兜底要分别catch具体的异常做不同处理。第三不要用异常控制正常流程比如用异常判断字符串能不能转数字这是反模式正确方式是先做预校验或用无异常的实现。资源管理这块JDK 7推出了try-with-resources语法这是每个Java开发者都必须掌握的。只要类实现了AutoCloseable接口比如InputStream、Connection、Statement就可以写在try小括号里代码块结束自动关闭资源不用手写finally块。如果你在finally里手动close还得处理close本身抛的异常层层嵌套很容易出bug。另外特别注意一个坑finally块里不要写return如果finally里有return它会覆盖try块里的return值这个行为在面试和实际案里都出现过。5.2 IO流分类与NIOJava IO流按方向分为输入流和输出流按处理单位分为字节流和字符流。四个抽象基类是InputStream、OutputStream、Reader、Writer。读写文件的套路是字节流读图片视频等二进制文件字符流读文本文件而且字符流在处理中文时不需要手动处理编码更安全。IO里最有价值的设计是装饰器模式。BufferedInputStream可以包装FileInputStream增加缓冲减少系统调用InputStreamReader可以把字节流转换成字符流BufferedReader的readLine()可以按行读取。平时读写文件一定要加缓冲层否则读写小文件还行大文件会有大量底层调用性能差一个数量级。序列化这块也需要掌握。对象要序列化必须实现Serializable接口同时最好显式声明serialVersionUID。如果不声明JVM会根据类结构自动生成一旦类增加了字段自动生成的serialVersionUID就变了反序列化直接报InvalidClassException线上兼容性就崩了。所以一个常见的最佳实践是每个序列化类都显式写上private static final long serialVersionUID 1L;。NIO的核心是三道流Buffer、Channel、Selector。理解NIO最粗暴的方式是类比传统IO是“一个线程处理一个连接读数据时阻塞干等”NIO是“一个线程通过Selector管理成百上千个连接哪个连接有数据就处理哪个”。JDK也提供了AIO异步IO但生产环境用得不多。面试时能把BIO、NIO、AIO区别讲清楚基本就达标了。5.3 线程基础与synchronized原理并发这块是Java进阶的分水岭基础阶段至少要掌握线程创建、生命周期、synchronized和volatile。创建线程有三种主流方式继承Thread类、实现Runnable接口、实现Callable接口配合FutureTask。我的建议是优先实现Runnable和Callable因为Java是单继承继承Thread就失去了扩展的余地。线程生命周期有六个状态NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED。注意RUNNABLE包含了操作系统层面的“运行中”和“可运行”而BLOCKED专指等待进入synchronized代码块时的状态WAITING是执行了wait()、join()等之后的等待状态。这些状态之间的流转是面试选择题的高频素材。synchronized的核心是内置锁monitor。修饰普通方法时锁住的是当前实例对象修饰静态方法时锁住的是Class对象修饰代码块时锁住的是括号里指定的对象。JDK 6之后做了锁升级优化从无锁状态开始逐步升级为偏向锁、轻量级锁、重量级锁这也是synchronized早期被诟病“性能差”、现在差距大幅缩小的原因。volatile则负责两件事保证线程间的可见性禁止指令重排序。但它不保证原子性i这种操作就算加了volatile多线程下依然会丢更新。一句话总结synchronized解决同步互斥问题volatile解决可见性和有序性问题各管一块。6. 反射、动态代理与JVM6.1 反射机制核心反射是Java“动态语言”特性的根基。它的本质是程序运行时可以拿到类的完整结构信息包括类名、方法、字段、注解还能直接操作它们。拿到Class对象有三种方式Class.forName(全限定类名)、类名.class、实例.getClass()。推荐用Class.forName的方式因为很多框架场景是只知道类名字符串。反射常用API集中在java.lang.reflect包里getDeclaredMethods()拿方法getDeclaredFields()拿字段setAccessible(true)可以绕过private访问限制method.invoke(obj, args)动态调用方法。Spring的IOC容器、MyBatis的ORM映射、Jackson/Gson的序列化底层全是反射。但反射有两个必须注意的问题。一是性能开销反射调用比直接调用慢一个数量级因为涉及到安全性检查、方法查找、参数装箱所以高频路径上要把反射结果缓存起来Spring里就大量缓存了反射得到的Method对象。二是setAccessible(true)可能绕过访问控制在某些安全敏感场景会有风险。我的建议是业务代码里能用正常引用就别用反射框架代码里才需要反射能力。6.2 动态代理与InvocationHandler动态代理是反射的经典应用也是Spring AOP的核心机制。它的价值在于在不修改目标类源码的前提下给目标方法增加公共逻辑比如日志、鉴权、事务、性能监控。这就是AOP面向切面编程的基本思想。JDK动态代理的用法分三步目标类实现接口传入InvocationHandler实现类在invoke()方法里写增强逻辑调用Proxy.newProxyInstance()生成代理对象。下面是一个完整例子public interface UserService { void login(String name); } public class UserServiceImpl implements UserService { Override public void login(String name) { System.out.println(用户登录: name); } } public class LogHandler implements InvocationHandler { private final Object target; public LogHandler(Object target) { this.target target; } Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(方法调用前: method.getName()); Object result method.invoke(target, args); System.out.println(方法调用后: method.getName()); return result; } } // 使用 UserService service new UserServiceImpl(); UserService proxy (UserService) Proxy.newProxyInstance( service.getClass().getClassLoader(), service.getClass().getInterfaces(), new LogHandler(service) ); proxy.login(admin);注意这段代码的关键点代理对象是JVM运行时动态生成的一个Proxy子类它必须实现目标类的接口这就是JDK动态代理要求目标对象必须实现接口的根本原因。如果目标类没有接口就得用CGLIB——它通过字节码技术生成目标类的子类来完成代理Spring AOP默认优先JDK动态代理目标类没有实现接口时自动切换CGLIB。面试里常问的“JDK动态代理为什么只能代理接口”答案就在代理类生成逻辑里。6.3 JVM内存区域与垃圾回收JVM内存这块我建议从运行时数据区讲起。五个核心区域是程序计数器、虚拟机栈、本地方法栈、堆、方法区JDK 8之后实现为元空间。其中只有程序计数器不存在OOM虚拟机栈栈帧过深时会StackOverflowError堆和方法区会OutOfMemoryError。一句话记忆栈管运行堆管存储方法区管类元数据。垃圾回收首先要判断对象“已死”主流用可达性分析从GC Roots出发沿着引用链搜索不可达的对象就是垃圾。GC Roots包括虚拟机栈中引用的对象、方法区中静态属性和常量引用的对象、本地方法栈中引用的对象。引用计数法因为解决不了循环引用问题JVM不采用。堆被分为新生代和老年代新生代又分为Eden区和两个Survivor区S0、S1。绝大多数新对象在Eden区分配Minor GCYoung GC时存活对象从Eden区和S0复制到S1经历一定次数年龄提升后进入老年代。老年代触发Major GC/Full GC。常见回收器有Serial、Parallel、CMS、G1目前大多数JDK 8默认是ParallelJDK 11之后很多场景用G1。G1的特点是区域化、可预测停顿能指定期望停顿时间这也是它成为主流的原因。类加载机制里还有一个必考点双亲委派模型。类加载器从上到下是Bootstrap、Extension、Application加载一个类时先让父加载器尝试加载父类加载不了才轮到子类。这样做最直接的好处是保证核心类不被篡改你即使写了一个java.lang.String并放到classpath里也会被父加载器拦截加载的是JDK自带的String。7. 面试自测与避坑清单7.1 高频面试题速查表如果你想快速自测基础掌握程度可以用下面这张表。每个问题先自己在心里回答一遍再看要点答不上来的就是需要巩固的知识点面试题回答要点HashMap底层结构数组链表红黑树初始容量16负载因子0.75链表长度超8且数组长度超64转红黑树ArrayList和LinkedList区别数组 vs 双向链表随机访问 vs 头尾操作ArrayList扩容1.5倍String为什么不可变final类final字符数组线程安全、常量池复用、hash缓存和equals区别比较引用或基本类型值equals默认也是重写后比较内容包装类注意缓存范围synchronized和volatile区别锁 vs 可见性synchronized保证原子性volatile不保证线程有哪几种状态NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATEDJDK动态代理为什么要求接口生成的代理类继承Proxy并实现目标接口没有接口无法实现反射有什么应用Spring IOC/MyBatis映射/JSON序列化注意性能开销和缓存JVM内存分区程序计数器、虚拟机栈、本地方法栈、堆、方法区/元空间双亲委派模型父加载器优先防止核心类被篡改如java.lang.String不被覆盖这张表建议每周自测一次直到每道题都能脱口而出并写出一段演示代码。7.2 自学路线与实战建议最后聊聊学习路线。我的建议是不要一上来就啃Java编程思想那种大部头也不要只刷视频不动手。可以按这个节奏走掌握Java基础语法和环境配置重点吃透面向对象和集合框架然后是MySQL和JDBC接着学前端基础HTML、CSS、JavaScript方便后面理解前后端交互再进入Web开发了解Servlet和JSP这个阶段要能写一个简单的增删改查接着是Spring、SpringMVC、MyBatis这套主流框架然后是Redis、消息队列等中间件最后做两个完整项目收尾。面试准备时基础、项目、算法三条线都要兼顾。基础靠上面这张速查表反复过项目要对每个技术点都准备“为什么用它”“遇到什么问题”“怎么解决的”这铁三角算法至少刷完热门的链表反转、二叉树遍历、动态规划入门题。算法这块不用追求难题但手写代码的能力必须过关。学习资源上官方文档Java Tutorials永远是最权威的Effective Java适合工作一两年级别读Java并发编程实战是并发进阶必选深入理解Java虚拟机建议在学习框架时同步看。源码阅读我特别推荐JDK自带的ArrayList、HashMap、String这三兄弟的源码加起来不过几千行把注释都读明白你的基础直接上一个档次。我个人在带新人这几年里最深的体会是基础这东西看起来是死记硬背实际上所有高级框架、分布式中间件里的思想都是从这些基础概念长出来的。你理解透了动态代理Spring AOP的原理一目了然你搞清了HashMap的扩容机制很多性能问题定位的时候思路就清晰了。所以别急着追求框架多新、功能多炫先把这一亩三分地耕好。最后分享一个小习惯平时写代码时顺手看一眼JDK类的源码注释和实现方式遇到问题多问一句底层是怎么做的积累半年你会发现面试官问什么你都不慌。