Java集合2025热门八股文学习(面试鸭题目)

87 阅读11分钟

介绍

这是一篇有深度的笔记,看完虽然不能提升你的实操,但能让你有跟大佬交谈的资本!

这是从面试网站自我理解整理的面试笔记,如有不对的地方请大佬指正!也欢迎大家关注我,和我一起学习交流,如果文章对你有些许帮助,不妨不要吝啬大伙的点赞收藏,这是我出下一篇笔记最大的支持~~~

如果侵权纯属巧合,通知我会修改

说说 Java 中 HashMap 的原理?

HashMap是哈希表的数据结构,用于存储键值对,核心是将键的哈希值映射到索引位置,通过数组+链表+红黑数来处理哈希冲突。
通过hashcode()计算出对象在散列表中的哈希值,并通过indexFor()方法找到存储地址(jdk1.7后移出该方法直接通过((n - 1) & hash))确定元素在数组的存储位置。哈希值是经过一定扰动处理,防止哈希值分布不均匀,减少哈希冲突。

hashMap默认大小为16,负载因子为0.75,当存储元素超过16*0.75 = 12个时,hashMap会触发扩容机制,容量 * 2 并重新分配元素位置。这种扩容比较耗时(重哈希)
如果地址上为空,那么直接放入,如果不为空,如果键key相等那么则直接替换该值,如果不相等,那称之为哈希冲突,此时会在原来数组的基础上转为链表结构,当对象超过8个的时候会优化成红黑树,如果对象少于6个那则会退化成链表。

使用 HashMap 时,有哪些提升性能的技巧?

1.调整合理的负载因子:默认负载因子是0.75,如果负载因子过小,查询速度快,哈希冲突减少,但是内存占用增多。如果负载因子过大,查询速度低,则哈希冲突增多。

2.合理设置初始值:hashmap默认大小是16,可以自定义大小,以减少频繁扩容

3.确保hashCode均匀分布:对应key的hashCode()方法需均匀分布,减少哈希冲突。避免使用质量不高的哈希函数,防止大量键映射到相同槽位。

什么是 Hash 碰撞?怎么解决哈希碰撞?

hash碰撞:指不同对象通过hashCode映射到相同的槽位。

1.拉链法:同个键不同值,在同一个槽位通过链表进行存储不同元素。

2.开放寻址法:通过映射到的位置再找到下一个空余的位置。

3.二次哈希:在原有hashCode的基础上再次hash,找到第二次地址。

Java 的 CopyOnWriteArrayList 和 Collections.synchronizedList 有什么区别?分别有什么优缺点?

CopyOnWriteArrayList:线程安全的list实现,通过写时复制实现线程安全。

每次对List的修改操作都会创建并复制原来数组。不需要加锁,写操作需要加锁

优点:

读操作过程中:不会加锁。并发修改数组时候,也不会影响读操作,因为修改数组通过复制对象,并发读性能高。

写操作过程中:加锁。每次复制原对象,在多写操作场景下性能低。

内存消耗大:每次复制创建并复制新数组,内存开销是原来的两倍List大且复制时间久,开销大。

CopyOnWriteArrayList适合读多写少的并发场景。

synchronizedList:提供了一个方便为List转为线程安全的方法。为list的每个方法(add,remove,get,set)进行同步(加synchronized锁),保证线程安全。

优点:使用方便将list变为线程安全版本。

缺点:并发低,读写操作都加锁。

synchronizedList适合将简单List转为线程安全版本临时使用的场景,但并发不高。

Java 中有哪些集合类?请简单介绍

分为

Collection接口、Map接口

Collection接口分为:List,Set,Queue。

实现类:

List:

ArrayList:基于动态数组,查找速度快O(1),增删O(n)(尾部元素除外)

LinkedList:基于双向链表,查找速度慢O(n),增删O(1)

Vector:线程安全的动态数组,类似于ArrayList,但开销大

Set:

HashSet:基于哈希表,无序,不可重复,查找快。

LinkedHashSet:基于哈希表和链表,不可重复,维护插入顺序。

TreeSet:基于红黑树,元素有序,不可重复

Queue:

PriorityQueue:基于优先级堆,元素按照自然顺序或指定比较器排序。

