Java集合框架是每个Java开发者必须掌握的核心知识体系,它提供了一套精心设计的接口和类,用于存储和操作数据集合。作为一名有十年Java开发经验的工程师,我经常在项目中发现开发者对集合框架的理解存在诸多误区。今天我就带大家深入剖析这个看似简单实则暗藏玄机的知识体系。
Java集合框架主要分为两大分支:Collection接口和Map接口。Collection用于处理对象集合,而Map则专门处理键值对映射。这种设计体现了"单一职责原则",让不同类型的集合各司其职。
重要提示:Java 8之后集合框架引入了许多重要改进,比如HashMap的红黑树优化、Stream API支持等,这些变化在实际开发中影响深远。
Collection接口是整个集合框架的根基,它定义了所有集合类共有的基本操作,如添加、删除、遍历等。从设计模式角度看,这是一个典型的"抽象工厂"模式应用。
java复制// Collection接口核心方法示例
public interface Collection<E> extends Iterable<E> {
int size();
boolean isEmpty();
boolean contains(Object o);
Iterator<E> iterator();
Object[] toArray();
<T> T[] toArray(T[] a);
boolean add(E e);
boolean remove(Object o);
// ... 其他方法
}
Collection的三个主要子接口各具特色:
Map接口独立于Collection体系,专门处理键值对映射。它的设计采用了"策略模式",不同的Map实现采用不同的哈希和排序策略。
java复制// Map接口核心方法示例
public interface Map<K,V> {
int size();
boolean isEmpty();
boolean containsKey(Object key);
boolean containsValue(Object value);
V get(Object key);
V put(K key, V value);
V remove(Object key);
// ... 其他方法
}
ArrayList是使用最频繁的List实现,其底层基于动态数组。我曾在处理百万级数据时深刻体会到它的特性:
java复制// ArrayList扩容关键代码
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); // 1.5倍扩容
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
elementData = Arrays.copyOf(elementData, newCapacity);
}
性能特点:
实战经验:初始化时预估容量能显著提升性能。比如已知要存储10000个元素,使用new ArrayList(10000)可避免多次扩容。
LinkedList采用双向链表实现,特别适合频繁插入删除的场景。它的节点结构如下:
java复制private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
性能对比:
| 操作 | ArrayList | LinkedList |
|---|---|---|
| get(0) | O(1) | O(1) |
| get(n/2) | O(1) | O(n) |
| add(0) | O(n) | O(1) |
| remove(n/2) | O(n) | O(n) |
有趣的是,中间位置的删除操作两者都是O(n),但LinkedList的常数因子更大,因为需要遍历链表定位。
虽然Vector已经过时,但在遗留系统中仍可能遇到。它与CopyOnWriteArrayList的线程安全实现截然不同:
java复制// CopyOnWriteArrayList的add实现
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
避坑指南:CopyOnWriteArrayList适合读多写少的并发场景,频繁写入会导致性能急剧下降。
HashSet实际上是HashMap的包装类,利用HashMap键的唯一性特性:
java复制// HashSet的核心实现
public class HashSet<E> implements Set<E> {
private transient HashMap<E,Object> map;
private static final Object PRESENT = new Object();
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
}
性能特点:
TreeSet基于TreeMap实现,支持两种排序方式:
java复制// TreeSet构造方法示例
public TreeSet(Comparator<? super E> comparator) {
this(new TreeMap<>(comparator));
}
红黑树特性:
LinkedHashSet在HashSet基础上维护了双向链表,因此具有可预测的迭代顺序:
java复制// LinkedHashMap.Entry扩展了Node
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
性能提示:LinkedHashSet比HashSet多消耗约8字节/元素(前后指针),但提供了稳定的遍历顺序。
ArrayDeque使用循环数组实现双端队列,是Stack和Queue的理想替代:
java复制// ArrayDeque核心字段
transient Object[] elements;
transient int head;
transient int tail;
扩容策略:
PriorityQueue基于二叉堆实现,是典型的优先队列:
java复制// 上浮操作
private void siftUp(int k, E x) {
if (comparator != null)
siftUpUsingComparator(k, x);
else
siftUpComparable(k, x);
}
堆排序特点:
ArrayBlockingQueue是典型的有界阻塞队列:
java复制// 出队操作
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
并发技巧:合理设置队列容量能平衡吞吐量和内存消耗,通常设置为处理能力的2-3倍。
Java 8对HashMap进行了重大优化:
Java 7实现:
Java 8改进:
java复制// TreeNode节点结构
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
TreeNode<K,V> parent; // 红黑树链接
TreeNode<K,V> left;
TreeNode<K,V> right;
TreeNode<K,V> prev; // 保持链表关系
boolean red;
}
JDK 1.7和1.8的实现有本质区别:
JDK 1.7:
JDK 1.8:
java复制// JDK 8的putVal关键代码
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)
tab = initTable();
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null)))
break; // CAS成功
}
// ... 其他情况处理
}
}
TreeMap是唯一基于红黑树的Map实现,其核心操作都遵循红黑树规则:
java复制// 红黑树修复
private void fixAfterInsertion(Entry<K,V> x) {
x.color = RED;
while (x != null && x != root && x.parent.color == RED) {
if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
Entry<K,V> y = rightOf(parentOf(parentOf(x)));
if (colorOf(y) == RED) {
setColor(parentOf(x), BLACK);
setColor(y, BLACK);
setColor(parentOf(parentOf(x)), RED);
x = parentOf(parentOf(x));
} else {
// ... 旋转操作
}
}
// ... 对称情况
}
root.color = BLACK;
}
合理设置初始容量能避免频繁扩容:
java复制// 计算HashMap最佳初始容量
public static int calculateInitialCapacity(int expectedSize) {
return (int) Math.ceil(expectedSize / 0.75f);
}
// 使用示例
Map<String, String> map = new HashMap<>(calculateInitialCapacity(100));
不同遍历方式的性能差异:
| 集合类型 | 最佳遍历方式 | 时间复杂度 |
|---|---|---|
| ArrayList | 索引for循环 | O(n) |
| LinkedList | 迭代器 | O(n) |
| HashSet | 迭代器 | O(n) |
| TreeSet | 迭代器 | O(n) |
| HashMap | entrySet迭代器 | O(n) |
| TreeMap | entrySet迭代器 | O(n) |
性能陷阱:LinkedList使用索引遍历会导致O(n²)时间复杂度!
根据并发需求选择合适的线程安全集合:
| 场景 | 推荐实现 | 特点 |
|---|---|---|
| 读多写少 | CopyOnWriteArrayList | 写时复制 |
| 高并发Map | ConcurrentHashMap | 分段锁/CAS |
| 阻塞队列 | LinkedBlockingQueue | 可选容量 |
| 优先级阻塞队列 | PriorityBlockingQueue | 自动扩容 |
| 延迟队列 | DelayQueue | 基于PriorityQueue |
这个异常通常发生在遍历时修改集合:
java复制// 错误示例
List<String> list = new ArrayList<>();
list.add("a");
for (String s : list) {
list.remove(s); // 抛出异常
}
// 正确做法
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String s = it.next();
if (s.equals("a")) {
it.remove(); // 安全删除
}
}
某些集合可能导致内存泄漏:
java复制// 典型内存泄漏场景
Map<Object, String> map = new HashMap<>();
Object key = new Object();
map.put(key, "value");
key = null; // key对象仍然被map引用
解决方案:
集合性能问题排查步骤:
我在实际项目中遇到过HashMap在多线程环境下导致的CPU 100%问题,最终通过替换为ConcurrentHashMap解决。这个案例让我深刻理解了不同集合实现的线程安全特性差异。