Java集合
约 2835 字大约 9 分钟
2026-04-04
[TOC]
有哪些集合类?
Java 中有哪些集合类?请简单介绍 - Java 集合面试题 - 面试鸭 - 程序员求职面试刷题神器
Java 中的 LinkedHashMap 是什么? - Java 集合面试题 - 面试鸭 - 程序员求职面试刷题神器
Java 中的 List 接口有哪些实现类? - Java 集合面试题 - 面试鸭 - 程序员求职面试刷题神器
Java 中的 TreeMap 是什么? - Java 集合面试题 - 面试鸭 - 程序员求职面试刷题神器
Collection接口:存储对象集合类
List接口
ArrayList:动态数组,查询快,插入删除慢
LinkedList:双向链表,查询慢,插入删除快
Vector:线程安全的动态数组,类似ArrayList
Set接口
HashSet:哈希表,无序,不重复
LinkedHashSet:链表+哈希表,维护插入顺序,不重复
TreeSet:基于红黑树,有序,不重复
Queue接口
LinkedList:作为队列使用,支持FIFO(先进先出)
PriorityQueue:优先堆,自然排序或指定比较器排序
Map接口:key-value键值对
hashMap:哈希表,无需,key不重复
LinkedHashMap:链表+哈希表,维护插入顺序,key不重复
TreeMap:红黑树,键值对有序,key不重复,key不能为null
ConcurrentHashMap:线程安全的哈希表,key和值不能为null
数组和链表的区别
数组和链表在 Java 中的区别是什么? - Java 集合面试题 - 面试鸭 - 程序员求职面试刷题神器
Java 中 ArrayList 和 LinkedList 有什么区别? - Java 集合面试题 - 面试鸭 - 程序员求职面试刷题神器
数组
大小固定的连续存储空间
长度固定,扩展数组需要重新分配新的空间
可直接用下标访问
链表
不需要连续存储空间
扩展只需要使用指针存储新的地址空间,扩展方便
需要从头节点开始查找
额外占用空间存储指针,占用空间比数组大
hashMap原理
说说 Java 中 HashMap 的原理? - Java 集合面试题 - 面试鸭 - 程序员求职面试刷题神器
使用 HashMap 时,有哪些提升性能的技巧? - Java 集合面试题 - 面试鸭 - 程序员求职面试刷题神器
什么是 Hash 碰撞?怎么解决哈希碰撞? - Java 集合面试题 - 面试鸭 - 程序员求职面试刷题神器
HashMap是基于哈希表的数据结构,用于存储键值对。将键的hash值映射到数组索引位置,通过数组+链表(jdk1.8以后是数组+链表+红黑树)来处理哈希冲突。
使用hashcode()计算hash值,通过indexFor()(jdk1.8以后使用(n-1)& hash)确定元素在数组中的存储位置。
默认初始容量16,负载因子0.75。当存储元素数量超过16 * 0.75 = 12 个时,触发扩容操作,容量 * 2 并重新分配元素位置。
优化当多个元素映射到同一个哈希桶时的查找性能,当链表长度>8,链表会转变为红黑树(自平衡二叉树)。树中元素<6,会转换回链表
jdk1.7之前链表插入使用头插法,发生hash冲突时,新节点总是插入到链表头部;在多线程环境下,可能形成环,特别是在并发扩容时
jdk1.8,改为尾插法,保持插入顺序
元素数量 > 容量 * 负载因子,触发扩容 * 2,会从新计算所有键的索引,耗时操作
- 确定一个初始大小
- 调整负载因子
- hashcode均匀分布
保留插入顺序,使用LinkedHashMap
保证有序,使用TreeMap
线程安全,使用ConcurrentHashMap
hashcode冲突
- 使用链表存储
- 开放寻址
- 重新计算hashcode
concurrentHashMap1.7和1.8的区别
Java 中 ConcurrentHashMap 1.7 和 1.8 之间有哪些区别? - Java 集合面试题 - 面试鸭 - 程序员求职面试刷题神器
什么是 Java 的 CAS(Compare-And-Swap)操作? - 面试鸭 - 程序员求职面试刷题神器
1.7
使用分段锁,每一个segment是独立的,可并发访问,默认16
多个segment组成,每个segment包含一个hashmap。单独扩容
不加锁,三次获取有变化,重新加锁获取
1.8
移除segment,只在更新链表或红黑树节点使用synchronized锁住链表或树的头节点
增加红黑树
扩容整个hashmap,通过CAS操作确保线程安全,多个线程可逐步同时扩容
使用数组维护节点数量,使用CAS修改basecount,失败就选择countercell对象修改,basecount + 所有countercell
concurrentHashMap的get方法是否需要加锁?
Java 中 ConcurrentHashMap 的 get 方法是否需要加锁? - Java 集合面试题 - 面试鸭 - 程序员求职面试刷题神器
使用volatile,保证写入时,就算读取也能获取最新的数据
concurrentHashMap不支持key或value为空?
为什么 Java 的 ConcurrentHashMap 不支持 key 或 value 为 null? - Java 集合面试题 - 面试鸭 - 程序员求职面试刷题神器
避免混淆,并发环境下,如果通过key获取value1为空,就不清楚真的是空还是值是空的情况
HashMap和HashTable的区别
Java 中的 HashMap 和 Hashtable 有什么区别? - Java 集合面试题 - 面试鸭 - 程序员求职面试刷题神器
| 线程安全 | 性能差异 | 允许空值 | 继承结构 | 迭代器类型 | |
|---|---|---|---|---|---|
| hashMap | ❌ | 单线程环境下优于hashTable | 1个nul key,多个null值 | AbstractMap,Map的实现类 | 快速失败的Iterator |
| hashTable | ✅synchronized | 低于hashMap | 不允许null key和值 | Dictionary | 弱一致性的Enumerator |
ConcurrentHashMap是HashTable的替代方案。通过分段锁提高了并发性能,读无锁,写使用局部分段锁
ConcurrentHashMap和HashTable的区别
ConcurrentHashMap 和 Hashtable 的区别是什么? - Java 集合面试题 - 面试鸭 - 程序员求职面试刷题神器
都是线程安全的哈希表实现
hashtable:使用单一的synchronized锁对整个哈希表进行同步,效率低下
ConcurrentHashMap:CAS + synchronized 实现,如果node为空,使用cas将数据插入节点,如果不为空,使用synchronized锁定node的头节点
hashset和hashmap的区别
Java 中的 HashSet 和 HashMap 有什么区别? - Java 集合面试题 - 面试鸭 - 程序员求职面试刷题神器
hashset内部使用hashmap实现,hashset中的元素实际存储在hashmap的key中,value都是一个常量对象PRESENT
CopyOnWriteArrayList和Collections.synchronizedList()的区别
Java 中的 CopyOnWriteArrayList 是什么? - Java 集合面试题 - 面试鸭 - 程序员求职面试刷题神器
CopyOnWriteArrayList
线程安全的List实现,特性:写时复制
增删改操作会复制一个新的底层数组。读操作不加锁(volatile 修饰的数组),写操作加锁
合适读多写少
Collections.synchronizedList()
一个包装方法,将任何List转换为线程安全的版本,对增删改查使用synchronized进行同步,保证线程安全
适合临时转换为线程安全的List使用
ArrayList扩容机制
Java ArrayList 的扩容机制是什么? - Java 集合面试题 - 面试鸭 - 程序员求职面试刷题神器
默认大小10,超过当前容量,触发扩展机制,创建新数组大小为原来的1.5倍(int newCapacity = oldCapacity + (oldCapacity >> 1);)使用copyOf()将原数组复制到新数组
HashMap的扩容机制
Java 中 HashMap 的扩容机制是怎样的? - Java 集合面试题 - 面试鸭 - 程序员求职面试刷题神器
假设有一个 1G 大的 HashMap,此时用户请求过来刚好触发它的扩容,会怎样?让你改造下 HashMap 的实现该怎样优化? - 后端场景面试题 - 面试鸭 - 程序员求职面试刷题神器
基于负载因子决定。默认为0.75
默认扩容原大小的2次方,即2倍
在扩容时,并不是所有元素都需要重新计算哈希码。由于数组长度变为原来的两倍,新的数组索引计算方式为:
int newIndex = (newTable.length - 1) & hash;对于原来的哈希值hash,如果(hash & oldCap) == 0,那么在新数组中的索引位置不变;如果(hash & oldCap)!= 0,那么在新数组中的索引位置是原来的索引位置加上旧数组的容量。
这是因为数组长度翻倍后,新的索引计算实际上是在原来的基础上增加了一位判断。例如,假设原来数组长度为 8(二进制为1000),新数组长度为 16(二进制为10000)。原来的索引计算是hash & 0111,新的索引计算是hash & 1111。如果hash的第 4 位(从右往左,第 0 位是最低位)为 0,那么新的索引与原来相同;如果第 4 位为 1,那么新的索引就是原来的索引加上 8。
hashmap扩容时使用2的n次方?
为什么 HashMap 在 Java 中扩容时采用 2 的 n 次方倍? - Java 集合面试题 - 面试鸭 - 程序员求职面试刷题神器
通过(n-1)&hash计算元素的存储索引位置,只有在数组容量是n^2才能确保索引均匀分布。位运算的效率高于取模运算,提高了hash运算的效率
i = (n-1)&hash,满足这个公式,当b=2的n次幂是,a%b = a&(b-1)
hashmap负载因子默认0.75?
为什么 Java 中 HashMap 的默认负载因子是 0.75? - Java 集合面试题 - 面试鸭 - 程序员求职面试刷题神器
时间复杂度和空间复杂度度的合理平衡
高并发读取场景:0.5空间利用率低,hash冲突少,查找效率高,频繁扩容
内存受限场景:1.0空间利用率高,哈希冲突概率大,降低查找效率
为什么jdk1.8对hashmap进行了红黑树的改动?
为什么 JDK 1.8 对 HashMap 进行了红黑树的改动? - Java 集合面试题 - 面试鸭 - 程序员求职面试刷题神器
JDK 1.8 对 HashMap 除了红黑树还进行了哪些改动? - Java 集合面试题 - 面试鸭 - 程序员求职面试刷题神器
1.8之前使用链表解决hash冲突。当哈希冲突较多时,链表元素增多,查找、插入和删除的时间复杂度从O(1)->O(n)
使用红黑树,当数组长度>=64,链表长度>=8时,链表转化为红黑树;链表长度<6时,重新转换为链表;平衡二叉树,时间复杂度O(log n)
避免频繁树化,减少内存占用(红黑树需要额外指针和结构,红黑树节点大小是普通节点大小的两倍)
优化hash计算:扰动函数
扩容机制优化:不是所有元素都重新计算hashcode
头插法变为尾插法:避免多线程扩容操作出现环
贡献者
更新日志
fb8bc-更新为vuepress于