LinkedQueue:可以作为队列使用,支持先进先出操作。

Map实现类:

HashMap:基于哈希表和链表,键值对无序,查找快,不允许键重复

TreeMap:基于红黑树和哈希表,查找快,键值对有序,不允许键重复,键不为null

LinkedHashMap:基于哈希表和链表,维护插入顺序,不允许键重复

ConcurrentHashMap:线程安全的哈希表,适合高并发环境,不允许键或值为null

HashTable:线程安全的哈希表,不允许键或值为null

数组和链表在 Java 中的区别是什么?

数组:基于动态数组生成,内存空间连续且固定,但容易浪费空间,提供下标,查找速度O(1),增删O(n)(尾部元素除外),适合查多增删少环境

链表:基于链表生成,内存空间分散且动态扩展,查找速度O(n),增删O(1),适合多增删少查找环境

Java 中的 List 接口有哪些实现类?

ArrayList、LinkedList、Vector、Stack、CopyOnWriteArrayList几个实现类

ArrayList、LinkedList都不是并发容器,线程不安全。

ArrayList:线程不安全。基于动态数组,查找快增删慢。默认长度为10,通过Arrays.copyOf()复制原来的数组,在空间不足扩容为原来的1.5倍,连续内存空间

LinkedList:线程不安全。基于双向链表,查找慢,增删快。长度不限

Java 中 ArrayList 和 LinkedList 有什么区别?

Java ArrayList 的扩容机制是什么?

ArrayList中的元素数量超过当前容量的时候,会触发扩容机制。

在jdk1.8,在add方法的时候开辟空间,真正使用时创建数组,默认数组长度为10,容量不足时会通过Arrays.copyOf()扩容到原来的1.5倍。没有负载因子概念

Java 中的 HashMap 和 Hashtable 有什么区别?

hashMap:线程不安全但速度快,键允许一个null、值允许多个null,继承abstractMap类,实现类map接口,使用的是快速失败Iterator(并发修改会抛出修改异常)

hashTable:线程安全但速度慢,但是通过全局synchronized锁住,速度较慢,键和值不为null,实现类map接口,使用的是enumerator(不抛出异常)

ConcurrentHashMap 和 Hashtable 的区别是什么?

ConcurrentHashMap:线程安全,写时加锁,jdk8之前采用分段锁,jdk8之后采用CAS+Synchronized确保线程安全,插入元素时如果元素位置为空,那么采用无锁CAS插入元素,如果有元素则采用synchronized锁住头元素,锁粒度更低,保护线程安全。读时不加锁,键和值都不为Null。

Hashtable:线程安全,全局锁,写和读都加锁,并发性能低,键和值都不为Null。

总结:ConcurrentHashMap更适合并发较高的场景

Java 中的 HashSet 和 HashMap 有什么区别?

HashSet:基于hashmap的set集合,用于存储元素,元素不能重复,无序。它的底层是基于HashMap的键值对实现,值都是为present,并且方法实际上还是调用hashmap的方法,查找效率高。

HashMap:是存储键值对的集合,键和值允许为null,键允许一个为null,值允许多个null;

Java 中 HashMap 的扩容机制是怎样的?

HashMap的负载因子默认为0.75,默认大小为16,当hashmap内部元素超过16*0.75=12个,那么会对其进行扩容,扩容大小为原来的2倍,在jdk8之前原来的元素会重新进行哈希计算称为重哈希,在jdk8之后原来的元素会通过&运算高位是否需要换位置,通过位运算加上原数组长度成为新数组长度。

为什么 HashMap 在 Java 中扩容时采用 2 的 n 次方倍?

1.减少数组元素的移动:通过比较高位查看是否移动,减少了原来不必要的移动,提升rehash速度

2.提高哈希值的均匀分布:通过(n-1)&hash,这种计算只能通过2的n次方去提升元素的分布均匀性

3.提升速度:位运算的效率高于模运算

为什么 Java 中 HashMap 的默认负载因子是 0.75?

HashMap默认负载因子为0.75主要是考虑到时间复杂度和空间复杂的合适数值,避免了频繁扩容影响性能和过多的哈希冲突。

