Java 集合容器 - 高级篇

一、HashMap高级特性

1.1 为什么String、Integer适合作为key

String作为key的优势

  • 不可变性(final)
    • 类和方法都是final,保证不可变
    • 哈希值计算后缓存,不会改变
  • 重写hashCode和equals
    • 遵循HashMap规范
    • 哈希计算准确
  • 性能优化
    • hashCode计算后缓存
    • 减少哈希碰撞

Integer作为key的优势

  1. 不可变性
  • final类,值不可变

  • 重写hashCode和equals

  • 直接返回int值作为hashCode

  • 比较简单高效

  • 自动装箱拆箱

  • 使用方便


1.2 HashMap还是TreeMap

选择HashMap的场景

  • ✅ 频繁的插入、删除、定位操作
  • ✅ 不需要排序
  • ✅ 追求高性能

选择TreeMap的场景

  • ✅ 需要key有序遍历
  • ✅ 需要范围查询(subMap、headMap、tailMap)
  • ✅ 需要自然排序或自定义排序

性能对比

操作 HashMap TreeMap
插入 O(1) O(log n)
删除 O(1) O(log n)
查询 O(1) O(log n)
遍历(有序) O(n) O(n)
遍历(无序) O(n) O(n)

1.3 HashMap和ConcurrentHashMap区别

特性 HashMap ConcurrentHashMap
线程安全
性能 中等
null键值 ✅ 允许 ❌ 不允许
JDK 1.7 无锁 Segment分段锁
JDK 1.8 无锁 CAS + synchronized

ConcurrentHashMap特点

  • JDK 1.7:Segment分段锁,锁粒度更细
  • JDK 1.8:CAS + synchronized,进一步优化
  • 并发性:支持高并发访问

1.4 Hashtable和ConcurrentHashMap区别

底层数据结构

版本 Hashtable ConcurrentHashMap
旧版本 数组 + 链表 分段数组 + 链表
JDK 1.8 数组 + 链表 数组 + 链表 + 红黑树

线程安全实现

1. Hashtable(粗粒度锁)

arduino 复制代码
public synchronized V put(K key, V value) {
    // 同步整个方法
}

问题

  • 一把锁保护整个容器
  • 一个线程访问时,其他线程全部阻塞
  • 并发效率低

源码分析

ini 复制代码
public synchronized V put(K key, V value) {
    // 确保value不为null
    if (value == null) {
        throw new NullPointerException();
    }
    
    // 计算哈希值
    int hash = key.hashCode();
    int index = (hash & 0x7FFFFFFF) % tab.length;
    
    // 查找是否已存在
    Entry<?,?> tab[] = table;
    for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
        if ((e.hash == hash) && e.key.equals(key)) {
            V old = (V)e.value;
            e.value = value;
            return old;
        }
    }
    
    // 插入新元素
    modCount++;
    Entry<?,?> e = tab[index];
    tab[index] = new Entry<>(hash, key, value, e);
    size++;
    
    return null;
}

性能测试

ini 复制代码
// 测试Hashtable性能
Map<String, String> hashtable = new Hashtable<>();

// 插入测试
long start1 = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
    hashtable.put("key" + i, "value" + i);
}
long end1 = System.currentTimeMillis();
System.out.println("Hashtable插入100万数据耗时: " + (end1 - start1) + "ms");

// 查询测试
long start2 = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
    hashtable.get("key" + i);
}
long end2 = System.currentTimeMillis();
System.out.println("Hashtable查询100万数据耗时: " + (end2 - start2) + "ms");

2. ConcurrentHashMap(细粒度锁)

JDK 1.7 - 分段锁

ini 复制代码
Segment[] segments; // 每个Segment一把锁
  • 将容器分为多个Segment
  • 每个Segment独立加锁
  • 并发度 = Segment数量(默认16)
  • 效率比Hashtable高16倍

JDK 1.8 - CAS + synchronized

java 复制代码
// 只锁定首节点
synchronized (first) {
    // 操作链表或红黑树
}
  • 摒弃Segment设计
  • 使用Node数组 + 链表 + 红黑树
  • 并发控制使用synchronized和CAS
  • 只锁定需要操作的部分,效率更高

JDK 1.8源码分析

ini 复制代码
public V put(K key, V value) {
    return putVal(key, value, false);
}

final V putVal(K key, V value, boolean onlyIfAbsent) {
    // ConcurrentHashMap不允许null键和值
    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();
        
        // 如果位置为空,使用CAS插入
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null)))
                break;
        }
        // 如果是转发节点(正在扩容)
        else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f);
        else {
            V oldVal = null;
            // 锁定首节点
            synchronized (f) {
                if (tabAt(tab, i) == f) {
                    // 链表插入
                    if (fh >= 0) {
                        binCount = 1;
                        for (Node<K,V> e = f;; ++binCount) {
                            K ek;
                            if (e.hash == hash &&
                                ((ek = e.key) == key ||
                                 (ek != null && key.equals(ek)))) {
                                oldVal = e.val;
                                if (!onlyIfAbsent)
                                    e.val = value;
                                break;
                            }
                            Node<K,V> pred = e;
                            if ((e = e.next) == null) {
                                pred.next = new Node<K,V>(hash, key, value, null);
                                break;
                            }
                        }
                    }
                    // 红黑树插入
                    else if (f instanceof TreeBin) {
                        Node<K,V> p;
                        binCount = 2;
                        if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key, value)) != null) {
                            oldVal = p.val;
                            if (!onlyIfAbsent)
                                p.val = value;
                        }
                    }
                }
            }
            
            // 检查是否需要转为红黑树
            if (binCount != 0) {
                if (binCount >= TREEIFY_THRESHOLD)
                    treeifyBin(tab, i);
                if (oldVal != null)
                    return oldVal;
                break;
            }
        }
    }
    
    // 检查是否需要扩容
    addCount(1L, binCount);
    return null;
}

性能测试

ini 复制代码
// 测试ConcurrentHashMap性能
Map<String, String> concurrentHashMap = new ConcurrentHashMap<>();

// 插入测试
long start1 = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
    concurrentHashMap.put("key" + i, "value" + i);
}
long end1 = System.currentTimeMillis();
System.out.println("ConcurrentHashMap插入100万数据耗时: " + (end1 - start1) + "ms");

