尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Java实习面试八股文通关指南:核心考点与底层原理

Java实习面试八股文通关指南:核心考点与底层原理 1. 面试开场必问面向对象与Java基础语法每年实习招聘季我都能在面试现场遇到一大半候选人栽在Java基础上。明明简历上写着熟练掌握Java结果问一句重载和重写的区别支支吾吾半天说不利索。说实话八股文这东西在实习面试里不是靠背而是靠理解。面试官考察的从来不是你记住了多少标准答案而是你能不能把一个知识点讲清楚、讲透彻、讲出它背后的设计逻辑。面向对象三大特性——封装、继承、多态几乎是所有Java面试的第一道开胃菜。封装不是让你背把属性私有化提供getter/setter这么简单它的本质是信息隐藏和访问控制目的是降低模块之间的耦合度。继承的本质是代码复用和扩展但这里有个面试官特别爱追问的点继承和组合怎么选我的建议是优先使用组合因为继承会破坏封装性子类对父类实现产生强依赖一旦父类变更子类可能直接崩掉。多态则有两个层面编译时多态方法重载和运行时多态方法重写运行时多态的三要素是继承、重写、父类引用指向子类对象。1.1 String、StringBuilder、StringBuffer的底层差异这一组三兄弟是Java面试里的常青树几乎每一场面试都会碰到。面试官通常会从String为什么是不可变的切入然后一路追到字符串常量池、拼接优化、线程安全。String底层是private final char[]数组JDK 9之后改成了byte[]final修饰保证了不可变性。不可变带来三个好处字符串常量池可以复用字符串对象节省内存String可以安全地被多个线程共享天然线程安全hashCode可以被缓存适合作为HashMap的key。但不可变也带来了性能问题——字符串拼接时会产生大量中间对象。比如在循环里写str a每次都会new一个StringBuilderappend完再toString循环一万次就创建一万个中间对象这在线上环境里是实打实的性能杀手。StringBuilder和StringBuffer的区别就一句话前者线程不安全但性能高后者所有公开方法都加了synchronized线程安全但性能稍差。单线程环境下无脑用StringBuilder就行了。这里我习惯给面试官补一个加分项JDK 9之后字符串拼接的底层优化策略编译器会把号自动优化为StringBuilder的append调用但循环内拼接依然会产生大量对象所以最佳实践是循环外先创建StringBuilder。1.2 equals与hashCode的约定这组问题在实习面试里的出现率高得惊人而且面试官特别爱把它和HashMap放在一起考。你先记住一个核心约定两个对象equals相等则hashCode必须相等两个对象hashCode相等equals不一定相等哈希冲突。为什么必须这样约定因为HashMap、HashSet这类基于哈希的集合在查找元素时会先计算hashCode定位到桶再用equals判断桶内是否有目标元素。假设你重写了equals但没有重写hashCode两个对象在业务逻辑上是相等的比如两个id相同但内存地址不同的User对象可它们的hashCode不同HashMap在get的时候会定位到不同的桶直接返回null——这是实际开发中最容易踩的坑之一。另外面试官还喜欢问重写equals为什么同时要重写hashCode你需要在回答里点出这个约定背后的一致性要求而不是只背结论。顺带可以提一下equals的一般重写步骤先比较引用是否相同再判断类型是否一致然后比较核心业务字段。补充一个高频子问题为什么重写equals必须重写hashCode举HashSet的去重场景就最能说明问题。1.3 异常体系与泛型、Lambda高频考点异常体系的常考内容是Error和Exception的区别受检异常checked exception和非受检异常unchecked exception的区别。受检异常必须显式捕获或抛出否则编译不通过比如IOException非受检异常包括RuntimeException和Error编译时不强制处理比如NullPointerException、ArrayIndexOutOfBoundsException。排查实际线上问题时ArrayIndexOutOfBoundsException这类数组越界异常可以说是实习生最容易踩的雷。我在代码评审中见到过太多类似的问题循环边界条件写错或者对集合进行遍历的同时做remove操作。比如要在for循环里删除集合元素直接list.remove会导致索引错位正确做法是用迭代器的remove方法或者倒序遍历。泛型这块核心考点是类型擦除——Java的泛型是编译期概念运行时会擦除为原始类型所以ArrayList 和ArrayList 在运行时是同一个Class。还有个经典陷阱题为什么不能在静态上下文、泛型类中创建泛型数组。Lambda本质上是对匿名内部类的简化配合函数式接口有且仅有一个抽象方法的接口使用比如Runnable、Comparator。JDK 8之后CompletableFuture异步编程也大量依赖Lambda表达式这里能接上并发编程的话题。2. 集合框架问答链ArrayList扩容到HashMap底层集合框架可以说是Java面试中最重要的一座大山从ArrayList的扩容机制到HashMap的底层原理每一个点都能延伸出一串追问。面试官特别喜欢用一个场景开头你有一个ArrayList往里add一百万条数据会发生什么然后一步步引导你讲出扩容机制。2.1 ArrayList扩容机制与LinkedList的取舍ArrayList底层是Object[]数组默认初始化容量是10。当元素个数超过当前容量时会触发扩容——新容量是旧容量的1.5倍oldCapacity (oldCapacity 1)然后通过Arrays.copyOf把旧数组元素拷贝到新数组这是一个O(n)的操作。所以如果能够预估数据规模一定要在初始化时指定容量new ArrayList(expectedSize)可以避免多次扩容带来的性能损耗。LinkedList底层是双向链表插入删除效率高O(1)不需要搬移元素但随机访问是O(n)。这里面试官常挖的坑是LinkedList的插入删除真的比ArrayList快吗答案是不一定。因为LinkedList每次插入都需要new Node对象而且如果插入位置靠中间还需要遍历定位ArrayList只有在头部插入或扩容时才明显劣势尾部插入配合ensureCapacity性能反而更好。实际开发中绝大多数场景用ArrayList就够了LinkedList的使用场景非常有限比如实现队列或LRU缓存的时候。2.2 HashMap底层原理从put流程到红黑树化HashMap是Java面试的重中之重几乎能问出半小时内容。我建议你把整个put流程背到滚瓜烂熟并且能用白板画出来。JDK 1.8之后HashMap底层是数组链表红黑树。put操作流程如下先对key的hashCode做扰动计算高16位异或低16位然后通过(n - 1) hash定位到数组桶下标如果该桶为空直接创建Node放入如果不为空遍历链表/红黑树有相同key就覆盖没有就尾插法新增节点当链表长度超过阈值8且数组长度大于等于64时链表会转化为红黑树红黑树的查找时间复杂度是O(log n)而链表是O(n)。这里有几个高频追问点。第一个为什么HashMap的容量必须是2的幂因为(n - 1) hash等价于hash % n但位运算的性能远高于取模而且只有n是2的幂时(n - 1) hash才能均匀分布减少哈希冲突。第二个为什么链表转红黑树的阈值是8这是基于泊松分布的数学推导负载因子0.75的情况下链表长度到达8的概率已经极低约千万分之六设置8是为了在时间和空间上做平衡。第三个JDK 1.7和1.8的区别重点说红黑树引入和头插法改尾插法——1.7的头插法在并发扩容时会形成环形链表导致get死循环这是1.8改为尾插法的核心原因。2.3 ConcurrentHashMap的线程安全演进HashMap在多线程环境下会出问题JDK 1.7并发扩容会死循环JDK 1.8虽然没有死循环了但put操作会丢数据。所以在并发场景下要用ConcurrentHashMap。JDK 1.7的ConcurrentHashMap用分段锁Segment数组默认16个Segment每把锁管理一段数据理论上支持16个线程并发写。JDK 1.8改成了CAS synchronized锁粒度细化到数组的每个桶节点并发度更高。这里面试官经常问为什么1.8还要用synchronized而不是完全用CAS因为CAS在冲突严重时会自旋消耗CPUsynchronized在竞争激烈时可以由JVM升级为重量级锁效率反而更高。顺带提一下Collections.synchronizedMap(new HashMap())是用全局锁包裹所有方法并发性能远不如ConcurrentHashMap这个对比能体现你对线程安全容器选型的理解深度。2.4 排序算法手写冒泡排序与快速排序实习面试手撕代码环节排序算法是出现频率最高的题型。冒泡排序和快速排序基本是必考我建议每个实习候选人都在IDE里练到闭着眼能写出来。冒泡排序的核心思想是相邻元素两两比较大的往后沉每一轮确定一个最大值的位置。时间复杂度最好O(n)已有序最坏O(n²)倒序。代码很短但要注意加一个flag做提前退出优化——如果一轮循环下来没有发生交换说明序列已经有序直接break。快速排序是分治思想的典型应用选择一个基准值pivot把数组分成小于基准和大于基准的两部分然后递归排序。平均时间复杂度O(n log n)最坏情况O(n²)——当基准值每次都选到最大或最小值时退化成一个冒泡排序。优化方案是三数取中法取左端、中间、右端三个数的中位数作为基准值以及递归到小区间时改用插入排序。手写快排的时候要注意边界条件这是最容易写错的地方public void quickSort(int[] arr, int left, int right) { if (left right) return; int pivot arr[left], i left, j right; while (i j) { while (i j arr[j] pivot) j--; arr[i] arr[j]; while (i j arr[i] pivot) i; arr[j] arr[i]; } arr[i] pivot; quickSort(arr, left, i - 1); quickSort(arr, i 1, right); }这段代码是挖坑法实现注意两个内层while循环的条件里一定要有i j否则会越界而且外层while结束后i就是基准值最终位置。3. JVM内存与垃圾回收实习面试最容易被追问垮的一关JVM这块是实习面试的分水岭。能答上来JVM内存分为哪几块的人不少但能把垃圾回收算法、内存溢出场景、类加载机制串成一条链的人就不多了。面试官在这个环节最爱干的事情就是从你提到的某个知识点不断往下追问直到你答不上来为止。3.1 运行时数据区线程共享与线程私有JVM运行时数据区分为五块程序计数器、虚拟机栈、本地方法栈、堆、方法区。程序计数器、虚拟机栈、本地方法栈是线程私有的。程序计数器记录当前线程执行的字节码行号是唯一不会抛出OutOfMemoryError的区域虚拟机栈储存栈帧每个方法调用对应一个栈帧的入栈和出栈栈帧里包含局部变量表、操作数栈、动态链接、方法出口——递归调用过深会抛出StackOverflowError本质上就是栈帧太多把栈空间耗尽了。堆和方法区是线程共享的。堆是对象分配的主要区域也是垃圾回收的主要区域可以细分为新生代Eden、Survivor From、Survivor To和老年代方法区在JDK 8之后被元空间Metaspace取代存储类信息、常量、静态变量等元空间使用本地内存而不是JVM堆内存所以默认情况下不会OOM但类加载过多时会报Metaspace溢出。面试官的一个高频追问是你给我说一下new一个对象内存里到底发生了什么这个问题要按这个顺序回答类加载检查→在堆中分配内存指针碰撞或空闲列表→初始化零值→设置对象头哈希码、GC分代年龄、锁状态标志→执行构造方法。3.2 垃圾回收算法与分代收集判断对象是否可回收主流用的是可达性分析算法从GC Roots虚拟机栈引用的对象、静态属性引用的对象、常量引用的对象、本地方法栈引用的对象等出发向下遍历引用链没有被引用到的对象就是可回收的。这里顺带可以提一句引用计数法的缺点——无法解决循环引用问题Android的早期版本就是用引用计数法所以会有循环引用导致的内存泄漏。垃圾回收算法有三个基础算法面试官会让你对比它们的优劣。标记-清除算法先标记可回收对象再统一清除缺点是产生大量内存碎片且标记和清除效率都不高。标记-复制算法把内存分为两块每次只用一块回收时把存活对象复制到另一块解决了碎片问题但浪费了一半内存——新生代用这个算法只是把Eden和两个Survivor的比例调成了8:1:1只有10%的内存会浪费。标记-整理算法标记存活对象然后把所有存活对象向一端移动直接清理边界以外的内存——老年代用这个算法因为老年代对象存活率高复制成本太大。JVM实际是分代收集新生代对象存活率低用复制算法老年代对象存活率高用标记-整理或标记-清除。垃圾收集器的演进这里实习面试常考的是CMS和G1。CMS以最短停顿时间为目标但会产生内存碎片且并发阶段会占用CPU资源G1把堆划分为若干Region可以预测停顿时间JDK 9之后成为默认收集器。理解到这一层就够了除非你面的是高级岗位那才需要深入聊ZGC。3.3 内存溢出排查从StackOverflowError到OutOfMemoryError实习面试中关于内存溢出的高频提问往往围绕两个报错StackOverflowError和OutOfMemoryError。StackOverflowError就是栈溢出常见于递归没有终止条件OutOfMemoryError的子类很多最典型的是Java heap space堆内存不足和Metaspace元空间不足。之前看过一篇JVM调优的文章里面有这么一段话OOM排查的核心思路不是看日志而是看内存快照。这句话建议你在面试里提出来能加不少印象分。排查Java heap space的常规流程是这样第一步加JVM参数-XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPath/path/to/dump让JVM在OOM时自动导出堆转储文件第二步用jmap工具手动导出当前堆快照第三步用MATMemory Analyzer Tool或VisualVM打开dump文件分析是什么对象占用了大量堆内存。有经验的工程师通过支配树Dominator Tree和泄漏嫌疑报告Leak Suspects基本能定位到具体业务代码。在JDK 1.8及之前还有一个非常经典的坑永久代OOM。字符串常量在永久代里如果系统里大量使用String.intern()就会把永久代撑爆。改成元空间之后这个坑基本消失了但如果你在Interview里能聊出这个演进过程面试官会认为你真的读过JVM相关的源码。3.4 类加载机制与双亲委派类加载机制分五个阶段加载、验证、准备、解析、初始化。加载阶段通过类的全限定名获取二进制字节流并在堆中生成Class对象验证阶段确保字节流符合JVM规范准备阶段为静态变量分配内存并设置零值解析阶段把符号引用替换为直接引用初始化阶段执行静态代码块和静态变量赋值。双亲委派模型是必考题一个类加载器收到加载请求时会先委托给父加载器逐级向上直到Bootstrap ClassLoader如果父加载器无法加载才向下返回让子加载器尝试。这样做的核心目的是安全——防止核心API被自定义类篡改比如你自己写一个java.lang.String双亲委派会保证加载到的是JDK自带的String。面试官常见的追问是能不能打破双亲委派怎么打破答案是能重写ClassLoader的loadClass方法即可。典型场景是Tomcat的WebAppClassLoader每个Web应用一个类加载器优先加载自己应用的类实现应用间类隔离。4. 并发编程必背链路volatile、synchronized与线程池并发编程是Java实习面试里体感最难的一关因为这里考察的不只是API使用还有对内存模型、锁机制的理解。很多实习生在这块翻车原因是只背了结论没有真正理解为什么。我建议你在准备这块时多问自己几次CPU缓存和JMM有什么关系synchronized底层到底做了什么线程池的线程是怎么复用的4.1 线程状态与创建方式Java线程有六种状态NEW新建、RUNNABLE可运行包含运行中和等待CPU资源、BLOCKED阻塞等待monitor锁、WAITING无限期等待、TIMED_WAITING限时等待、TERMINATED终止。这里面试官常设一个陷阱调用Thread.sleep()会让线程进入什么状态答案是TIMED_WAITING而不是BLOCKED。创建线程有四种方式继承Thread类、实现Runnable接口、实现Callable接口有返回值可以抛异常、使用线程池。推荐答案是实现Runnable或Callable因为Java是单继承继承Thread会占用唯一的继承位而线程池是最高级的方案能够复用线程、控制并发数。这里需要补充一个容易被问懵的点Callable和Runnable的区别。Callable的call()方法可以返回值可以抛出受检异常而Runnable的run()方法既不能返回值也不能抛受检异常。FutureTask是Runnable和Callable之间的桥梁FutureTask本身实现了Runnable内部持有Callable调用get()方法获取返回值时如果任务没执行完当前线程会阻塞。4.2 volatile和synchronized的底层原理volatile是Java提供的最轻量级的同步机制核心语义有两个可见性和有序性禁止指令重排序。但volatile不保证原子性。经典例子是一个线程执行count另一个线程读取count即使count被volatile修饰count依然是线程不安全的——因为count不是原子操作包含读、加、写三步volatile只能保证这三步各自被执行时是可见的无法保证三步作为一个整体不被其他线程插队。面试官接下来会追问那什么时候用volatile典型场景有两个状态标记位比如volatile boolean running true一个线程改其他线程读和单例模式的双重检查锁DCL。DCL中给单例对象加volatile是必要的因为new一个对象分为三步分配内存、初始化对象、把引用指向内存。这三步可能被指令重排序如果不加volatile另一个线程可能拿到一个未初始化完成的对象。synchronized有三种使用方式修饰实例方法锁this、修饰静态方法锁Class对象、修饰代码块锁指定对象。JDK 1.6之后引入了锁升级机制无锁→偏向锁→轻量级锁→重量级锁。偏向锁会记录第一个获取锁的线程ID后续该线程再次获取锁时不需要竞争如果出现竞争升级为轻量级锁通过CAS自旋获取自旋失败或竞争激烈时升级为重量级锁由操作系统实现线程阻塞和唤醒。这个锁升级的过程是面试官用来判断你有没有看过《深入理解Java虚拟机》这类书的重要依据建议多花点时间把偏向锁撤销和轻量级锁的Mark Word结构弄清楚。4.3 ThreadLocal的原理与内存泄漏隐患ThreadLocal的使用场景是线程封闭每个线程持有变量的独立副本互不干扰。底层原理是每个Thread对象内部维护一个ThreadLocalMapkey是ThreadLocal对象弱引用value是线程持有的变量副本。面试官最爱考的是ThreadLocal的内存泄漏问题。为什么会有泄漏因为ThreadLocalMap的key是弱引用当外部没有强引用指向ThreadLocal对象时key会被回收但value是强引用它挂在Thread上只要线程不结束value就无法被回收。如果线程池里的线程长期存活线程池的核心线程默认不销毁value就永远不会被回收形成内存泄漏。解决方案很明确在使用完ThreadLocal后主动调用remove()方法清除。这里有个很多人不知道的细节ThreadLocal在没有调用remove时get()方法会触发一次探测式清理但这是不可靠的因为如果之后没人再调用get()清理逻辑就不会执行。4.4 线程池七大参数与执行流程线程池是并发编程的最高频考点没有之一。ThreadPoolExecutor有七个参数核心线程数corePoolSize、最大线程数maximumPoolSize、存活时间keepAliveTime、存活时间单位unit、工作队列workQueue、线程工厂threadFactory、拒绝策略handler。执行流程必须背熟提交任务时如果当前线程数小于核心线程数直接创建新线程执行如果达到核心线程数任务放入工作队列如果队列满了且线程数小于最大线程数创建新线程执行任务如果线程数达到最大线程数执行拒绝策略。四种拒绝策略AbortPolicy抛异常、CallerRunsPolicy调用者线程执行、DiscardPolicy丢弃、DiscardOldestPolicy丢弃最旧的任务。这里有一个面试官特别爱的追问核心线程会被回收吗默认不会除非设置了allowCoreThreadTimeOut(true)。还有一个经典问题线程池的线程数应该怎么设置参考《Java并发编程实战》的建议CPU密集型任务设置为CPU核数1IO密集型任务设置为CPU核数×2。但实际生产中要根据压测结果调整比如IO密集任务可以设置为核心数/(1-阻塞系数)阻塞系数通常在0.8~0.9之间。5. Spring与Spring Boot实习生的日常战场到了Spring这块实习面试的考察重点会从原理转向应用能力。Spring Boot可以说是实习生入职后接触最多的框架你写的第一个接口、第一个定时任务、第一个接口安全校验都离不开它。这部分我建议你把IOC/AOP的原理理解到能讲清楚同时熟悉Spring Boot自动配置的机制。另外Spring Boot接口安全对接也是近期热搜词里出现比较多的内容实际工作中几乎每个项目都会涉及。5.1 IOC与AOP不是背书而是讲设计思想IOC控制反转的核心思想是对象的创建和管理由容器负责而不是由程序员手动new。控制反转和依赖注入DI是两个角度描述同一件事——从容器角度看是反转了控制权从开发者角度看是注入了依赖。依赖注入有三种方式构造器注入、Setter注入、字段注入Autowired。Spring官方推荐构造器注入因为可以保证依赖不可变且能避免循环依赖问题虽然Spring可以解决setter循环依赖但构造器循环依赖会直接报错。AOP面向切面编程解决了横切逻辑的复用问题。经典的场景包括日志记录、事务管理、权限校验。它的底层是动态代理Spring AOP默认使用JDK动态代理基于接口如果没有接口则使用CGLIB基于继承生成子类代理。面试中非常经典的追问是JDK动态代理和CGLIB的区别是什么JDK动态代理只能代理实现了接口的类且通过InvocationHandler实现要求目标类实现接口CGLIB通过ASM字节码生成目标类的子类可以代理没有接口的类但不能代理final类和方法。5.2 Bean的生命周期与作用域Bean的生命周期是Spring面试的保留节目虽然实习岗问得相对少但问出来很多人就懵了。完整链路过长我建议你抓住核心几个节点实例化Instantiation→属性填充Populate依赖注入发生在这里→初始化Initialization包括BeanPostProcessor的before和after方法→使用→销毁Destruction。Bean的作用域有五种singleton默认容器内单例、prototype每次获取都创建新实例、request、session、application后三种仅Web应用有效。面试官常问singleton Bean是线程安全的吗答案是否定的。singleton Bean本身不维护状态但如果Bean里定义了可变成员变量并发访问就会产生线程安全问题。解决办法是把可变状态用ThreadLocal包装或者用无状态设计推荐。5.3 Spring Boot自动配置原理Spring Boot的自动配置是它约定大于配置理念的核心体现。你只需要在pom里引入一个依赖框架就能帮你自动创建一堆Bean比如引入了spring-boot-starter-webTomcat和DispatcherServlet就自动配好了。自动配置的入口是SpringBootApplication它由三个注解组合而来SpringBootConfiguration、EnableAutoConfiguration、ComponentScan。关键在于EnableAutoConfiguration它通过Import导入了AutoConfigurationImportSelector这个类会扫描所有jar包里的META-INF/spring.factories文件Spring Boot 2.7之后是META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports加载所有标注了AutoConfigureBefore、AutoConfigureAfter、ConditionalOnClass等的配置类配合Conditional系列条件注解按需装配。你需要在面试中突出这个点自动配置不是无脑加载所有Bean而是通过ConditionalOnClass类路径存在某个类才生效、ConditionalOnMissingBean容器不存在某个Bean才生效等条件来控制是否装配。这也是Spring Boot能够保持启动快、配置少的原因。5.4 Spring Boot接口安全对接API Key实战随着前后端分离和微服务架构的普及接口安全对接成了实习生日常开发中经常遇到的需求。这里我结合实操讲一种最常见的方案API Key 签名校验这也是面试官爱让你现场设计的一个题目。API Key对接的核心流程如下对接方服务端到服务端生成一对密钥AppId AppSecret调用方发起请求时用AppSecret对请求参数计算签名通常用HMAC-SHA256把AppId、时间戳、随机数和签名放进请求头服务端收到请求后用AppId查数据源获取AppSecret用同样的规则重新计算签名比对是否一致。时间戳的作用是防止重放攻击比如要求时间差不能超过5分钟随机数可以用来校验同一时间戳下是否存在重复请求。后端实现的关键点有三个用拦截器或过滤器统一实现签名校验逻辑方便对所有需要鉴权的接口生效读写AppSecret的数据源要选择Redis或数据库并做好缓存避免每次请求都查库签名校验失败时统一返回约定好的错误码不要在响应头里泄露服务端异常信息。Component public class ApiKeyInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { String appId request.getHeader(AppId); String timestamp request.getHeader(Timestamp); String nonce request.getHeader(Nonce); String sign request.getHeader(Sign); // 1. 校验appId是否存在 // 2. 校验timestamp是否超时如5分钟 // 3. 用appId查AppSecret重新计算签名 // 4. 比对sign一致则放行 return true; } }这段代码里我重点标注了四个步骤实际开发中还要处理一个容易被忽略的问题哪些参数参与签名计算我的经验是参与签名的参数必须和请求体中的参数完全一致否则客户端和服务端计算出来的签名永远对不上。通常做法是取请求参数全部或特定字段排序后拼成字符串再和AppSecret一起参与HMAC计算。如果你在这里表现出对这些细节的敏感度面试官会认为你真的有项目经验而不是只会理论背书。6. 数据库常识实习面试绕不开的加分项数据库这块在实习面试中通常不是主战场但却是重要的加分项。如果你能在聊完Java基础后自然地展现出对MySQL索引、事务隔离级别的理解面试官对你的综合评价会明显上升。毕竟日常开发中大部分业务逻辑最后都要落到数据库操作上而Spring、MyBatis这些框架又不能完全替代你对数据库的理解。6.1 索引为什么MySQL用B树而不是红黑树索引相关的面试问题几乎都是从为什么MySQL用B树开始。这个问题的标准回答是索引是在磁盘上存储的而B树的磁盘IO次数少。B树是非叶子节点不存数据只存索引值所以每层能容纳的索引数量远比存储数据节点的平衡二叉树多InnoDB默认页大小是16KB一个三层B树就能存储千万级别的数据。B树和红黑树或者说平衡二叉树的关键差异在于红黑树每个节点存储数据树的高度比B树高很多查找时磁盘IO次数多而且B树的叶子节点通过指针串联成一个有序链表非常适合范围查询比如where id 100 and id 200只需要定位到开始位置再顺序扫描即可。B树虽然也支持范围查询但叶子节点不相连范围查询时需要回到父节点效率不如B树。面试官还可能追问聚簇索引和非聚簇索引的区别。InnoDB的聚簇索引是指主键索引叶子节点存储的是完整行数据非聚簇索引二级索引的叶子节点存储的是主键值。所以用二级索引查询时如果查询字段不在索引中会发生回表——先用二级索引找到主键再回聚簇索引查完整行数据。如果查询的所有字段都在二级索引中即覆盖索引则不需要回表这是SQL优化里很重要的一个技巧。最左前缀原则也是常考点联合索引(a, b, c)查询条件中包含a、ab、abc都能命中索引但直接查b或c则不会命中。这个原则的底层原理就是B树的排序规则——联合索引按照索引字段从左到右有序排列如果跳过最左字段后续字段的排序就没有意义了。6.2 事务的ACID与隔离级别事务是数据库面试另一个高频主题。ACID四个特性要能解释清楚特别是原子性和持久性分别依赖什么机制。原子性依赖undo log实现回滚持久性依赖redo log实现崩溃恢复这两个日志机制的对比在MySQL面试里很常见。隔离级别有四个从低到高依次是读未提交READ UNCOMMITTED、读已提交READ COMMITTED、可重复读REPEATABLE READ、串行化SERIALIZABLE。隔离级别解决的问题对应三类并发问题脏读、不可重复读、幻读。MySQL默认的隔离级别是可重复读而且InnoDB通过MVCC间隙锁解决了幻读问题。这里建议你用表格把隔离级别、并发问题、实现机制对应起来答题时逻辑更清晰隔离级别脏读不可重复读幻读InnoDB实现读未提交可能可能可能不加锁读已提交不可能可能可能MVCC快照可重复读不可能不可能可能InnoDB解决了MVCC间隙锁串行化不可能不可能不可能加锁面试官最喜欢追问的点是可重复读是怎么实现的答案是通过MVCC版本链ReadView机制。快照读时事务创建ReadView记录活跃事务列表之后查询只读取创建快照时已经提交的版本。当前读for update则通过行锁间隙锁防止幻读。6.3 一条慢SQL的排查思路与优化方向SQL优化这块面试官更看重你的排查思路而不是死记结论。我建议你按照先定位慢SQL再分析执行计划最后针对性优化这个链路来回答。第一步开启慢查询日志或者通过数据库性能监控平台发现耗时超过阈值的SQL。第二步用EXPLAIN分析执行计划重点看type字段从好到差依次是const、eq_ref、ref、range、index、ALLkey字段实际使用的索引rows字段预估扫描行数。第三步针对性优化。常见的优化手段包括避免SELECT *只查询需要的字段避免在WHERE子句中对索引列使用函数或运算使用覆盖索引减少回表大表分页查询可以用延迟关联或者游标分页。举一个我实际遇到的慢SQL优化案例一张订单表最初的查询是SELECT * FROM orders WHERE status 1 ORDER BY create_time LIMIT 100000, 20status字段有索引。你会发现这个SQL走了status索引但分页太深EXPLAIN里显示扫描了十几万行。优化方案是改成延迟关联先查主键再回表查完整数据。改造后的SQL大概是SELECT * FROM orders INNER JOIN (SELECT id FROM orders WHERE status 1 ORDER BY create_time LIMIT 100000, 20) t ON orders.id t.id执行时间从几百毫秒降到了几十毫秒。SQL优化这块不仅面试加分实际工作中也几乎天天用得到值得花时间把EXPLAIN的每个字段都研究清楚。我在带实习生的过程中发现八股文不是背出来的是练出来的。最有效的准备方式是把每道题当成一个话题先自己讲一遍讲到卡壳了就去查资料查完再讲直到能流畅地把原理、场景、坑点一条龙讲清楚。面试官问你的角度可能千变万化但核心知识点就那么多把底层原理吃透了怎么问都不怕。还有一个小技巧面试前一周每天花半小时把你整理的知识点从头到尾过一遍尤其是HashMap的put流程、线程池的执行流程、Spring Bean的生命周期这三条链路背到滚瓜烂熟面试时的底气会完全不一样。
返回列表