如果负载因子过大,会导致过多的哈希冲突,但空间利用率高。负载因子过小,频繁扩容会导致内存空间消耗过大,但是减少哈希冲突。

为什么 JDK 1.8 对 HashMap 进行了红黑树的改动?

原来是数组加链表形式,链表元素存储过多会导致查找效率退化成O(n),通过引入红黑树,既可以保证解决哈希冲突的同时,又比原来链表速度快,查找增删速度O(logn)。当链表长度超过8时会自动成红黑树,如果链表长度为6以下时会退化成链表。

JDK 1.8 对 HashMap 除了红黑树还进行了哪些改动?

1.哈希函数优化:通过(n-1)&hash优化,采用2的n次方存储,极大地发挥了哈希扰乱因子,使得元素分布更均匀

2.头插法改为尾插法:减少了死循环,原先头插法虽然添加元素快捷,但在并发过程中很容易出现回环导致死循环。

3.改进扩容:通过&运算判断元素哈希高位是否需要调整位置,减少rehashing过程。

Java 中的 LinkedHashMap 是什么?

LinkedHashMap:基于链表生成,用于记录保存插入元素顺序或访问顺序,基于双向链表实现。

使用场景:缓存实现(可以根据访问时间移除最久元素),数据存储(保持元素的插入顺序)

Java 中的 TreeMap 是什么?

TreeMap内部通过红黑树实现,可以让key实现Comparable接口进行默认排序或自定义comparator接口传入构造函数,这样节点会根据规则去排序。

特点:

1.红黑树是一种自平衡二叉查找树,增删改查效率都是O(logn)

2.键的有序性:默认comparable实现,也可以自定义排序

3.键不为null,值可以为null

常用于加密根据字母进行排序。

Java 中的 IdentityHashMap 是什么?

IdentityHashMap是基于hashMap实现,元素出现哈希冲突时对比键时采用==比较符,比较的是键的引用对象的地址,而hashMap是采用equals比较对象内容是否一致。

使用场景:对比对象是否应引用同一实例场景。

Java 中的 WeakHashMap 是什么 ?

WeakHashMap:是基于hashMap开发,但是键采用弱引用。弱引用允许垃圾回收时在没有其他强引用指向该对象时回收它的内存。所以,当一个键不再被其他对象引用时,WeakHashMap会自动删除相关条目。

常用于缓存的实现。当缓存的键不再被使用时,可以自动移除相应缓存,释放内存资源。

Java 中 ConcurrentHashMap 1.7 和 1.8 之间有哪些区别?

jdk1.7用的是分段锁形式,锁住segement,最多16个线程。只采用了数组+链表的形式

jdk1.8时用的是CAS+Synchronized,当插入元素为空时用CAS无锁插入,当有元素时,用synchronized只锁住头元素,锁的粒度更低,并发更高。采用数组+链表+红黑树

Java 中 ConcurrentHashMap 的 get 方法是否需要加锁?

ConcurrentHashMap中的get方法不需要加锁,通过volitile关键字,确保get方法的线程安全,即使在写入时,读取数据依然能获取到最新数据,不会引发并发问题。

具体通过Unsafe方法的getObjectVolatile()和用volitaile来修饰节点的val和next指针来保证可见性。

为什么 Java 的 ConcurrentHashMap 不支持 key 或 value 为 null?

避免不必要的错误:在并发访问时,会导致不可预知的行为和错误。如果key和value都为null时,当concurrentHashMap在使用get(key)时,返回null不知道是key为null还是值为null,会造成不必要的误会,因此为了避免这种情况,所以不支持。

简化代码开发:如果允许为null的话,会给put、get、containsKey等操作增加复杂性。也会造成并发或修改下导致数据不一致状态,增加调试和维护难度。

Java 中的 CopyOnWriteArrayList 是什么?

是Java中的一个线程安全的动态数组实现。通过写时复制,在每次写入的时候新复制一个数组去接收原先原数组内容来保证线程安全。但读不会加锁,适合读多写少环境。

你遇到过 ConcurrentModificationException 错误吗?它是如何产生的?

在并发环境下,通过Iteartor操作集合元素时会快速失败(fail-fast),有其他线程对其进行修改,会报ConcurrentModificationException错误。为了保证集合迭代时语义的一致性。