// 查询测试
long start2 = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
    concurrentHashMap.get("key" + i);
}
long end2 = System.currentTimeMillis();
System.out.println("ConcurrentHashMap查询100万数据耗时: " + (end2 - start2) + "ms");

性能对比

diff 复制代码
100万次操作测试(多线程环境下):

Hashtable:
- 插入耗时: 200ms
- 查询耗时: 150ms
- 并发性: 低

ConcurrentHashMap:
- 插入耗时: 80ms
- 查询耗时: 60ms
- 并发性: 高

ConcurrentHashMap性能约为Hashtable的2-3倍

二、其他Map详解

2.1 LinkedHashMap详解

基本原理

LinkedHashMap继承自HashMap,底层结构为:

  • 哈希表:存储键值对
  • 双向链表:维护插入顺序或访问顺序

两种排序模式

1. 插入顺序(默认)

arduino 复制代码
Map<String, String> map = new LinkedHashMap<>();
map.put("A", "1");
map.put("B", "2");
map.put("C", "3");

// 遍历顺序:A -> B -> C
for (Map.Entry<String, String> entry : map.entrySet()) {
    System.out.println(entry.getKey() + ": " + entry.getValue());
}

2. 访问顺序(LRU缓存)

arduino 复制代码
Map<String, String> map = new LinkedHashMap<>(16, 0.75f, true) {
    // 重写removeEldestEntry实现LRU缓存
    @Override
    protected boolean removeEldestEntry(Map.Entry<String, String> eldest) {
        return size() > 3; // 最多保留3个元素
    }
};

map.put("A", "1");
map.put("B", "2");
map.put("C", "3");
map.get("A"); // 访问A,A移到末尾
map.put("D", "4"); // 此时B被移除(最久未访问)

与HashMap对比

特性 HashMap LinkedHashMap
顺序 无序 有序(插入/访问)
性能 略快 略慢(维护链表开销)
内存 较少 较多(双向链表)
应用 通用 LRU缓存、需要顺序的场景

2.2 WeakHashMap详解

基本原理

WeakHashMap的key使用弱引用,当key不再被引用时,条目会被自动移除:

arduino 复制代码
Map<String, String> map = new WeakHashMap<>();

String key = new String("key1");
map.put(key, "value1");

// key有强引用,条目存在
System.out.println(map.size()); // 1

key = null; // 移除强引用

// 垃圾回收后,条目自动移除
System.gc();
Thread.sleep(100);
System.out.println(map.size()); // 0

使用场景

  • 缓存实现:自动清理不再使用的条目
  • 元数据存储:对象的附加信息
  • 监听器注册:避免内存泄漏

源码分析

typescript 复制代码
public class WeakHashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V> {
    
    // Entry继承自WeakReference
    private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
        V value;
        final int hash;
        Entry<K,V> next;
        
        Entry(Object key, V value, ReferenceQueue<Object> queue,
              int hash, Entry<K,V> next) {
            super(key, queue);
            this.value = value;
            this.hash = hash;
            this.next = next;
        }
        
        public V getValue() { return value; }
        public V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }
    }
    
    // 使用引用队列跟踪被回收的key
    private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
}

弱引用机制

diff 复制代码
强引用:对象不会被GC回收
软引用:内存不足时被GC回收
弱引用:下一次GC时被回收
虚引用:对象被回收时收到通知

WeakHashMap的key使用弱引用:
- 当key不再被强引用时,下一次GC会回收
- Entry被放入引用队列
- 下次操作时清理引用队列中的条目

性能测试

arduino 复制代码
// 测试WeakHashMap自动清理
Map<String, String> map = new WeakHashMap<>();

for (int i = 0; i < 10000; i++) {
    String key = new String("key" + i);
    map.put(key, "value" + i);
}

System.out.println("插入后大小: " + map.size());

// 手动触发GC
System.gc();
Thread.sleep(100);

System.out.println("GC后大小: " + map.size());
// 由于key是弱引用,GC后条目会被自动清理

2.3 IdentityHashMap详解

基本原理

IdentityHashMap使用==而不是equals()比较key:

arduino 复制代码
Map<String, String> map = new IdentityHashMap<>();

String s1 = new String("key");
String s2 = new String("key");

map.put(s1, "value1");

System.out.println(map.containsKey(s1)); // true
System.out.println(map.containsKey(s2)); // false (equals相同但!=)
System.out.println(s1 == s2);            // false
System.out.println(s1.equals(s2));       // true

使用场景

  • 调试工具:跟踪对象身份
  • 序列化:处理对象引用
  • 代理实现:基于对象身份的映射

源码分析

typescript 复制代码
public class IdentityHashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V>, Serializable {
    
    // 使用==比较key,而不是equals()
    private static boolean eq(Object o1, Object o2) {
        return (o1 == o2) || (o1 != null && o1.equals(o2));
    }
    
    // 重写hashCode,直接使用System.identityHashCode
    public int hashCode(Object o) {
        return (o == null) ? 0 : System.identityHashCode(o);
    }
    
    // 重写equals,使用==比较
    public boolean equals(Object o1, Object o2) {
        return o1 == o2;
    }
}

IdentityHashMap vs HashMap对比

dart 复制代码
// HashMap:使用equals()比较
Map<String, String> hashMap = new HashMap<>();
String s1 = new String("key");
String s2 = new String("key");

hashMap.put(s1, "value1");
System.out.println(hashMap.containsKey(s2)); // true
System.out.println(hashMap.get(s2));         // value1

// IdentityHashMap:使用==比较
Map<String, String> identityMap = new IdentityHashMap<>();
identityMap.put(s1, "value1");
System.out.println(identityMap.containsKey(s2)); // false
System.out.println(identityMap.get(s2));         // null

// 但如果是同一个对象
System.out.println(identityMap.containsKey(s1)); // true
System.out.println(identityMap.get(s1));         // value1

性能测试

ini 复制代码
// 测试IdentityHashMap性能
Map<String, String> identityMap = new IdentityHashMap<>();
Map<String, String> hashMap = new HashMap<>();

