一、集合框架概述
Java集合框架主要分为三大类接口:
-
List(列表):有序集合,元素可重复
- 实现类:ArrayList、LinkedList、Vector
-
Set(集):无序集合,元素不可重复
- 实现类:HashSet、LinkedHashSet、TreeSet
-
Map(映射):键值对集合
- 实现类:HashMap、LinkedHashMap、TreeMap、Hashtable
二、List接口及实现类
1. ArrayList
-
底层结构:动态数组
-
特点:
-
随机访问快(O(1))
-
插入删除慢(需要移动元素,O(n))
-
线程不安全
-
-
扩容机制:
-
默认初始容量:10
-
扩容公式:newCapacity = oldCapacity + (oldCapacity >> 1)(即1.5倍)
-
扩容时调用Arrays.copyOf()复制数组
-
2. LinkedList
-
底层结构:双向链表
-
特点:
-
插入删除快(O(1))
-
随机访问慢(需要遍历,O(n))
-
实现了Deque接口,可用作队列或栈
-
3. Vector
-
底层结构:动态数组(类似ArrayList)
-
特点:
-
线程安全(方法使用synchronized修饰)
-
性能较差
-
扩容默认增长为原来的2倍
-
三、Set接口及实现类
1. HashSet
-
底层结构:基于HashMap实现
-
特点:
-
元素无序
-
允许null值
-
线程不安全
-
-
实现原理:
// 实际使用HashMap存储 private transient HashMap<E,Object> map; // 值使用固定Object对象 private static final Object PRESENT = new Object(); public boolean add(E e) { return map.put(e, PRESENT)==null; }
2. LinkedHashSet
-
底层结构:继承HashSet,基于LinkedHashMap实现
-
特点:
-
维护插入顺序
-
性能略低于HashSet
-
3. TreeSet
-
底层结构:基于TreeMap实现(红黑树)
-
特点:
-
元素有序(自然顺序或Comparator)
-
不允许null值
-
基本操作时间复杂度O(log n)
-
四、Map接口及实现类
1. HashMap(重点详解)
底层结构(JDK8及以后)
-
数组+链表+红黑树的混合结构
-
默认初始容量:16
-
负载因子(loadFactor):0.75(默认)
-
链表转红黑树阈值:8
-
红黑树转链表阈值:6
-
核心字段
transient Node<K,V>[] table; // 哈希桶数组
transient int size; // 键值对数量
int threshold; // 扩容阈值(capacity * loadFactor)
final float loadFactor; // 负载因子
存储结构图示
数组索引:
0 -> null
1 -> Node<K,V> -> Node<K,V> -> null
2 -> TreeNode<K,V> -> TreeNode<K,V> -> null
...
15 -> null
扩容机制
-
触发条件:
-
当前size >= threshold
-
链表长度 >= TREEIFY_THRESHOLD(8)但数组长度 < MIN_TREEIFY_CAPACITY(64)
-
-
扩容过程:
-
创建新数组(大小为原数组2倍)
-
重新计算所有元素的哈希位置(非常耗时的操作)
-
JDK8优化:元素在新数组中的位置要么不变,要么是原位置+原数组长度
-
-
源码分析(简化版):
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;// 1. 计算新容量和阈值 if (oldCap > 0) { if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1; // 双倍 } // ... 其他初始化情况处理 // 2. 创建新数组 Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; table = newTab; // 3. 迁移数据 if (oldTab != null) { for (int j = 0; j < oldCap; ++j) { Node<K,V> e; if ((e = oldTab[j]) != null) { oldTab[j] = null; if (e.next == null) newTab[e.hash & (newCap - 1)] = e; else if (e instanceof TreeNode) ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); else { // 链表优化重hash Node<K,V> loHead = null, loTail = null; Node<K,V> hiHead = null, hiTail = null; Node<K,V> next; do { next = e.next; if ((e.hash & oldCap) == 0) { if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } else { if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null); if (loTail != null) { loTail.next = null; newTab[j] = loHead; } if (hiTail != null) { hiTail.next = null; newTab[j + oldCap] = hiHead; } } } } } return newTab;
}
哈希冲突解决
-
链表法:哈希冲突时,在数组对应位置形成链表
-
红黑树优化:当链表长度>=8且数组长度>=64时,链表转为红黑树
重要特性
-
允许null键和null值
-
线程不安全
-
迭代时使用fail-fast机制
2. LinkedHashMap
-
底层结构:继承HashMap,增加双向链表维护插入顺序
-
特点:
-
可预测的迭代顺序
-
性能略低于HashMap
-
3. TreeMap
-
底层结构:红黑树
-
特点:
-
按键的自然顺序或Comparator排序
-
基本操作时间复杂度O(log n)
-
4. Hashtable
-
底层结构:类似JDK7的HashMap(数组+链表)
-
特点:
-
线程安全(方法使用synchronized修饰)
-
不允许null键和null值
-
初始容量11,扩容为2n+1
-
五、HashMap常见问题
1. 为什么链表长度超过8转为红黑树?
根据泊松分布,哈希冲突达到8的概率极低(小于千万分之一),此时使用红黑树(查询时间O(log n))比链表(O(n))更高效。
2. 为什么初始容量是16?
-
2的幂次方有利于通过位运算计算索引:index = hash & (length-1)
-
16是经验值,在大多数情况下提供了良好的性能平衡
3. 为什么负载因子是0.75?
时间和空间的折中:
-
过高(如1.0):减少空间开销,但增加查询成本
-
过低(如0.5):减少哈希冲突,但增加扩容频率
4. 如何设计好的键对象?
-
重写hashCode()和equals()方法
-
保证不可变性(否则可能导致哈希值变化)
-
实现Comparable接口(如需排序)
六、总结对比
集合类 | 底层结构 | 线程安全 | 有序性 | 允许null |
---|---|---|---|---|
ArrayList | 动态数组 | 否 | 插入顺序 | 是 |
LinkedList | 双向链表 | 否 | 插入顺序 | 是 |
Vector | 动态数组 | 是 | 插入顺序 | 是 |
HashSet | HashMap | 否 | 无 | 是 |
LinkedHashSet | LinkedHashMap | 否 | 插入顺序 | 是 |
TreeSet | 红黑树 | 否 | 自然顺序 | 否 |
HashMap | 数组+链表+红黑树 | 否 | 无 | 是 |
LinkedHashMap | HashMap+双向链表 | 否 | 插入/访问顺序 | 是 |
TreeMap | 红黑树 | 否 | 自然顺序 | 否 |
Hashtable | 数组+链表 | 是 | 无 | 否 |
理解这些集合类的底层实现和特性,有助于在实际开发中选择最合适的数据结构,编写出更高效的Java代码。