List、Set、Map异同
参考本人关于Collection
接口的文章:https://blog.csdn.net/Z_H_sheng/article/details/139316621
List
ArrayList
- 基于数组实现的动态数组。
- 支持随机访问(通过索引),因此对于随机访问性能较好。
- 适用于读取操作频繁的场景。
- 在尾部进行插入、删除操作的性能较好,但在中间或头部插入、删除操作的性能相对较低。
- 扩容机制:超出长度存数时需要新建数组,然后将老数组的数据拷贝到新数组,如果不是尾部插入还会涉及元素移动,使用尾插法并指定初始容量可以极大提升性能,甚至超过
LinkedList
(需要创建大量Node
对象)
LinkedList
- 基于链表实现的双向链表。
- 插入、删除操作的性能较好,尤其是在中间或头部。
- 不支持随机访问,需要从头或尾部开始遍历到指定位置。
- 内存消耗比
ArrayList
稍大。 - 遍历
LinkedList
必须使用Iterator
不能使用for循环,因为每次for
循环体内通过get(i)
取得某一元素时都需要对list
遍历。 - 不要试图使用
IndexOf
等返回元素索引,其本质也是遍历
Vector
- 类似于
ArrayList
,但是是线程安全的。 - 所有方法都使用
synchronized
关键字进行同步,因此在多线程环境下使用。 - 由于同步开销,性能通常低于
ArrayList
。
CopyOnWriteArrayList
- 线程安全的
ArrayList
的替代实现。 - 写操作会创建一个新的底层数组,所有的写操作都在这个副本上进行,不影响原始数组,因此写操作较慢,但读操作快速且不需要同步。
- 适用于读操作远远多于写操作的场景。
理解:在CopyOnWriteArrayList
中,并发情况下,每个写操作都会在各自的副本数组上进行修改,每个线程都会成功地修改自己的副本数组,只是最终只有一个线程的修改会被应用到原始数组上(一个写操作完成后,会将原始数组的引用指向新的副本数组)。
Set
HashSet
- 基于哈希表实现,不保证元素的顺序。
- 添加、删除、查找元素的时间复杂度通常为O(1)。
TreeSet
- 基于红黑树(Red-Black Tree)实现,可以保持元素的自然顺序或者指定的排序顺序。
- 添加、删除、查找元素的时间复杂度通常为O(log n)。
LinkedHashSet
- 基于哈希表和链表实现,可以保持元素的插入顺序。
- 添加、删除、查找元素的时间复杂度通常为O(1)。
EnumSet
- 专门用于存储枚举类型元素的集合,非常高效。
ConcurrentSkipListSet
- 基于跳表(Skip List)实现的并发安全的
Set
。 - 可以支持并发访问,且元素按照升序排序。
跳表是一种有序的数据结构,类似于平衡树,但实现起来更加简单。跳表通过在链表中增加多级索引来加速查询操作,使得查找、插入和删除操作的时间复杂度均为O(log n),其中n是元素的数量。
ConcurrentSkipListSet适用于需要在多线程环境中安全地进行操作,并且需要有序集合的场景。它提供了一种高效的并发安全的有序集合实现,可以满足多线程并发访问时的要求。
CopyOnWriteArraySet
- 基于
CopyOnWriteArrayList
实现的Set
,线程安全的。 - 适用于读操作远远多于写操作的场景。
原理与CopyOnWriteArrayList
相同。
Map
HashMap
- 基于
Node<K,V>[] table
数组存储KV
节点 - 允许存储
null
键和null
值 - 无序存储(元素并不按顺序插入,经过哈希算法插入)
- 注意扩容机制(另写文章)
- 线程不安全,且可能会有哈希冲突
HashTable
- 基于数组存储
Entry
节点且无序存储,和HashMap
原理相同 - 在
HashMap
的基础上,给各个方法加上了synchronized
关键字,保证线程安全 - 不允许存储
null
键和null
值
ConcurrentHashMap
- 并发性能更高的
HashTable
升级版,其他特性与HashTable
相似 - 原理是锁数组中每一个
Node
(JDK8之前是分段锁),相较于HashTable
粒度更小,性能更高。
tips: 做一个简述版总结,方便大致扫一眼回顾细节,面试常问的扩容机制以及JUC 包中的更多细节会另写文章。