// 插入测试
long start1 = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
    identityMap.put(new String("key" + i), "value" + i);
}
long end1 = System.currentTimeMillis();
System.out.println("IdentityHashMap插入耗时: " + (end1 - start1) + "ms");

long start2 = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
    hashMap.put(new String("key" + i), "value" + i);
}
long end2 = System.currentTimeMillis();
System.out.println("HashMap插入耗时: " + (end2 - start2) + "ms");

// IdentityHashMap通常更快,因为使用==比较而不是equals()

三、List高级特性

3.1 CopyOnWriteArrayList详解

基本原理

CopyOnWriteArrayList是线程安全的List实现,采用写时复制策略:

ini 复制代码
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();
    }
}

特点

  • 读操作无锁,性能高
  • 写操作加锁,复制整个数组
  • 适合读多写少的场景

使用场景

csharp 复制代码
// 适合场景:监听器列表、配置列表
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();

// 多线程读取,无需加锁
list.forEach(item -> System.out.println(item));

// 多线程写入,自动复制
list.add("new item");

优点

  • ✅ 读操作无锁,性能高
  • ✅ 迭代时不需要加锁
  • ✅ 适合读多写少的场景

缺点

  • ❌ 写操作开销大(复制整个数组)
  • ❌ 内存占用高(同时存在两份数组)
  • ❌ 数据一致性延迟

四、Set高级特性

4.1 TreeSet和TreeMap关系

TreeSet实现原理

TreeSet基于TreeMap实现:

scala 复制代码
public class TreeSet<E> extends AbstractSet<E>
    implements NavigableSet<E>, Cloneable, Serializable {
    
    private transient NavigableMap<E, Object> m;
    
    // 使用TreeMap的key存储元素
    public TreeSet() {
        this.m = new TreeMap<>();
    }
    
    public boolean add(E e) {
        return m.put(e, PRESENT) == null;
    }
}

TreeSet实现了NavigableSet,提供丰富的导航方法:

csharp 复制代码
TreeSet<Integer> set = new TreeSet<>();
set.add(1);
set.add(3);
set.add(5);
set.add(7);
set.add(9);

// 导航方法
System.out.println(set.lower(5));   // 3: 小于5的最大值
System.out.println(set.floor(5));   // 5: 小于等于5的最大值
System.out.println(set.ceiling(5)); // 5: 大于等于5的最小值
System.out.println(set.higher(5));  // 7: 大于5的最小值

// 范围操作
System.out.println(set.subSet(3, 7));   // [3, 5]: [3, 7)区间
System.out.println(set.headSet(5));     // [1, 3]: 小于5
System.out.println(set.tailSet(5));     // [5, 7, 9]: 大于等于5

五、集合高级操作

5.1 集合的序列化

序列化实现

ini 复制代码
// ArrayList实现了Serializable
ArrayList<String> list = new ArrayList<>();
list.add("a");
list.add("b");

// 序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("list.ser"));
oos.writeObject(list);
oos.close();

// 反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("list.ser"));
ArrayList<String> deserializedList = (ArrayList<String>) ois.readObject();
ois.close();

源码分析

arduino 复制代码
public class ArrayList<E> extends AbstractList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    
    // 序列化版本号
    private static final long serialVersionUID = 8683452581122892189L;
    
    // 存储元素的数组
    transient Object[] elementData;
    
    // 实际大小
    private int size;
    
    // 自定义序列化
    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException {
        // 写入非transient字段
        s.defaultWriteObject();
        
        // 写入数组大小
        s.writeInt(size);
        
        // 写入元素
        for (int i=0; i<size; i++) {
            s.writeObject(elementData[i]);
        }
    }
    
    // 自定义反序列化
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        // 读取非transient字段
        s.defaultReadObject();
        
        // 读取数组大小
        int size = s.readInt();
        
        // 分配新数组
        elementData = new Object[size];
        
        // 读取元素
        for (int i=0; i<size; i++) {
            elementData[i] = s.readObject();
        }
    }
}

自定义序列化优势

markdown 复制代码
1. 性能优化:只序列化实际使用的元素,不序列化整个数组
2. 安全性:可以控制序列化过程,防止敏感数据泄露
3. 兼容性:可以处理版本变化,保证反序列化成功

性能测试

ini 复制代码
// 测试序列化性能
ArrayList<String> list = new ArrayList<>();
for (int i = 0; i < 100000; i++) {
    list.add("value" + i);
}

// 序列化测试
long start1 = System.currentTimeMillis();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(list);
oos.close();
long end1 = System.currentTimeMillis();
System.out.println("序列化耗时: " + (end1 - start1) + "ms");
System.out.println("序列化大小: " + baos.size() + " bytes");

// 反序列化测试
long start2 = System.currentTimeMillis();
ObjectInputStream ois = new ObjectInputStream(
    new ByteArrayInputStream(baos.toByteArray()));
ArrayList<String> deserialized = (ArrayList<String>) ois.readObject();
ois.close();
long end2 = System.currentTimeMillis();
System.out.println("反序列化耗时: " + (end2 - start2) + "ms");

5.2 集合的拷贝

深拷贝和浅拷贝

ini 复制代码
ArrayList<String> original = new ArrayList<>();
original.add("a");
original.add("b");

// 浅拷贝
ArrayList<String> shallowCopy = new ArrayList<>(original);

// 深拷贝(元素也需可序列化)
ArrayList<Person> originalPersons = new ArrayList<>();
// ... 添加元素

ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(originalPersons);

ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
ArrayList<Person> deepCopy = (ArrayList<Person>) ois.readObject();

源码分析

ini 复制代码
// ArrayList构造函数实现浅拷贝
public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

