Java集合框架可以分为两条大的支线:
- Collection,主要由List、Set、Queue组成:
List代表有序,可重复的集合,典型代表就是封装了动态数组的ArrayList和封装了链表的LinkedList
Set代表无序,不可重复的集合,典型代表就是HashSet和TreeSet;
Queue代表队列典型代表就是双端队列ArrayDeque,以及优先级队列PriorityQueue; - Map,代表键值对的集合,典型代表就是HashMap。
List
List的特点是存取有序,可以存放重复的元素,可以索引访问,允许null值。
|----------------|------|-----------------|------|--------|----------|---------------|-------------|---------------|
| 实现类 | 底层结构 | 线程安全 | 顺序 | 允许null | 查询性能 | 插入/删除性能 | 扩容机制 | 适用场景 |
| ArrayList | 动态数组 | 否 | 插入顺序 | 允许 | O(1)随机访问 | 尾部O(1),中间O(n) | 默认10,1.5倍扩容 | 随机访问频繁,数据量不大 |
| LinkedList | 双向链表 | 否 | 插入顺序 | 允许 | O(n)顺序访问 | O(1)头尾操作 | 无扩容,节点动态创建 | 频繁插入删除,队列/栈操作 |
| Vector | 动态数组 | 是(synchronized) | 插入顺序 | 允许 | O(1)随机访问 | 尾部O(1),中间O(n) | 默认10,可指定增量 | 遗留代码,需要线程安全 |
Set
Set的特点是存取无序、不可以存放重复的元素,无索引访问
|---------------------------|-----------------------|------|------------|--------|----------|------------------------|-------------|---|
| 实现类 | 底层结构 | 线程安全 | 顺序 | 允许null | 性能 | 唯一性判断 | 适用场景 | |
| HashSet | HashMap | 否 | 无序 | 允许 | O(1) | hashCode() + equals() | 快速去重,不关心顺序 |
| LinkedHashSet | LinkedHashMap | 否 | 插入顺序/访问顺序 | 允许 | O(1) | hashCode() + equals() | 需要保持插入顺序的去重 |
| TreeSet | TreeMap(红黑树) | 否 | 自然排序/自定义排序 | 不允许 | O(log n) | compareTo()/Comparator | 需要排序的去重集合 |
| ConcurrentSkipListSet | ConcurrentSkipListMap | 是 | 自然排序/自定义排序 | 不允许 | O(log n) | compareTo()/Comparator | 并发有序集合 |
Map
Map保存的是键值对,键要求保持唯一性,值可以重复。
|-------------------|------------------|--------|-----------|----------|----------|-----------|--------------|
| 实现类 | 底层结构 | 线程安全 | 顺序 | 允许null键值 | 性能 | 扩容机制 | 适用场景 |
| HashMap | 数组+链表/红黑树(JDK8+) | 否 | 无序 | 键值都允许 | O(1)平均 | 默认16,2倍扩容 | 通用键值对存储,性能优先 |
| LinkedHashMap | 数组+链表+双向链表 | 否 | 插入顺序/访问顺序 | 键值都允许 | O(1)平均 | 同HashMap | 需要保持插入/访问顺序 |
| TreeMap | 红黑树 | 否 | Key的自然排序 | 键不允许 | O(log n) | 无扩容 | 需要按键排序的场景 |
| Hashtable | 数组+链表 | 是(全表锁) | 无序 | 键值都不允许 | O(1)平均 | 默认11,2n+1 | 遗留代码,线程安全 |
总结(List,Set,Map)
|------------|--------------------------------|--------------------------------------------|-----------------------------------------|
| 特性 | List | Set | Map |
| 存储结构 | 有序序列 | 唯一元素集合 | 键值对映射 |
| 元素重复 | 允许 | 不允许 | Key不允许重复,Value允许重复 |
| 顺序保证 | 插入顺序 | HashSet无序,TreeSet排序,LinkedHashSet插入顺序 | HashMap无序,TreeMap按键排序,LinkedHashMap插入顺序 |
| null处理 | 允许null | HashSet允许null,TreeSet不允许 | HashMap允许null键值,Hashtable不允许 |
| 访问方式 | 索引访问 | 迭代器遍历 | 通过Key访问Value |
| 主要实现 | ArrayList, LinkedList | HashSet, TreeSet, LinkedHashSet | HashMap, TreeMap, LinkedHashMap |
| 线程安全 | Vector, CopyOnWriteArrayList | CopyOnWriteArraySet, ConcurrentSkipListSet | Hashtable, ConcurrentHashMap |
| 性能特点 | ArrayList随机访问快,LinkedList插入删除快 | HashSet查询快,TreeSet有序 | HashMap查询快,TreeMap有序 |
| 使用场景 | 有序、可重复的数据集合 | 去重、集合运算 | 键值对查找、缓存、配置 |
基础八股文:
1,简述Java的Set
Set即集合,该数据结构不允许元素重复且无序。Java对Set有三种实现方式。
HashSet通过HashMap实现,HashMap的key即HashSet存储的元素,Value系统自定义一个名为PRESENT的Object类型常量。判断元素是否相同时,先比较hashCode,相同后再利用equals比较,查询效率为O(1)。
LinkHashSet继承自HashSet,通过LinkedHashMap实现,使用双向链表维护元素插入顺序。
TreeSet通过TreeMap实现的,底层数据结构是红黑树,添加元素到集合时按照比较规则将其插入合适的位置,保证插入后的集合仍然有序。查询效率为O(logn)
2,简述Java中的HashMap
JDK8之前底层实现是数组+链表,JDK8改为数组+链表/红黑树。主要成员变量包括存储数据的table数组,元素数量site、加载因子loadFactor。HashMap中数据以键值对的形式存在,键对应的hash值用来计算数组下标,如果两个元素的key的hash值一样,就会发生哈希冲突,被放到同一个链表上。
table数组记录HashMap的数据,每个下标对应一条链表。所有哈希冲突的数据都会被存放到同一条链表,Node/Entry节点包含四个成员变量:key、value、next指针和hash值。在JDK8之后链表超过8会转化成红黑树。
若当前数据/总数据容量>负载因子,HashMap将执行扩容操作。默认初始化容量为16,扩容容量必须是2的幂次方、最大容量为1 << 30 默认加载因子为 0.75。
3,为何HashMap线程不安全
在JDK1.7中,HashMap采用头插法插入元素,因此并发情况下会导致环形链表,产生死循环。
虽然JDK1.8采用了尾插法解决了这个问题,但是并发下的put操作也会使前一个key被后一个key覆盖。
由于HashMap有扩容机制存在,也存在A线程进行扩容后,B线程执行get方法出现失误的情况。
4,简述Java的TreeMap
TreeMap是底层利用红黑树实现的Map结构,底层实现是一颗平衡的排序二叉树,由于红黑树的插入、删除、遍历时间复杂度都为O(logN),所以性能上低于哈希表。但是哈希表无法提供键值对的有序输出,红黑树可以按照键的值的大小有序输出。
ArrayList,Vector和LinkedList有什么共同点和区别?
- ArrayList、Vector和LinkedList都是可伸缩的数组,即可以动态改变长度的数组。
- ArrayList、Vector都是基于存储元素的Object[] array来实现的,他们会在内存中开辟一块连续的空间来存储,支持下标,索引访问。但在涉及插入元素时可能需要移动容器中的元素,插入效率较低。当存储元素超过容器的初始化容量大小,ArrayList与Vector均会进行扩容。
- Vector是线程安全的,其大部分方法是直接或间接同步的。ArrayList不是线程安全的,其方法不具有同步性质。LinkedList也不是线程安全的。
- LinkedList采用双向列表实现,对数据索引需要从头开始遍历,因此随机访问效率较低,但在插入元素的时候不需要对数据进行移动,插入效率较高。
HashMap和Hashtable有什么区别?
- HashMap是Hashtable的轻量级实现,HashMap允许key和value为null,但最多允许一条记录的key为null,HashTable不允许。
- HashTable中的方法是线程安全的,而HashMap不是。在多线程访问HashMap需要提供额外的同步机制。
- Hashtable使用Enumeration进行遍历,HashMap使用Interator进行遍历。