文章目录String、StringBuffer、StringBuilder的区别接口和抽象类的区别Java常见的异常类有哪些说一说Java面向对象三大特性说一说你对Java多态的理解Java重写和重载的区别final关键字有什么作用 和equals的区别Java的集合类有哪些那些是线程安全的那些是线程不安全的ArrayList 和 Array 有什么区别ArrayList 和 LinkedList 的区别是什么一、ArrayList 和 Array 的区别二、ArrayList 和 LinkedList 的区别ArrayList 扩容机制介绍一下HashMapHashMap 的底层实现是什么?解决Hash冲突的方法有哪些HashMap 是如何解决 hash 冲突的HashMap 的 put 方法流程HashMap 的扩容机制HashMap 为什么是线程不安全的? 如何实现线程安全concurrentHashMap 如何保证线程安全HashMap和ConcurrentHashMap的区别HashSet 和 HashMap 的区别HashMap 和 HashTable 的区别Java 创建线程有哪几种方式线程start和run的区别你知道Java中有哪些锁吗说一说你对 synchronized 的理解synchronized和lock的区别是什么synchronized和ReentrantLock的区别是什么volatile 关键字的作用有那些volatile 与synchronized 的对比JDK8有哪些新特性为什么要有线程池说一说线程池有哪些常用参数BIO、NIO、AIO 的区别Java 内存区域有哪些部分介绍一下什么是强引用、软引用、弱引用、虚引用有哪些垃圾回收算法有哪些垃圾回收器类加载机制介绍一下介绍一下双亲委派机制String、StringBuffer、StringBuilder的区别是什么string 是不可变的, 内部的字符数组用 final 修饰, 为不可变的字符串类当我们对String对象进行改变时, 会创建一个新的对象, 因此也是线程安全的stringbuffer 是可变的, 允许修改字符串, 线程安全, 内部的方法用synchronized修饰stringbuilder 是可变的, 允许修改字符串, 线程不安全性能如果要频繁进行拼接, 替换, 和删除等字符串运算, stringbuffer和stringbuilder效率高于string使用场景字符串不经常变化的场景可以使用String频繁修改字符串, 在单线程中, 可以用stringbuilder频繁修改字符串, 运行在多线程幻觉中, 用stringbuffer接口和抽象类的区别设计目的不同接口 定义的是能力和行为, 比如可飞行, 可奔跑抽象类 定义的是什么, 是动物, 还是交通工具本质与内容接口只声明方法, 不包含示例变量.可以看作一个极简的抽象类, 通常只包含抽象方法抽象类本质是一个类, 可以有成员变量, 构造方法, 具体方法和抽象方法继承与实现接口支持多继承, 一个类可以实现多个接口抽象类只支持单继承, 一个类只能继承一个抽象类使用场景当需要用跨不同的类的行为时, 用接口当要多个相关类提供基类, 用抽象类Java常见的异常类有哪些Java的异常都是继承 Throwable异常又分为 RuntimeException(NullPointerException) 和其他异常(IOException)RuntimeException, 运行时异常:运行时才可能抛出的异常编译器不会处理此类异常。代码逻辑错误(空指针, 数组越界等), 就是程序员的bug其他异常也被称为编译时异常, 检查异常:程序本身没问题, 外部问题(IO, 数据库等), 这部分异常编译器要求必须处置, 必须用 try-catch 或 throws 处理说一说Java面向对象三大特性封装:就是把对象的属性和方法打包到一起, 隐藏内部实现细节, 通过有限的接口和外部交互通常使用关键字private、protected、public等来定义访问权限以实现封装。继承:允许一个类继承了另一个类的属性和方法. 子类可以复用父类的代码, 可以添加新的方法或修改已有的方法Java支持单继承一个类只能直接继承一个父类。多态:就是一个接口, 多种实现允许父类的引用指向子类的对象并在运行时确定具体调用的方法说一说你对Java多态的理解多态是指同一个行为具有多种表现形式即父类引用可以指向子类对象调用方法时会执行子类的实现。简单理解一个接口或父类多种实现。// 多态的体现父类引用指向子类对象Animala1newDog();// 编译时类型是Animal运行时类型是DogAnimala2newCat();// 编译时类型是Animal运行时类型是Cata1.speak();// 输出汪汪汪实际调用Dog的方法a2.speak();// 输出喵喵喵实际调用Cat的方法Java重写和重载的区别方法重载就是编译时多态在编译时就确定调用哪个方法, 根据参数类型,个数, 顺序决定classCalculator{// 方法重载方法名相同参数不同publicintadd(inta,intb){returnab;}publicdoubleadd(doublea,doubleb){returnab;}publicintadd(inta,intb,intc){returnabc;}}编译时就知道调用哪个方法看参数类型和数量方法重写就是运行时多态运行时才确定调用哪个方法, 根据对象的实际类型决定Animalanimal;// 编译时类型是Animal// 运行时才确定调用哪个类的speak()animalnewDog();animal.speak();// 输出汪汪汪animalnewCat();animal.speak();// 输出喵喵喵final关键字有什么作用背景在开发中有时需要限制对核心逻辑的操作, 防止被意外改变或破坏。Java 提供了 final 关键字来满足这些需求。是什么final 表示“不可变”可以修饰三个地方修饰类该类不能被继承修饰方法该方法不能被重写修饰变量变量成为常量基本类型数值不可变引用类型引用指向不可变但对象内容可变为什么修饰类保证类的核心逻辑不被子类篡改如 String 类保证字符串的安全性和不可变性修饰方法防止子类重写关键方法确保行为一致修饰变量固定值提升代码可读性防止误修改配合 static 可定义全局常量 和equals的区别背景在 Java 中判断两个对象是否相等是常见需求。但“相等”有两个维度是否是同一个对象内存地址相同还是逻辑上内容相同。 和 equals() 分别对应这两种场景开发者需要根据业务场景正确选择。是什么比较的是内存地址基本类型比较数值是否相等引用类型比较两个引用是否指向同一个对象equals()比较的是内容是 Object 类中的方法默认行为等同于 比较地址需要子类重写才有意义例如 String、Integer 等已重写用于比较内容是否相等重写 equals() 必须同时重写 hashCode()否则在 HashMap、HashSet 等集合中会出现逻辑错误为什么为什么用 当需要判断两个引用是否指向同一个对象时使用如对象唯一性校验为什么用 equals()当需要判断两个对象逻辑上相等时使用如判断两个字符串内容是否相同为什么重写 equals() 必须重写 hashCode()因为 HashMap、HashSet 等集合先通过 hashCode() 定位存储位置再用 equals() 判断是否相等。如果两者不一致会导致集合中元素重复或无法找到Java的集合类有哪些那些是线程安全的那些是线程不安全的一、背景Java 集合框架在设计时为了性能考虑大部分实现都是线程不安全的。但实际开发中既有单线程也有多线程场景因此需要区分哪些是线程安全的哪些不是。二、是什么线程安全的集合位于java.util.concurrent包ListVector古老全表锁、CopyOnWriteArrayList读多写少场景SetCopyOnWriteArraySet、ConcurrentSkipListSetMapHashtable古老、ConcurrentHashMap推荐QueueConcurrentLinkedQueue、ArrayBlockingQueue、LinkedBlockingQueue线程不安全的集合位于java.util包ListArrayList、LinkedListSetHashSet、LinkedHashSet、TreeSetMapHashMap、LinkedHashMap、TreeMap三、为什么为什么大部分集合线程不安全性能考虑同步会带来性能开销单线程场景不需要设计选择把选择权交给开发者按需选择同步方案ArrayList 和 Array 有什么区别ArrayList 和 LinkedList 的区别是什么一、ArrayList 和 Array 的区别背景数组长度固定、功能单一为了满足动态存储需求Java 提供了ArrayList。是什么对比项ArrayArrayList长度固定不可变动态可自动扩容存储可存基本类型和引用类型只能存引用类型泛型功能只有length属性提供add()、remove()等丰富方法为什么数组性能更高适合长度固定、性能敏感场景ArrayList使用更灵活适合需要动态增删的场景实际开发中ArrayList 使用更广泛二、ArrayList 和 LinkedList 的区别背景两者都是 List 的实现但底层数据结构不同性能表现各异。是什么对比项ArrayListLinkedList底层结构动态数组双向链表随机访问O(1)快O(n)慢中间插入/删除O(n)慢需移动元素O(1)快只改指针内存占用连续内存较省每个节点多存前后指针较耗为什么ArrayList 随机访问快数组通过索引直接定位LinkedList 中间插入快链表只改前后节点的指针为什么实际开发 ArrayList 用得多查询场景远多于增删场景且数组对 CPU 缓存友好综合性能更好使用建议查询多 →ArrayList中间增删多 →LinkedList大多数情况直接选ArrayListArrayList 扩容机制背景ArrayList 底层基于数组实现但数组长度固定。为了支持动态添加元素需要自动扩容机制。是什么1. 初始容量JDK 1.8 后无参构造初始容量为0懒加载第一次add()时扩容到10也可通过构造器指定初始容量2. 扩容规则javaint newCapacity oldCapacity (oldCapacity 1); // 1.5 倍每次扩容为原容量的1.5 倍10 → 15 → 22 → 33 → 49 …3. 扩容过程计算新容量1.5 倍创建新数组用Arrays.copyOf()复制原数组元素原数组被 GC 回收为什么1. 为什么是 1.5 倍不是 2 倍避免内存浪费不是 1.1 倍避免频繁扩容影响性能在时间和空间之间取得平衡2. 为什么采用懒加载初始容量 0节省内存很多 ArrayList 实际使用元素很少避免提前分配不必要的内存3. 性能建议能预知元素数量时指定初始容量减少扩容次数提升性能java// 不推荐可能多次扩容 ArrayListString list new ArrayList(); // 推荐指定容量避免扩容 ArrayListString list new ArrayList(10000);一句话总结ArrayList 在容量不足时会创建一个1.5 倍大小的新数组并复制元素能预知数据量时建议指定初始容量以减少扩容开销。介绍一下HashMap一、背景需要存储键值对并快速查找数组和链表都无法同时满足HashMap 结合两者优点提供 O(1) 的平均查询速度。二、是什么1. 底层结构JDK 1.8数组 链表 红黑树链表长度 8 且数组长度 ≥ 64 时链表转红黑树红黑树节点 6 时退化为链表2. 核心参数初始容量16加载因子0.75扩容倍数2 倍阈值 容量 × 加载因子3. put 流程计算 hash(key.hashCode()) ^ (h 16)计算索引(n - 1) hash该位置为空 → 直接插入不为空 → 判断 key 是否相等相等则覆盖是红黑树 → 红黑树插入是链表 → 遍历链表尾插法插入插入后判断是否转红黑树元素个数 阈值 → 扩容4. get 流程计算 hash 和索引找到位置后先判断第一个节点不匹配则按链表或红黑树查找5. 扩容机制触发元素个数 阈值新容量 旧容量 × 2JDK 1.8 优化元素要么在原位置要么在原位置 旧容量无需重新计算 hash效率更高三、为什么1. 为什么容量是 2 的幂HashMap 容量为 2 的幂是为了用位运算(n-1) hash替代取模提升性能同时扩容时只需位运算判断新增位无需重新计算 hash大幅提升扩容效率。扩容时容量翻倍元素的新位置只需判断(hash oldCap) 0为 0 → 位置不变不为 0 → 原位置 旧容量不需要重新计算 hash 值只需一次位运算效率极高2. 为什么加载因子是 0.75时间和空间的平衡太大1.0冲突多查询慢太小0.5频繁扩容浪费空间3. 为什么引入红黑树解决链表过长导致查询退化为 O(n) 的问题红黑树查询 O(log n)性能更好阈值 8 基于泊松分布概率极低4. 为什么线程不安全没有同步机制HashMap 所有方法都没有 synchronized 或锁保护非原子操作put、get、扩容等操作不是原子的多线程下会相互干扰5. 重写 equals 为什么要重写 hashCodeHashMap 先用 hashCode 定位再用 equals 判断只重写 equals 会导致相同对象 hash 不同存到不同位置逻辑错误HashMap 的底层实现是什么?HashMap 在 JDK 1.8 之后采用数组 链表 红黑树的结构“数组的每个位置称为一个桶bucket桶中可能存储三种情况null- 没有元素链表- 哈希冲突时使用红黑树- 链表过长时转换”当插入一个键值对时首先计算 key 的哈希值“桶[3]为空 → 直接创建新节点”“桶[3]有节点 → 遍历链表/树”size如果 size threshold容量×0.75进行扩容”扩容是 HashMap 性能的关键。新容量 旧容量 × 2所有元素需要重新分配位置解决Hash冲突的方法有哪些HashMap 是如何解决 hash 冲突的解决哈希冲突的方法主要有以下两种链地址法在数组的每个位置维护一个链表。当发生冲突时新的元素会被添加到链表的尾部。开放寻址法当发生冲突时根据某种探测算法在哈希表中寻找下一个空闲位置来存储元素。Java中的HashMap使用链地址法解决hash冲突。为什么选择链地址法而不是开放寻址法对负载因子更友好开放寻址法对负载因子敏感负载因子过高时探测序列变长性能急剧下降通常负载因子不超过 0.7。链地址法可以容忍更高的负载因子HashMap 默认 0.75且性能下降较平缓。删除操作简单开放寻址法删除元素时需要特殊标记如“已删除”状态否则会破坏探测路径实现复杂且容易产生“墓碑”堆积。链地址法删除元素只需在链表中操作逻辑简单。避免聚集问题开放寻址法尤其是线性探测容易产生主聚集导致连续冲突区域扩大。链地址法冲突元素分散在各链表中不会相互干扰探测过程。空间利用率与动态扩展HashMap 使用链地址法 树化在冲突严重时仍能保持较好性能。开放寻址法需要保证数组有足够空闲位置扩容更频繁。HashMap 的 put 方法流程“我通过一个具体例子说明 put 的完整流程”检查数组“如果 table 为空先调用 resize() 初始化默认16”计算位置“比如 put(“name”, “张三”)hash(“name”) 15 算出索引为 3”桶状态判断“桶[3]为空 → 直接创建新节点”“桶[3]有节点 → 遍历链表/树”键值处理“找到相同key → 覆盖value”“没找到 → 尾插法插入新节点”结构转换“插入后如果链表长度 ≥ 8且数组长度 ≥ 64转换为红黑树”扩容检查“size如果 size threshold容量×0.75进行扩容”HashMap 的扩容机制扩容是 HashMap 性能的关键。新容量 旧容量 × 2所有元素需要重新分配位置。负载因子越小扩容越频繁空间利用率低但链表短查询快负载因子越大扩容越少空间利用率高但链表可能长查询慢默认0.75在时间和空间成本间取得平衡HashMap 为什么是线程不安全的? 如何实现线程安全concurrentHashMap 如何保证线程安全HashMap和ConcurrentHashMap的区别HashSet 和 HashMap 的区别HashMap 和 HashTable 的区别Java 创建线程有哪几种方式线程start和run的区别你知道Java中有哪些锁吗说一说你对 synchronized 的理解synchronized和lock的区别是什么synchronized和ReentrantLock的区别是什么volatile 关键字的作用有那些volatile 与synchronized 的对比JDK8有哪些新特性为什么要有线程池说一说线程池有哪些常用参数BIO、NIO、AIO 的区别Java 内存区域有哪些部分介绍一下什么是强引用、软引用、弱引用、虚引用有哪些垃圾回收算法有哪些垃圾回收器类加载机制介绍一下介绍一下双亲委派机制