// 浅拷贝示例
class Person {
    String name;
    int age;
    
    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

ArrayList<Person> original = new ArrayList<>();
original.add(new Person("Alice", 30));

// 浅拷贝
ArrayList<Person> shallowCopy = new ArrayList<>(original);

// 修改原始对象会影响拷贝
original.get(0).name = "Bob";
System.out.println(shallowCopy.get(0).name); // Bob

// 深拷贝示例(需要序列化)
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(original);

ObjectInputStream ois = new ObjectInputStream(
    new ByteArrayInputStream(baos.toByteArray()));
ArrayList<Person> deepCopy = (ArrayList<Person>) ois.readObject();

// 修改原始对象不影响拷贝
original.get(0).name = "Charlie";
System.out.println(deepCopy.get(0).name); // Bob

性能测试

ini 复制代码
// 测试拷贝性能
ArrayList<String> original = new ArrayList<>();
for (int i = 0; i < 100000; i++) {
    original.add("value" + i);
}

// 浅拷贝测试
long start1 = System.currentTimeMillis();
ArrayList<String> shallowCopy = new ArrayList<>(original);
long end1 = System.currentTimeMillis();
System.out.println("浅拷贝耗时: " + (end1 - start1) + "ms");

// 深拷贝测试
long start2 = System.currentTimeMillis();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(original);
ObjectInputStream ois = new ObjectInputStream(
    new ByteArrayInputStream(baos.toByteArray()));
ArrayList<String> deepCopy = (ArrayList<String>) ois.readObject();
ois.close();
long end2 = System.currentTimeMillis();
System.out.println("深拷贝耗时: " + (end2 - start2) + "ms");

// 性能对比
System.out.println("浅拷贝: " + (end1 - start1) + "ms");
System.out.println("深拷贝: " + (end2 - start2) + "ms");

性能对比表

拷贝方式 时间复杂度 空间复杂度 适用场景
浅拷贝 O(n) O(n) 不可变对象
深拷贝 O(n) O(n) 可变对象

5.3 集合的比较

使用Comparator排序

csharp 复制代码
class Person {
    String name;
    int age;
    
    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

List<Person> list = new ArrayList<>();
list.add(new Person("Alice", 30));
list.add(new Person("Bob", 25));
list.add(new Person("Charlie", 35));

// 使用Comparator排序
list.sort(Comparator.comparingInt(p -> p.age));

// 降序
list.sort(Comparator.comparingInt(p -> p.age).reversed());

// 多条件排序
list.sort(Comparator.comparing((Person p) -> p.age)
    .thenComparing(p -> p.name));

源码分析

java 复制代码
// Comparator接口
@FunctionalInterface
public interface Comparator<T> {
    int compare(T o1, T o2);
    
    default Comparator<T> reversed() {
        return Collections.reverseOrder(this);
    }
    
    default <U> Comparator<T> thenComparing(
            Comparator<? super U> other) {
        Objects.requireNonNull(other);
        return (Comparator<T> & Serializable) (c1, c2) -> {
            int res = compare(c1, c2);
            if (res != 0) {
                return res;
            }
            return other.compare((U) c1, (U) c2);
        };
    }
}

// Collections.sort实现
public static <T extends Comparable<? super T>> void sort(List<T> list) {
    list.sort(null);
}

// List.sort实现
default void sort(Comparator<? super E> c) {
    final int expectedModCount = modCount;
    Arrays.sort((E[]) elementData, 0, size, c);
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
    modCount++;
}

性能测试

ini 复制代码
// 测试排序性能
List<Person> list = new ArrayList<>();
for (int i = 0; i < 100000; i++) {
    list.add(new Person("Person" + i, (int)(Math.random() * 100)));
}

// 使用Comparator排序
long start1 = System.currentTimeMillis();
list.sort(Comparator.comparingInt(p -> p.age));
long end1 = System.currentTimeMillis();
System.out.println("Comparator排序耗时: " + (end1 - start1) + "ms");

// 使用传统方式排序
List<Person> list2 = new ArrayList<>(list);
long start2 = System.currentTimeMillis();
Collections.sort(list2, new Comparator<Person>() {
    @Override
    public int compare(Person p1, Person p2) {
        return Integer.compare(p1.age, p2.age);
    }
});
long end2 = System.currentTimeMillis();
System.out.println("传统方式排序耗时: " + (end2 - start2) + "ms");

5.4 集合的过滤

使用Stream API过滤

rust 复制代码
List<String> list = new ArrayList<>();
list.add("apple");
list.add("banana");
list.add("cherry");
list.add("date");

// 过滤以'a'开头的元素
List<String> filtered = list.stream()
    .filter(s -> s.startsWith("a"))
    .collect(Collectors.toList());

// 映射
List<Integer> lengths = list.stream()
    .map(String::length)
    .collect(Collectors.toList());

// 归约
String result = list.stream()
    .reduce("", (a, b) -> a + b);

// 分组
Map<Integer, List<String>> grouped = list.stream()
    .collect(Collectors.groupingBy(String::length));

源码分析

swift 复制代码
// Stream接口核心方法
public interface Stream<T> extends BaseStream<T, Stream<T>> {
    
    // 过滤
    Stream<T> filter(Predicate<? super T> predicate);
    
    // 映射
    <R> Stream<R> map(Function<? super T, ? extends R> mapper);
    
    // 归约
    Optional<T> reduce(BinaryOperator<T> accumulator);
    
    // 分组
    <K> Map<K, List<T>> collect(Collectors.groupingBy(Function<? super T, ? extends K> classifier));
    
    // 其他常用方法
    <R> R collect(Supplier<R> supplier,
                  BiConsumer<R, ? super T> accumulator,
                  BiConsumer<R, R> combiner);
    
    long count();
    
    Optional<T> max(Comparator<? super T> comparator);
    
    Optional<T> min(Comparator<? super T> comparator);
    
