Java核心源码高频面试题及详细答案(实战向)_第1页
Java核心源码高频面试题及详细答案(实战向)_第2页
Java核心源码高频面试题及详细答案(实战向)_第3页
Java核心源码高频面试题及详细答案(实战向)_第4页
Java核心源码高频面试题及详细答案(实战向)_第5页
已阅读5页,还剩2页未读, 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

Java核心源码高频面试题及详细答案(实战向)一、String源码相关面试题1.说一下String的不可变性,底层原理是什么?String的不可变性指的是字符串一旦创建完成,就不能修改它的内部字符内容,所有的增删改操作本质都是生成新字符串。底层核心原理分两点:第一,JDK1.9之前,String底层是finalchar[]数组;JDK1.9及之后改为finalbyte[]数组,同时做了编码压缩,纯英文数字用单字节存储,节省内存。数组被final修饰,意味着数组的引用地址不可修改,且源码没有提供任何修改数组元素的公开方法。第二,String类本身被final修饰,无法被继承,避免了子类重写方法破坏不可变性。很多人会误以为拼接字符串是修改原字符串,其实不管是+拼接还是substring截取,都会在内存中创建新的String对象,原对象始终不会改变。2.String为什么要设计成不可变的?主要是工程层面的性能和安全考量,实际开发中核心优势有四个:1.字符串常量池复用:因为不可变,多个引用可以共用常量池中的同一个字符串对象,不会出现一个引用修改、其他引用受影响的情况,大幅节省内存。2.保证HashMap键的唯一性:String是HashMap最常用的键,不可变保证了hash值一旦计算就不会改变,避免因为字符串修改导致哈希定位错乱、数据丢失。3.线程安全:不可变对象天然线程安全,多线程并发访问时不需要加锁,不会出现并发修改异常。4.安全可靠:常用于存储密码、路径、网络地址等关键信息,不可变可以防止数据被恶意篡改。3.String、StringBuilder、StringBuffer的底层区别?1.String:不可变,每次拼接生成新对象,频繁拼接会产生大量无效垃圾对象,效率最低,适合少量字符串定义。2.StringBuffer:可变字符数组,所有修改方法都加了synchronized锁,线程安全,但并发场景下锁竞争会导致效率偏低,适合多线程字符串拼接。3.StringBuilder:可变字符数组,无锁、线程不安全,没有同步开销,效率最高,是单线程拼接字符串的首选,日常开发99%场景都用它。底层扩容逻辑:两者初始容量都是16,拼接超出容量时,会自动扩容为原容量的2倍+2,同时复制原数组元素到新数组。二、ArrayList源码相关面试题1.ArrayList底层结构和初始容量、扩容机制?ArrayList底层是动态扩容的Object数组,基于数组实现,所以查询快、增删慢。初始容量:空参构造创建的ArrayList,初始是空数组,并不会直接初始化16容量,是首次添加元素时才初始化容量16,这是JDK1.8的优化,延迟初始化节省内存。带初始容量的构造方法,会直接初始化对应容量的数组。扩容机制:当数组元素个数超出当前容量时,触发扩容,核心方法是grow()。默认扩容比例是原容量的1.5倍,通过位运算实现,效率很高。扩容时会创建新数组,通过Arrays.copyOf()复制原数组所有元素,舍弃原数组。如果一次性添加大量元素,1.5倍扩容不够用,会直接扩容到刚好容纳所有元素的容量,避免多次扩容。2.ArrayList为什么线程不安全?并发场景会出现什么问题?ArrayList的add、set等修改方法没有加锁,多线程并发写入时会出现线程不安全问题,核心问题有两个:第一,元素覆盖丢失:两个线程同时执行add方法,同时获取到数组尾部索引,先后写入同一个位置,会导致前一个线程的元素被覆盖,数据丢失。第二,数组越界异常:多个线程同时判断容量,都认为不需要扩容,同时执行添加,超出数组最大容量,直接抛出ArrayIndexOutOfBoundsException。日常并发场景下,推荐使用CopyOnWriteArrayList,写时复制,线程安全且效率高于Vector。3.ArrayList的subList方法有什么坑?subList返回的不是新的ArrayList对象,而是ArrayList的内部SubList视图,底层和原集合共用同一个数组。核心坑点:1.修改subList返回的子集合元素,原集合元素会同步改变;修改原集合元素,子集合也会变化。2.如果原集合在截取后进行增删操作,会修改数组结构,再次操作子集合会直接抛出ConcurrentModificationException。如果需要独立的子集合,需要手动newArrayList<>(subList结果),新建集合脱离原数组。三、HashMap核心源码面试题(重中之重)1.JDK1.8HashMap底层数据结构?底层是数组+链表+红黑树的复合结构。数组是主体,每个数组位置称为一个桶;发生哈希冲突时,同一桶位的元素以单向链表存储;当链表长度大于等于8,且数组容量大于等于64时,链表会转化为红黑树,提升查询效率;如果后续树节点数量减少到6以下,会重新退化为链表。这么设计的核心目的:链表查询是O(n),红黑树查询是O(logn),大数据量下能大幅优化哈希冲突带来的性能问题。2.HashMap初始容量、负载因子、扩容机制?1.初始容量:默认16,且HashMap规定容量必须是2的幂次方,方便通过位运算替代取模运算,提升哈希定位效率。如果手动传入非2的幂次方容量,底层会自动向上取最近的2的幂次方。2.负载因子:默认0.75,是扩容的阈值系数。阈值=容量*负载因子,当元素数量超过阈值,触发扩容。0.75是时间和空间的平衡值,过小会频繁扩容浪费空间,过大哈希冲突变多、查询变慢。3.扩容机制:扩容后容量为原来的2倍,扩容后不需要重新计算所有元素的哈希值,只需要通过高位运算,将元素分为原桶位和原桶位+旧容量两个位置,大幅提升扩容效率,这是JDK1.8的核心优化。3.HashMap为什么要把链表转成红黑树?为什么阈值是8和6?链表长度较短时,遍历速度很快,开销很小;但链表过长的话,遍历耗时会线性增长。当链表长度达到8,大概率是哈希算法失效或者恶意哈希碰撞,此时转红黑树可以把查询时间复杂度从O(n)降到O(logn)。阈值设计8和6是为了防止频繁树化、退化的震荡:如果插入到8就树化,删除到8就退化,会出现反复转换的性能损耗,设置差值2可以有效规避这个问题。另外补充:必须同时满足链表长度≥8、数组容量≥64才会树化,如果数组容量小于64,只会触发扩容,不会树化,避免小容量数组无谓树化。4.HashMap为什么线程不安全?JDK1.7和1.8的并发问题区别?HashMap所有读写操作都没有加锁,并发场景下必然不安全,两个版本问题不同:JDK1.7:并发扩容时会出现链表环形引用,导致后续get方法死循环,CPU100%占用,同时会出现数据丢失、覆盖问题。JDK1.8:优化了扩容链表迁移逻辑,解决了环形链表问题,但依然存在数据覆盖、元素丢失、计数不准的问题,依旧不能用于并发场景。并发场景推荐使用ConcurrentHashMap。5.ConcurrentHashMapJDK1.8底层原理?核心是CAS+synchronized分段锁,摒弃了1.7的Segment分段锁,锁粒度更细,并发效率更高。1.初始化、数组扩容、树化等全局操作通过CAS保证无锁并发安全;2.普通的增改操作,锁定当前桶位的头节点,只锁住单个桶,不同桶位可以并发操作,锁粒度极小;3.底层同样是数组+链表+红黑树,默认初始容量16,负载因子0.75,扩容机制和HashMap基本一致;4.不支持key和value为null,避免空值歧义问题,而HashMap支持一个nullkey、多个nullvalue。四、线程与锁核心源码面试题1.synchronized底层源码原理?偏向锁、轻量级锁、重量级锁区别?synchronized是JVM层面的内置锁,底层基于对象头的MarkWord实现,锁状态会随着竞争情况自动升级,且锁只能升级不能降级。1.偏向锁:无锁竞争场景,默认开启,偏向第一个获取锁的线程,后续该线程重复获取锁,无需任何同步操作,开销最低,适合单线程重复加锁场景。2.轻量级锁:出现少量线程竞争,没有阻塞,通过CAS自旋尝试获取锁,不阻塞线程,用户态执行,适合竞争不激烈、锁持有时间短的场景。3.重量级锁:竞争激烈、CAS自旋多次失败后升级,依赖操作系统互斥量,线程会阻塞进入内核态,开销最大,适合高并发竞争场景。2.ReentrantLock底层AQS原理?AQS(抽象队列同步器)是Java锁和并发工具的底层核心,ReentrantLock就是基于AQS实现的。AQS核心结构:volatile修饰的state状态变量+FIFO双向阻塞队列。1.state变量:表示锁状态,0代表无锁,大于0代表有线程持有锁,可实现可重入(同一线程多次加锁,state累加);2.阻塞队列:抢锁失败的线程会封装成节点,进入队列排队阻塞;3.锁释放后,会唤醒队列头部的下一个线程继续抢锁。ReentrantLock分为公平锁和非公平锁:公平锁严格按照队列顺序获取锁,非公平锁线程抢锁时会直接尝试CAS抢占,效率更高,是默认模式。3.synchronized和ReentrantLock区别?1.底层层面:synchronized是JVM内置锁,原生语法;ReentrantLock是JDK代码层面实现的锁,基于AQS。2.可重入性:两者都是可重入锁。3.锁机制:synchronized锁状态自动升级、自动释放锁;ReentrantLock手动加锁、手动解锁,必须在finally中释放,否则会死锁。4.高级功能:ReentrantLock支持公平锁、可中断锁、超时锁、条件变量精准唤醒;synchronized都不支持。5.效率:低竞争下两者效率差不多,高竞争下ReentrantLock可控性更强,性能更稳定。五、线程池源码面试题1.ThreadPoolExecutor核心参数及底层工作原理?线程池7个核心参数:核心线程数、最大线程数、空闲超时时间、时间单位、任务队列、线程工厂、拒绝策略。底层执行任务流程(源码真实执行逻辑):1.提交任务后,首先判断当前核心线程数是否已满,未满则新建核心线程执行任务;2.核心线程已满,判断任务队列是否已满,未满则将任务存入队列排队;3.队列已满,判断当前线程数是否达到最大线程数,未达到则新建非核心线程执行任务;4.线程数已达最大值,触发拒绝策略。非核心线程空闲超时后会被回收,核心线程默认永久常驻,开启核心线程超时回收参数后也可被回收。2.线程池四种拒绝策略?1.AbortPolicy(默认):直接抛出任务拒绝异常,终止任务提交;2.CallerRunsPolicy:谁提交的任务谁执行,主线程自己跑,不会抛异常,能限流;3.DiscardPolicy:直接丢弃当前任务,不报错、不处理;4.DiscardOldestPolicy:丢弃队列中最旧的未执行任务,替换为当前新任务。六、JDK集合其他高频源码题1.LinkedList底层原理和优缺点?LinkedList底层是双向链表,每个节点存储前驱、后继指针和数据,没有扩容机制。优点:头尾增删元素效率极高,只需要修改指针引用,不需要复制数组;缺点:随机查询效率极低,get(index)会从头尾双向遍历查找,时间复杂度O(n),不适合高频查询场景

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论