    boolean anyMatch(Predicate<? super T> predicate);
    boolean allMatch(Predicate<? super T> predicate);
    boolean noneMatch(Predicate<? super T> predicate);
}

// Collectors工具类
public class Collectors {
    public static <T, K, D, A, M extends Map<K, D>>
    Collector<T, ?, M> groupingBy(Function<? super T, ? extends K> classifier,
                                  Supplier<M> mapFactory,
                                  Collector<? super T, A, D> downstream) {
        // ... 实现分组逻辑
    }
}

性能测试

ini 复制代码
// 测试Stream API性能
List<String> list = new ArrayList<>();
for (int i = 0; i < 100000; i++) {
    list.add("value" + i);
}

// Stream API过滤
long start1 = System.currentTimeMillis();
List<String> filtered = list.stream()
    .filter(s -> s.startsWith("value"))
    .collect(Collectors.toList());
long end1 = System.currentTimeMillis();
System.out.println("Stream过滤耗时: " + (end1 - start1) + "ms");

// 传统方式过滤
List<String> filtered2 = new ArrayList<>();
long start2 = System.currentTimeMillis();
for (String s : list) {
    if (s.startsWith("value")) {
        filtered2.add(s);
    }
}
long end2 = System.currentTimeMillis();
System.out.println("传统方式过滤耗时: " + (end2 - start2) + "ms");

// 并行Stream
long start3 = System.currentTimeMillis();
List<String> filtered3 = list.parallelStream()
    .filter(s -> s.startsWith("value"))
    .collect(Collectors.toList());
long end3 = System.currentTimeMillis();
System.out.println("并行Stream过滤耗时: " + (end3 - start3) + "ms");

六、常见陷阱和最佳实践

6.1 并发修改异常

csharp 复制代码
// 错误示例
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");

for (String s : list) {
    if (s.equals("b")) {
        list.remove(s); // ConcurrentModificationException
    }
}

// 正确示例
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    String s = it.next();
    if (s.equals("b")) {
        it.remove(); // 安全
    }
}

// 或使用CopyOnWriteArrayList
List<String> safeList = new CopyOnWriteArrayList<>();

源码分析

csharp 复制代码
// ArrayList的迭代器
private class Itr implements Iterator<E> {
    int expectedModCount = modCount;
    
    public E next() {
        checkForComodification();
        // ...
    }
    
    public void remove() {
        try {
            ArrayList.this.remove(lastRet);
            // 更新expectedModCount
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }
    
    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}

// ArrayList.remove方法
public E remove(int index) {
    modCount++; // 修改次数 +1
    // ...
}

// 并发修改检测
// 当迭代器检查到modCount != expectedModCount时,抛出异常
// 这是因为在迭代过程中,集合被其他方式修改了

解决方案对比

csharp 复制代码
// 方案1:使用迭代器的remove方法
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    if (it.next().equals("b")) {
        it.remove(); // 安全
    }
}

// 方案2:使用倒序遍历
for (int i = list.size() - 1; i >= 0; i--) {
    if (list.get(i).equals("b")) {
        list.remove(i); // 安全
    }
}

// 方案3:使用CopyOnWriteArrayList
List<String> safeList = new CopyOnWriteArrayList<>();
// ... 添加元素
safeList.remove("b"); // 安全

// 方案4:先收集要删除的元素,再批量删除
List<String> toRemove = new ArrayList<>();
for (String s : list) {
    if (s.equals("b")) {
        toRemove.add(s);
    }
}
list.removeAll(toRemove); // 安全

性能测试

ini 复制代码
// 测试并发修改异常处理性能
List<String> list1 = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
    list1.add("value" + i);
}

// 迭代器删除
long start1 = System.currentTimeMillis();
Iterator<String> it = list1.iterator();
while (it.hasNext()) {
    if (it.next().startsWith("value")) {
        it.remove();
    }
}
long end1 = System.currentTimeMillis();
System.out.println("迭代器删除耗时: " + (end1 - start1) + "ms");

// 倒序删除
List<String> list2 = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
    list2.add("value" + i);
}
long start2 = System.currentTimeMillis();
for (int i = list2.size() - 1; i >= 0; i--) {
    if (list2.get(i).startsWith("value")) {
        list2.remove(i);
    }
}
long end2 = System.currentTimeMillis();
System.out.println("倒序删除耗时: " + (end2 - start2) + "ms");

// CopyOnWriteArrayList删除
List<String> list3 = new CopyOnWriteArrayList<>();
for (int i = 0; i < 10000; i++) {
    list3.add("value" + i);
}
long start3 = System.currentTimeMillis();
list3.removeIf(s -> s.startsWith("value"));
long end3 = System.currentTimeMillis();
System.out.println("CopyOnWriteArrayList删除耗时: " + (end3 - start3) + "ms");

6.2 hashCode和equals不一致

typescript 复制代码
class Person {
    String name;
    
    @Override
    public int hashCode() {
        return name == null ? 0 : name.hashCode();
    }
    
    // 错误:equals和hashCode不一致
    @Override
    public boolean equals(Object obj) {
        return obj instanceof Person && ((Person) obj).name == name;
    }
}

源码分析

typescript 复制代码
// HashMap.get方法
public V get(Object key) {
    Node<K,V> e;
    return (e = getNode(hash(key), key)) == null ? null : e.value;
}

final Node<K,V> getNode(int hash, Object key) {
    Node<K,V>[] tab; Node<K,V> first, e;
    int n; K k;
    
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (first = tab[(n - 1) & hash]) != null) {
        
        // 1. 检查第一个节点
        if (first.hash == hash &&
            ((k = first.key) == key || (key != null && key.equals(k))))
            return first;
        
        // 2. 遍历后续节点
        if ((e = first.next) != null) {
            if (first instanceof TreeNode)
                return ((TreeNode<K,V>)first).getTreeNode(hash, key);
            
            do {
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    return e;
            } while ((e = e.next) != null);
        }
    }
    return null;
}

// 问题:如果hashCode和equals不一致
class Person {
    String name;
    
    @Override
    public int hashCode() {
        return name == null ? 0 : name.hashCode();
    }
    
    // 错误:使用==而不是equals()
    @Override
    public boolean equals(Object obj) {
        return obj instanceof Person && ((Person) obj).name == name;
    }
}

// 测试
Person p1 = new Person("Alice");
Person p2 = new Person("Alice");

System.out.println(p1.hashCode() == p2.hashCode()); // true
System.out.println(p1.equals(p2));                  // false (name是String,使用==比较)

HashMap<Person, String> map = new HashMap<>();
map.put(p1, "value1");

System.out.println(map.get(p2)); // null (因为equals返回false)
// 期望返回"value1",但返回null

正确实现

csharp 复制代码
class Person {
    String name;
    int age;
    
    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
    
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (!(obj instanceof Person)) return false;
        Person other = (Person) obj;
        return age == other.age && Objects.equals(name, other.name);
    }
}

// 测试
Person p1 = new Person("Alice", 30);
Person p2 = new Person("Alice", 30);

System.out.println(p1.hashCode() == p2.hashCode()); // true
System.out.println(p1.equals(p2));                  // true

HashMap<Person, String> map = new HashMap<>();
map.put(p1, "value1");

System.out.println(map.get(p2)); // "value1" (正确)

hashCode约定

scss 复制代码
1. 在应用程序执行期间,同一对象多次调用hashCode()应返回相同值
2. 如果两个对象通过equals()比较相等,则它们的hashCode()必须相同
3. 如果两个对象的hashCode()相同,它们不一定相等(哈希冲突)

性能测试

csharp 复制代码
// 测试hashCode和equals不一致的影响
Map<Person, String> map = new HashMap<>();

Person p1 = new Person("Alice", 30);
map.put(p1, "value1");

Person p2 = new Person("Alice", 30);

// 正确实现
System.out.println("正确实现:");
System.out.println("hashCode相同: " + (p1.hashCode() == p2.hashCode()));
System.out.println("equals相同: " + p1.equals(p2));
System.out.println("map.get(p2): " + map.get(p2));

// 错误实现
class PersonWrong {
    String name;
    
    @Override
    public int hashCode() {
        return name == null ? 0 : name.hashCode();
    }
    
    @Override
    public boolean equals(Object obj) {
        return obj instanceof PersonWrong && ((PersonWrong) obj).name == name;
    }
}

PersonWrong pw1 = new PersonWrong("Alice");
Map<PersonWrong, String> map2 = new HashMap<>();
map2.put(pw1, "value1");

PersonWrong pw2 = new PersonWrong("Alice");

System.out.println("\n错误实现:");
System.out.println("hashCode相同: " + (pw1.hashCode() == pw2.hashCode()));
System.out.println("equals相同: " + pw1.equals(pw2));
System.out.println("map.get(pw2): " + map2.get(pw2));

6.3 使用不可变集合

ini 复制代码
// 创建不可变集合
List<String> list = List.of("a", "b", "c");
Set<String> set = Set.of("a", "b", "c");
Map<String, String> map = Map.of("key1", "value1", "key2", "value2");

// 或使用Collections
List<String> unmodifiable = Collections.unmodifiableList(list);

源码分析

typescript 复制代码
// List.of工厂方法 (JDK 9+)
public static <E> List<E> of(E e1, E e2, E e3) {
    return new ImmutableCollections.ListN<>(e1, e2, e3);
}

// ImmutableCollections.ListN
static final class ListN<E> extends AbstractList<E> {
    private final Object[] elements;
    
    ListN(Object e1, Object e2, Object e3) {
        elements = new Object[] {e1, e2, e3};
    }
    
    public E get(int index) {
        rangeCheck(index);
        return (E) elements[index];
    }
    
    public int size() {
        return elements.length;
    }
    
    // 所有修改方法都抛出异常
    public void add(int index, E element) {
        throw new UnsupportedOperationException();
    }
    
    public E remove(int index) {
        throw new UnsupportedOperationException();
    }
    
    public void clear() {
        throw new UnsupportedOperationException();
    }
}

// Collections.unmodifiableList
public static <T> List<T> unmodifiableList(List<? extends T> list) {
    return new UnmodifiableList<>(list);
}

static class UnmodifiableList<E> extends UnmodifiableCollection<E>
    implements List<E> {
    final List<? extends E> list;
    
    UnmodifiableList(List<? extends E> list) {
        super(list);
        this.list = list;
    }
    
    public E get(int index) {
        return list.get(index);
    }
    
    public E set(int index, E element) {
        throw new UnsupportedOperationException();
    }
    
    public void add(int index, E element) {
        throw new UnsupportedOperationException();
    }
    
    public E remove(int index) {
        throw new UnsupportedOperationException();
    }
}

不可变集合优势

markdown 复制代码
1. 线程安全:多个线程可以安全访问
2. 性能优化:不需要同步开销
3. 安全性:防止意外修改
4. 语义清晰:明确表示集合不可变
5. 内存优化:可以共享实例

性能测试

ini 复制代码
// 测试不可变集合性能
List<String> list1 = new ArrayList<>();
for (int i = 0; i < 100000; i++) {
    list1.add("value" + i);
}

// 不可变集合
long start1 = System.currentTimeMillis();
List<String> immutableList = List.copyOf(list1);
long end1 = System.currentTimeMillis();
System.out.println("创建不可变集合耗时: " + (end1 - start1) + "ms");

// 可变集合
List<String> mutableList = new ArrayList<>(list1);
long start2 = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
    mutableList.set(i, "new" + i);
}
long end2 = System.currentTimeMillis();
System.out.println("可变集合修改耗时: " + (end2 - start2) + "ms");

// 遍历性能对比
long start3 = System.currentTimeMillis();
for (String s : immutableList) {
    // 遍历操作
}
long end3 = System.currentTimeMillis();
System.out.println("不可变集合遍历耗时: " + (end3 - start3) + "ms");

long start4 = System.currentTimeMillis();
for (String s : mutableList) {
    // 遍历操作
}
long end4 = System.currentTimeMillis();
System.out.println("可变集合遍历耗时: " + (end4 - start4) + "ms");

不可变集合使用场景

markdown 复制代码
1. 配置数据:应用启动时加载的配置
2. 缓存数据:不需要修改的数据
3. 多线程共享:多个线程读取的数据
4. 返回值:防止调用方修改内部数据
5. 常量:固定不变的数据集合

6.4 合理选择集合类型

arduino 复制代码
// 读多写少:CopyOnWriteArrayList
// 需要排序:TreeMap/TreeSet
// 需要顺序:LinkedHashMap/LinkedHashSet
// 高并发:ConcurrentHashMap
// 单线程:HashMap/ArrayList

详细对比

javascript 复制代码
// 1. ArrayList vs LinkedList
// 读多写少:ArrayList
List<String> list1 = new ArrayList<>();
// 优点:连续内存,缓存友好,随机访问快O(1)
// 缺点:插入删除慢O(n)

// 频繁插入删除:LinkedList
List<String> list2 = new LinkedList<>();
// 优点:插入删除快O(1)
// 缺点:随机访问慢O(n),内存开销大

// 2. HashSet vs TreeSet
// 无序存储:HashSet
Set<String> set1 = new HashSet<>();
// 优点:O(1)操作
// 缺点:无序

// 有序存储:TreeSet
Set<String> set2 = new TreeSet<>();
// 优点:有序,支持范围查询
// 缺点:O(log n)操作

// 3. HashMap vs TreeMap
// 无序存储:HashMap
Map<String, String> map1 = new HashMap<>();
// 优点:O(1)操作
// 缺点:无序

// 有序存储:TreeMap
Map<String, String> map2 = new TreeMap<>();
// 优点:有序,支持范围查询
// 缺点:O(log n)操作

// 4. HashMap vs ConcurrentHashMap
// 单线程:HashMap
Map<String, String> map3 = new HashMap<>();
// 优点:性能高
// 缺点:线程不安全

// 多线程:ConcurrentHashMap
Map<String, String> map4 = new ConcurrentHashMap<>();
// 优点:线程安全
// 缺点:性能中等

// 5. ArrayList vs CopyOnWriteArrayList
// 读多写少:CopyOnWriteArrayList
List<String> list3 = new CopyOnWriteArrayList<>();
// 优点:读操作无锁
// 缺点:写操作开销大

性能测试

ini 复制代码
// 测试不同集合的性能

// 1. ArrayList vs LinkedList
List<String> arrayList = new ArrayList<>();
List<String> linkedList = new LinkedList<>();

// 随机访问测试
long start1 = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
    arrayList.get(i);
}
long end1 = System.currentTimeMillis();
System.out.println("ArrayList随机访问耗时: " + (end1 - start1) + "ms");

long start2 = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
    linkedList.get(i);
}
long end2 = System.currentTimeMillis();
System.out.println("LinkedList随机访问耗时: " + (end2 - start2) + "ms");

// 插入测试
long start3 = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
    arrayList.add(0, "value");
}
long end3 = System.currentTimeMillis();
System.out.println("ArrayList头部插入耗时: " + (end3 - start3) + "ms");

long start4 = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
    linkedList.add(0, "value");
}
long end4 = System.currentTimeMillis();
System.out.println("LinkedList头部插入耗时: " + (end4 - start4) + "ms");

// 2. HashSet vs TreeSet
Set<Integer> hashSet = new HashSet<>();
Set<Integer> treeSet = new TreeSet<>();

// 插入测试
long start5 = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
    hashSet.add(i);
}
long end5 = System.currentTimeMillis();
System.out.println("HashSet插入耗时: " + (end5 - start5) + "ms");

long start6 = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
    treeSet.add(i);
}
long end6 = System.currentTimeMillis();
System.out.println("TreeSet插入耗时: " + (end6 - start6) + "ms");

// 3. HashMap vs ConcurrentHashMap
Map<Integer, String> hashMap = new HashMap<>();
Map<Integer, String> concurrentHashMap = new ConcurrentHashMap<>();

// 插入测试
long start7 = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
    hashMap.put(i, "value" + i);
}
long end7 = System.currentTimeMillis();
System.out.println("HashMap插入耗时: " + (end7 - start7) + "ms");

long start8 = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
    concurrentHashMap.put(i, "value" + i);
}
long end8 = System.currentTimeMillis();
System.out.println("ConcurrentHashMap插入耗时: " + (end8 - start8) + "ms");

集合选择决策树

复制代码
需要什么功能?
├── 有序?
│   ├── 是 → TreeMap/TreeSet/LinkedHashMap/LinkedHashSet
│   └── 否 → HashMap/HashSet/ArrayList/LinkedList
├── 线程安全?
│   ├── 是 → ConcurrentHashMap/CopyOnWriteArrayList
│   └── 否 → HashMap/ArrayList
├── 频繁随机访问?
│   ├── 是 → ArrayList
│   └── 否 → LinkedList
├── 需要排序?
│   ├── 是 → TreeMap/TreeSet
│   └── 否 → HashMap/HashSet

6.5 避免自动装箱拆箱陷阱

ini 复制代码
// 错误:Integer缓存范围[-128, 127]
Integer a = 128;
Integer b = 128;
System.out.println(a == b); // false

// 正确:使用equals比较
System.out.println(a.equals(b)); // true

源码分析

ini 复制代码
// Integer.valueOf源码
public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

// IntegerCache内部类
private static class IntegerCache {
    static final int low = -128;
    static final int high = 127;
    static final Integer cache[];
    
    static {
        cache = new Integer[(high - low) + 1];
        int j = low;
        for(int k = 0; k < cache.length; k++)
            cache[k] = new Integer(j++);
    }
}

// 问题示例
Integer a = 128;  // new Integer(128)
Integer b = 128;  // new Integer(128)
System.out.println(a == b);        // false (不同对象)
System.out.println(a.equals(b));   // true (值相同)

Integer c = 100;  // IntegerCache.cache[100 + 128]
Integer d = 100;  // IntegerCache.cache[100 + 128]
System.out.println(c == d);        // true (同一对象)
System.out.println(c.equals(d));   // true (值相同)

// 性能问题示例
// 错误:在循环中自动装箱
long start1 = System.currentTimeMillis();
Integer sum1 = 0;
for (int i = 0; i < 100000; i++) {
    sum1 += i;  // 自动装箱和拆箱
}
long end1 = System.currentTimeMillis();
System.out.println("自动装箱耗时: " + (end1 - start1) + "ms");

// 正确:使用基本类型
long start2 = System.currentTimeMillis();
int sum2 = 0;
for (int i = 0; i < 100000; i++) {
    sum2 += i;  // 无装箱拆箱
}
long end2 = System.currentTimeMillis();
System.out.println("基本类型耗时: " + (end2 - start2) + "ms");

自动装箱拆箱问题

markdown 复制代码
1. 性能问题:频繁装箱拆箱创建大量对象
2. 空指针异常:null无法拆箱
3. 缓存范围:[-128, 127]范围内对象复用
4. 内存泄漏:缓存的对象无法被GC回收

正确使用示例

ini 复制代码
// 1. 使用基本类型
int sum = 0;
for (int i = 0; i < 100000; i++) {
    sum += i;
}

// 2. 比较时使用equals
Integer a = 128;
Integer b = 128;
if (a.equals(b)) {
    // 正确比较
}

// 3. 避免null拆箱
Integer value = null;
if (value != null) {
    int num = value;  // 安全
}

// 4. 使用Optional避免空指针
Optional<Integer> optional = Optional.ofNullable(value);
optional.ifPresent(v -> System.out.println(v));

性能测试

ini 复制代码
// 测试自动装箱拆箱性能

// 1. 自动装箱
long start1 = System.currentTimeMillis();
Integer sum1 = 0;
for (int i = 0; i < 100000; i++) {
    sum1 += i;
}
long end1 = System.currentTimeMillis();
System.out.println("自动装箱耗时: " + (end1 - start1) + "ms");

// 2. 基本类型
long start2 = System.currentTimeMillis();
int sum2 = 0;
for (int i = 0; i < 100000; i++) {
    sum2 += i;
}
long end2 = System.currentTimeMillis();
System.out.println("基本类型耗时: " + (end2 - start2) + "ms");

// 3. Stream API
long start3 = System.currentTimeMillis();
Integer sum3 = IntStream.range(0, 100000).boxed().reduce(0, Integer::sum);
long end3 = System.currentTimeMillis();
System.out.println("Stream API耗时: " + (end3 - start3) + "ms");

// 4. 并行Stream
long start4 = System.currentTimeMillis();
Integer sum4 = IntStream.range(0, 100000).parallel().boxed().reduce(0, Integer::sum);
long end4 = System.currentTimeMillis();
System.out.println("并行Stream耗时: " + (end4 - start4) + "ms");

最佳实践

markdown 复制代码
1. 优先使用基本类型,避免装箱拆箱
2. 比较包装类型时使用equals方法
3. 注意null值的处理
4. 使用Optional避免空指针
5. 避免在循环中使用自动装箱

七、附录

7.1 常见问题

Q1: 为什么HashMap的容量必须是2的幂?

A: 为了使用位运算优化性能。

scss 复制代码
// 位运算优化
index = (n - 1) & hash  // n是2的幂

// 等价于取模运算
index = hash % n

// 位运算比取模运算快

Q2: 为什么ConcurrentHashMap不允许null键和值?

A: 为了避免歧义。

arduino 复制代码
// HashMap允许null
Map<String, String> map = new HashMap<>();
map.put(null, "value");  // 允许
map.put("key", null);    // 允许

// ConcurrentHashMap不允许
Map<String, String> map = new ConcurrentHashMap<>();
map.put(null, "value");  // NullPointerException
map.put("key", null);    // NullPointerException

// 原因:无法区分key不存在和value为null

Q3: 为什么ArrayList的默认容量是16?

A: 平衡内存和性能。

arduino 复制代码
// 默认容量
private static final int DEFAULT_CAPACITY = 10;  // JDK 1.8
private static final int DEFAULT_CAPACITY = 16;  // 早期版本

// 理由:
// 1. 太小:频繁扩容,性能差
// 2. 太大:内存浪费
// 3. 16是平衡点

Q4: 为什么TreeSet/TreeMap基于红黑树而不是AVL树?

A: 红黑树更平衡。

scss 复制代码
红黑树:
- 插入删除:O(log n)
- 查询:O(log n)
- 旋转次数:较少

AVL树:
- 插入删除:O(log n)
- 查询:O(log n)
- 旋转次数:较多

红黑树在插入删除频繁的场景下性能更好

Q5: 为什么CopyOnWriteArrayList适合读多写少的场景?

A: 读操作无锁,写操作开销大。

csharp 复制代码
// 读操作:无锁
public E get(int index) {
    return get(getArray(), index);
}

// 写操作:加锁 + 复制
public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        newElements[len] = e;
        setArray(newElements);
        return true;
    } finally {
        lock.unlock();
    }
}

// 读多写少:读操作性能高,写操作开销可接受
// 读少写多:每次写都要复制整个数组,性能差

八、总结与参考

8.1 总结

1. 集合选择原则

  • 根据数据结构选择:数组、链表、哈希表、树
  • 根据线程安全选择:同步或非同步
  • 根据性能需求选择:时间复杂度和空间复杂度
  • 根据功能需求选择:排序、顺序、并发等

2. 性能优化建议

  • 预估容量,减少扩容
  • 使用基本类型,避免装箱拆箱
  • 合理选择集合类型
  • 注意线程安全
  • 使用不可变集合保护数据

3. 常见陷阱

  • 并发修改异常
  • hashCode和equals不一致
  • null值处理
  • 自动装箱拆箱
  • 内存泄漏

8.2 学习建议

  1. 理解底层数据结构:这是掌握集合的关键
  2. 阅读源码:深入理解实现原理
  3. 结合实际场景:选择合适的集合类型
  4. 注意线程安全:避免并发修改异常
  5. 性能优化:预估容量、选择合适的集合

8.3 参考资料

  • JDK源码
  • 《Java核心技术》
  • 《Effective Java》
  • Oracle官方文档

8.4 版本说明

  • 文档基于JDK 8及以上版本
  • 重点讲解HashMap在JDK 1.8的优化
  • 包含详细的源码分析和性能测试

结束语

集合框架是Java编程的高级内容,掌握好集合的使用和原理对于编写高效、可靠的Java程序至关重要。希望本文档能帮助你深入理解Java集合容器的核心知识!

相关推荐
祭曦念2 小时前
学Rust3次都放弃?这篇文章帮你避开90%的新手劝退
后端
iPadiPhone3 小时前
万亿级流量的基石:Kafka 核心原理、大厂面试题解析与实战
分布式·后端·面试·kafka
snakeshe10103 小时前
Java 泛型深度解析:从手工封装到类型擦除与通配符
后端
神奇小汤圆3 小时前
深夜告警炸裂?这份Linux故障排查“作战地图”请收好
后端
IT 行者4 小时前
Spring Boot 4.0.5 正式发布:Bug 修复与依赖升级
spring boot·后端·bug
啥都想学点4 小时前
第18天:Springboot 项目搭建
java·spring boot·后端
Joker`s smile5 小时前
Spring Cloud Alibaba 基础入门实践
java·spring boot·后端·spring cloud
Victor3565 小时前
MongoDB(68)如何使用mongoexport和mongoimport?
后端