Java 容器.md 38.3 KB
Newer Older
C
CyC2018 已提交
1
# 一、概览
C
CyC2018 已提交
2

C
CyC2018 已提交
3
容器主要包括 Collection 和 Map 两种,Collection 存储着对象的集合,而 Map 存储着键值对(两个对象)的映射表。
C
CyC2018 已提交
4

C
CyC2018 已提交
5
## Collection
C
CyC2018 已提交
6

C
CyC2018 已提交
7
![](index_files/VP6n3i8W48Ptde8NQ9_0eSR5eOD6uqx.png)
C
CyC2018 已提交
8

C
CyC2018 已提交
9
### 1. Set
C
CyC2018 已提交
10

C
CyC2018 已提交
11
- TreeSet:基于红黑树实现,支持有序性操作,例如根据一个范围查找元素的操作。但是查找效率不如 HashSet,HashSet 查找的时间复杂度为 O(1),TreeSet 则为 O(logN)。
C
CyC2018 已提交
12

C
CyC2018 已提交
13
- HashSet:基于哈希表实现,支持快速查找,但不支持有序性操作。并且失去了元素的插入顺序信息,也就是说使用 Iterator 遍历 HashSet 得到的结果是不确定的。
C
CyC2018 已提交
14

C
CyC2018 已提交
15
- LinkedHashSet:具有 HashSet 的查找效率,且内部使用双向链表维护元素的插入顺序。
C
CyC2018 已提交
16

C
CyC2018 已提交
17
### 2. List
C
CyC2018 已提交
18

C
CyC2018 已提交
19
- ArrayList:基于动态数组实现,支持随机访问。
C
CyC2018 已提交
20

C
CyC2018 已提交
21
- Vector:和 ArrayList 类似,但它是线程安全的。
C
CyC2018 已提交
22

C
CyC2018 已提交
23
- LinkedList:基于双向链表实现,只能顺序访问,但是可以快速地在链表中间插入和删除元素。不仅如此,LinkedList 还可以用作栈、队列和双向队列。
C
CyC2018 已提交
24

C
CyC2018 已提交
25
### 3. Queue
C
CyC2018 已提交
26

C
CyC2018 已提交
27
- LinkedList:可以用它来实现双向队列。
C
CyC2018 已提交
28

C
CyC2018 已提交
29
- PriorityQueue:基于堆结构实现,可以用它来实现优先队列。
C
CyC2018 已提交
30

C
CyC2018 已提交
31
## Map
C
CyC2018 已提交
32

C
CyC2018 已提交
33
![](index_files/SoWkIImgAStDuUBAp2j9BKfBJ4vLy4q.png)
C
CyC2018 已提交
34

C
CyC2018 已提交
35
- TreeMap:基于红黑树实现。
C
CyC2018 已提交
36

C
CyC2018 已提交
37
- HashMap:基于哈希表实现。
C
CyC2018 已提交
38

C
CyC2018 已提交
39
- HashTable:和 HashMap 类似,但它是线程安全的,这意味着同一时刻多个线程可以同时写入 HashTable 并且不会导致数据不一致。它是遗留类,不应该去使用它。现在可以使用 ConcurrentHashMap 来支持线程安全,并且 ConcurrentHashMap 的效率会更高,因为 ConcurrentHashMap 引入了分段锁。
C
CyC2018 已提交
40

C
CyC2018 已提交
41
- LinkedHashMap:使用双向链表来维护元素的顺序,顺序为插入顺序或者最近最少使用(LRU)顺序。
C
CyC2018 已提交
42 43


C
CyC2018 已提交
44
# 二、容器中的设计模式
C
CyC2018 已提交
45

C
CyC2018 已提交
46
## 迭代器模式
C
CyC2018 已提交
47

C
CyC2018 已提交
48
![](index_files/SoWkIImgAStDuUBAp2j9BKfBJ4vLy0G.png)
C
CyC2018 已提交
49

C
CyC2018 已提交
50
Collection 继承了 Iterable 接口,其中的 iterator() 方法能够产生一个 Iterator 对象,通过这个对象就可以迭代遍历 Collection 中的元素。
C
CyC2018 已提交
51

C
CyC2018 已提交
52
从 JDK 1.5 之后可以使用 foreach 方法来遍历实现了 Iterable 接口的聚合对象。
C
CyC2018 已提交
53 54

```java
C
CyC2018 已提交
55
List<String> list = new ArrayList<>();
C
CyC2018 已提交
56 57
list.add("a");
list.add("b");
C
CyC2018 已提交
58 59
for (String item : list) {
    System.out.println(item);
C
CyC2018 已提交
60 61 62
}
```

C
CyC2018 已提交
63
## 适配器模式
C
CyC2018 已提交
64

C
CyC2018 已提交
65
java.util.Arrays#asList() 可以把数组类型转换为 List 类型。
C
CyC2018 已提交
66 67 68

```java
@SafeVarargs
C
CyC2018 已提交
69
public static <T> List<T> asList(T... a)
C
CyC2018 已提交
70 71
```

C
CyC2018 已提交
72
应该注意的是 asList() 的参数为泛型的变长参数,不能使用基本类型数组作为参数,只能使用相应的包装类型数组。
C
CyC2018 已提交
73 74

```java
C
CyC2018 已提交
75 76
Integer[] arr = {1, 2, 3};
List list = Arrays.asList(arr);
C
CyC2018 已提交
77 78
```

C
CyC2018 已提交
79
也可以使用以下方式调用 asList():
C
CyC2018 已提交
80 81

```java
C
CyC2018 已提交
82
List list = Arrays.asList(1, 2, 3);
C
CyC2018 已提交
83 84
```

C
CyC2018 已提交
85
# 三、源码分析
C
CyC2018 已提交
86

C
CyC2018 已提交
87
如果没有特别说明,以下源码分析基于 JDK 1.8。
C
CyC2018 已提交
88

C
CyC2018 已提交
89
在 IDEA 中 double shift 调出 Search EveryWhere,查找源码文件,找到之后就可以阅读源码。
C
CyC2018 已提交
90

C
CyC2018 已提交
91
## ArrayList
C
CyC2018 已提交
92

C
CyC2018 已提交
93
### 1. 概览
C
CyC2018 已提交
94

C
CyC2018 已提交
95
实现了 RandomAccess 接口,因此支持随机访问。这是理所当然的,因为 ArrayList 是基于数组实现的。
C
CyC2018 已提交
96 97

```java
C
CyC2018 已提交
98 99
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
C
CyC2018 已提交
100 101
```

C
CyC2018 已提交
102
数组的默认大小为 10。
C
CyC2018 已提交
103 104

```java
C
CyC2018 已提交
105
private static final int DEFAULT_CAPACITY = 10;
C
CyC2018 已提交
106 107
```

C
CyC2018 已提交
108
### 2. 扩容
C
CyC2018 已提交
109

C
CyC2018 已提交
110
添加元素时使用 ensureCapacityInternal() 方法来保证容量足够,如果不够时,需要使用 grow() 方法进行扩容,新容量的大小为 `oldCapacity + (oldCapacity >> 1)`,也就是旧容量的 1.5 倍。
C
CyC2018 已提交
111

C
CyC2018 已提交
112
扩容操作需要调用 `Arrays.copyOf()` 把原数组整个复制到新数组中,这个操作代价很高,因此最好在创建 ArrayList 对象时就指定大概的容量大小,减少扩容操作的次数。
C
CyC2018 已提交
113 114

```java
C
CyC2018 已提交
115 116 117 118
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
C
CyC2018 已提交
119 120
}

C
CyC2018 已提交
121 122 123 124 125
private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    ensureExplicitCapacity(minCapacity);
C
CyC2018 已提交
126
}
C
CyC2018 已提交
127

C
CyC2018 已提交
128 129 130 131 132
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
C
CyC2018 已提交
133 134
}

C
CyC2018 已提交
135 136 137 138 139 140 141 142 143 144
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);
C
CyC2018 已提交
145 146 147
}
```

C
CyC2018 已提交
148
### 3. 删除元素
C
CyC2018 已提交
149

C
CyC2018 已提交
150
需要调用 System.arraycopy() 将 index+1 后面的元素都复制到 index 位置上,该操作的时间复杂度为 O(N),可以看出 ArrayList 删除元素的代价是非常高的。
C
CyC2018 已提交
151 152

```java
C
CyC2018 已提交
153 154 155 156 157 158 159 160 161
public E remove(int index) {
    rangeCheck(index);
    modCount++;
    E oldValue = elementData(index);
    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index, numMoved);
    elementData[--size] = null; // clear to let GC do its work
    return oldValue;
C
CyC2018 已提交
162 163 164
}
```

C
CyC2018 已提交
165
### 4. Fail-Fast
C
CyC2018 已提交
166

C
CyC2018 已提交
167
modCount 用来记录 ArrayList 结构发生变化的次数。结构发生变化是指添加或者删除至少一个元素的所有操作,或者是调整内部数组的大小,仅仅只是设置元素的值不算结构发生变化。
C
CyC2018 已提交
168

C
CyC2018 已提交
169
在进行序列化或者迭代等操作时,需要比较操作前后 modCount 是否改变,如果改变了需要抛出 ConcurrentModificationException。
C
CyC2018 已提交
170 171

```java
C
CyC2018 已提交
172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
private void writeObject(java.io.ObjectOutputStream s)
    throws java.io.IOException{
    // Write out element count, and any hidden stuff
    int expectedModCount = modCount;
    s.defaultWriteObject();

    // Write out size as capacity for behavioural compatibility with clone()
    s.writeInt(size);

    // Write out all elements in the proper order.
    for (int i=0; i<size; i++) {
        s.writeObject(elementData[i]);
    }

    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
C
CyC2018 已提交
189 190 191
}
```

C
CyC2018 已提交
192
### 5. 序列化
C
CyC2018 已提交
193

C
CyC2018 已提交
194
ArrayList 基于数组实现,并且具有动态扩容特性,因此保存元素的数组不一定都会被使用,那么就没必要全部进行序列化。
C
CyC2018 已提交
195

C
CyC2018 已提交
196
保存元素的数组 elementData 使用 transient 修饰,该关键字声明数组默认不会被序列化。
C
CyC2018 已提交
197 198

```java
C
CyC2018 已提交
199
transient Object[] elementData; // non-private to simplify nested class access
C
CyC2018 已提交
200 201
```

C
CyC2018 已提交
202
ArrayList 实现了 writeObject() 和 readObject() 来控制只序列化数组中有元素填充那部分内容。
C
CyC2018 已提交
203 204

```java
C
CyC2018 已提交
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224
private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    elementData = EMPTY_ELEMENTDATA;

    // Read in size, and any hidden stuff
    s.defaultReadObject();

    // Read in capacity
    s.readInt(); // ignored

    if (size > 0) {
        // be like clone(), allocate array based upon size not capacity
        ensureCapacityInternal(size);

        Object[] a = elementData;
        // Read in all elements in the proper order.
        for (int i=0; i<size; i++) {
            a[i] = s.readObject();
        }
    }
C
CyC2018 已提交
225 226 227 228
}
```

```java
C
CyC2018 已提交
229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245
private void writeObject(java.io.ObjectOutputStream s)
    throws java.io.IOException{
    // Write out element count, and any hidden stuff
    int expectedModCount = modCount;
    s.defaultWriteObject();

    // Write out size as capacity for behavioural compatibility with clone()
    s.writeInt(size);

    // Write out all elements in the proper order.
    for (int i=0; i<size; i++) {
        s.writeObject(elementData[i]);
    }

    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
C
CyC2018 已提交
246 247 248
}
```

C
CyC2018 已提交
249
序列化时需要使用 ObjectOutputStream 的 writeObject() 将对象转换为字节流并输出。而 writeObject() 方法在传入的对象存在 writeObject() 的时候会去反射调用该对象的 writeObject() 来实现序列化。反序列化使用的是 ObjectInputStream 的 readObject() 方法,原理类似。
C
CyC2018 已提交
250 251

```java
C
CyC2018 已提交
252 253
ArrayList list = new ArrayList();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
C
CyC2018 已提交
254 255 256
oos.writeObject(list);
```

C
CyC2018 已提交
257
## Vector
C
CyC2018 已提交
258

C
CyC2018 已提交
259
### 1. 同步
C
CyC2018 已提交
260

C
CyC2018 已提交
261
它的实现与 ArrayList 类似,但是使用了 synchronized 进行同步。
C
CyC2018 已提交
262 263

```java
C
CyC2018 已提交
264 265 266 267 268
public synchronized boolean add(E e) {
    modCount++;
    ensureCapacityHelper(elementCount + 1);
    elementData[elementCount++] = e;
    return true;
C
CyC2018 已提交
269 270
}

C
CyC2018 已提交
271 272 273
public synchronized E get(int index) {
    if (index >= elementCount)
        throw new ArrayIndexOutOfBoundsException(index);
C
CyC2018 已提交
274

C
CyC2018 已提交
275
    return elementData(index);
C
CyC2018 已提交
276 277 278
}
```

C
CyC2018 已提交
279
### 2. 与 ArrayList 的比较
C
CyC2018 已提交
280

C
CyC2018 已提交
281 282
- Vector 是同步的,因此开销就比 ArrayList 要大,访问速度更慢。最好使用 ArrayList 而不是 Vector,因为同步操作完全可以由程序员自己来控制;
- Vector 每次扩容请求其大小的 2 倍空间,而 ArrayList 是 1.5 倍。
C
CyC2018 已提交
283

C
CyC2018 已提交
284
### 3. 替代方案
C
CyC2018 已提交
285

C
CyC2018 已提交
286
可以使用 `Collections.synchronizedList();` 得到一个线程安全的 ArrayList。
C
CyC2018 已提交
287

C
CyC2018 已提交
288
```java
C
CyC2018 已提交
289 290
List<String> list = new ArrayList<>();
List<String> synList = Collections.synchronizedList(list);
C
CyC2018 已提交
291
```
C
CyC2018 已提交
292

C
CyC2018 已提交
293
也可以使用 concurrent 并发包下的 CopyOnWriteArrayList 类。
C
CyC2018 已提交
294

C
CyC2018 已提交
295
```java
C
CyC2018 已提交
296
List<String> list = new CopyOnWriteArrayList<>();
C
CyC2018 已提交
297
```
C
CyC2018 已提交
298

C
CyC2018 已提交
299
## CopyOnWriteArrayList
C
CyC2018 已提交
300

C
CyC2018 已提交
301
### 读写分离
C
CyC2018 已提交
302 303 304

写操作在一个复制的数组上进行,读操作还是在原始数组中进行,读写分离,互不影响。

C
CyC2018 已提交
305
写操作需要加锁,防止并发写入时导致写入数据丢失。
C
CyC2018 已提交
306 307

写操作结束之后需要把原始数组指向新的复制数组。
C
CyC2018 已提交
308 309

```java
C
CyC2018 已提交
310 311 312 313 314 315 316 317 318 319 320 321 322
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();
    }
C
CyC2018 已提交
323 324
}

C
CyC2018 已提交
325 326
final void setArray(Object[] a) {
    array = a;
C
CyC2018 已提交
327
}
C
CyC2018 已提交
328
```
C
CyC2018 已提交
329

C
CyC2018 已提交
330
```java
C
CyC2018 已提交
331
@SuppressWarnings("unchecked")
C
CyC2018 已提交
332 333
private E get(Object[] a, int index) {
    return (E) a[index];
C
CyC2018 已提交
334 335 336
}
```

C
CyC2018 已提交
337
### 适用场景
C
CyC2018 已提交
338

C
CyC2018 已提交
339
CopyOnWriteArrayList 在写操作的同时允许读操作,大大提高了读操作的性能,因此很适合读多写少的应用场景。
C
CyC2018 已提交
340

C
CyC2018 已提交
341
但是 CopyOnWriteArrayList 有其缺陷:
C
CyC2018 已提交
342

C
CyC2018 已提交
343 344
- 内存占用:在写操作时需要复制一个新的数组,使得内存占用为原来的两倍左右;
- 数据不一致:读操作不能读取实时性的数据,因为部分写操作的数据还未同步到读数组中。
C
CyC2018 已提交
345

C
CyC2018 已提交
346
所以 CopyOnWriteArrayList 不适合内存敏感以及对实时性要求很高的场景。
C
CyC2018 已提交
347

C
CyC2018 已提交
348
## LinkedList
C
CyC2018 已提交
349

C
CyC2018 已提交
350
### 1. 概览
C
CyC2018 已提交
351

C
CyC2018 已提交
352
基于双向链表实现,使用 Node 存储链表节点信息。
C
CyC2018 已提交
353

C
CyC2018 已提交
354
```java
C
CyC2018 已提交
355 356 357 358
private static class Node<E> {
    E item;
    Node<E> next;
    Node<E> prev;
C
CyC2018 已提交
359 360
}
```
C
CyC2018 已提交
361

C
CyC2018 已提交
362
每个链表存储了 first 和 last 指针:
C
CyC2018 已提交
363 364

```java
C
CyC2018 已提交
365 366
transient Node<E> first;
transient Node<E> last;
C
CyC2018 已提交
367 368
```

C
CyC2018 已提交
369
<img src="index_files/49495c95-52e5-4c9a-b27b-92cf235ff5ec.png" width="500"/>
C
CyC2018 已提交
370

C
CyC2018 已提交
371
### 2. 与 ArrayList 的比较
C
CyC2018 已提交
372

C
CyC2018 已提交
373 374 375
- ArrayList 基于动态数组实现,LinkedList 基于双向链表实现;
- ArrayList 支持随机访问,LinkedList 不支持;
- LinkedList 在任意位置添加删除元素更快。
C
CyC2018 已提交
376

C
CyC2018 已提交
377
## HashMap
C
CyC2018 已提交
378

C
CyC2018 已提交
379
为了便于理解,以下源码分析以 JDK 1.7 为主。
C
CyC2018 已提交
380

C
CyC2018 已提交
381
### 1. 存储结构
C
CyC2018 已提交
382

C
CyC2018 已提交
383
内部包含了一个 Entry 类型的数组 table。
C
CyC2018 已提交
384 385

```java
C
CyC2018 已提交
386
transient Entry[] table;
C
CyC2018 已提交
387 388
```

C
CyC2018 已提交
389
Entry 存储着键值对。它包含了四个字段,从 next 字段我们可以看出 Entry 是一个链表。即数组中的每个位置被当成一个桶,一个桶存放一个链表。HashMap 使用拉链法来解决冲突,同一个链表中存放哈希值相同的 Entry。
C
CyC2018 已提交
390

C
CyC2018 已提交
391
<img src="index_files/8fe838e3-ef77-4f63-bf45-417b6bc5c6bb.png" width="600"/>
C
CyC2018 已提交
392 393

```java
C
CyC2018 已提交
394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442
static class Entry<K,V> implements Map.Entry<K,V> {
    final K key;
    V value;
    Entry<K,V> next;
    int hash;

    Entry(int h, K k, V v, Entry<K,V> n) {
        value = v;
        next = n;
        key = k;
        hash = h;
    }

    public final K getKey() {
        return key;
    }

    public final V getValue() {
        return value;
    }

    public final V setValue(V newValue) {
        V oldValue = value;
        value = newValue;
        return oldValue;
    }

    public final boolean equals(Object o) {
        if (!(o instanceof Map.Entry))
            return false;
        Map.Entry e = (Map.Entry)o;
        Object k1 = getKey();
        Object k2 = e.getKey();
        if (k1 == k2 || (k1 != null && k1.equals(k2))) {
            Object v1 = getValue();
            Object v2 = e.getValue();
            if (v1 == v2 || (v1 != null && v1.equals(v2)))
                return true;
        }
        return false;
    }

    public final int hashCode() {
        return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue());
    }

    public final String toString() {
        return getKey() + "=" + getValue();
    }
C
CyC2018 已提交
443 444 445
}
```

C
CyC2018 已提交
446
### 2. 拉链法的工作原理
C
CyC2018 已提交
447 448

```java
C
CyC2018 已提交
449 450 451 452
HashMap<String, String> map = new HashMap<>();
map.put("K1", "V1");
map.put("K2", "V2");
map.put("K3", "V3");
C
CyC2018 已提交
453 454
```

C
CyC2018 已提交
455 456 457 458
- 新建一个 HashMap,默认大小为 16;
- 插入 &lt;K1,V1> 键值对,先计算 K1 的 hashCode 为 115,使用除留余数法得到所在的桶下标 115%16=3。
- 插入 &lt;K2,V2> 键值对,先计算 K2 的 hashCode 为 118,使用除留余数法得到所在的桶下标 118%16=6。
- 插入 &lt;K3,V3> 键值对,先计算 K3 的 hashCode 为 118,使用除留余数法得到所在的桶下标 118%16=6,插在 &lt;K2,V2> 前面。
C
CyC2018 已提交
459

C
CyC2018 已提交
460
应该注意到链表的插入是以头插法方式进行的,例如上面的 &lt;K3,V3> 不是插在 &lt;K2,V2> 后面,而是插入在链表头部。
C
CyC2018 已提交
461 462 463

查找需要分成两步进行:

C
CyC2018 已提交
464 465
- 计算键值对所在的桶;
- 在链表上顺序查找,时间复杂度显然和链表的长度成正比。
C
CyC2018 已提交
466

C
CyC2018 已提交
467
<img src="index_files/49d6de7b-0d0d-425c-9e49-a1559dc23b10.png" width="600"/>
C
CyC2018 已提交
468

C
CyC2018 已提交
469
### 3. put 操作
C
CyC2018 已提交
470 471

```java
C
CyC2018 已提交
472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496
public V put(K key, V value) {
    if (table == EMPTY_TABLE) {
        inflateTable(threshold);
    }
    // 键为 null 单独处理
    if (key == null)
        return putForNullKey(value);
    int hash = hash(key);
    // 确定桶下标
    int i = indexFor(hash, table.length);
    // 先找出是否已经存在键为 key 的键值对,如果存在的话就更新这个键值对的值为 value
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }

    modCount++;
    // 插入新键值对
    addEntry(hash, key, value, i);
    return null;
C
CyC2018 已提交
497 498 499
}
```

C
CyC2018 已提交
500
HashMap 允许插入键为 null 的键值对。但是因为无法调用 null 的 hashCode() 方法,也就无法确定该键值对的桶下标,只能通过强制指定一个桶下标来存放。HashMap 使用第 0 个桶存放键为 null 的键值对。
C
CyC2018 已提交
501 502

```java
C
CyC2018 已提交
503 504 505 506 507 508 509 510 511 512 513 514
private V putForNullKey(V value) {
    for (Entry<K,V> e = table[0]; e != null; e = e.next) {
        if (e.key == null) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }
    modCount++;
    addEntry(0, null, value, 0);
    return null;
C
CyC2018 已提交
515 516 517 518 519 520
}
```

使用链表的头插法,也就是新的键值对插在链表的头部,而不是链表的尾部。

```java
C
CyC2018 已提交
521 522 523 524 525 526 527 528
void addEntry(int hash, K key, V value, int bucketIndex) {
    if ((size >= threshold) && (null != table[bucketIndex])) {
        resize(2 * table.length);
        hash = (null != key) ? hash(key) : 0;
        bucketIndex = indexFor(hash, table.length);
    }

    createEntry(hash, key, value, bucketIndex);
C
CyC2018 已提交
529 530
}

C
CyC2018 已提交
531 532 533 534 535
void createEntry(int hash, K key, V value, int bucketIndex) {
    Entry<K,V> e = table[bucketIndex];
    // 头插法,链表头部指向新的键值对
    table[bucketIndex] = new Entry<>(hash, key, value, e);
    size++;
C
CyC2018 已提交
536 537 538 539
}
```

```java
C
CyC2018 已提交
540 541 542 543 544
Entry(int h, K k, V v, Entry<K,V> n) {
    value = v;
    next = n;
    key = k;
    hash = h;
C
CyC2018 已提交
545 546
}
```
C
CyC2018 已提交
547

C
CyC2018 已提交
548
### 4. 确定桶下标
C
CyC2018 已提交
549

C
CyC2018 已提交
550 551 552
很多操作都需要先确定一个键值对所在的桶下标。

```java
C
CyC2018 已提交
553 554
int hash = hash(key);
int i = indexFor(hash, table.length);
C
CyC2018 已提交
555 556
```

C
CyC2018 已提交
557
**4.1 计算 hash 值**
C
CyC2018 已提交
558 559

```java
C
CyC2018 已提交
560 561 562 563 564 565 566 567 568 569 570 571 572
final int hash(Object k) {
    int h = hashSeed;
    if (0 != h && k instanceof String) {
        return sun.misc.Hashing.stringHash32((String) k);
    }

    h ^= k.hashCode();

    // This function ensures that hashCodes that differ only by
    // constant multiples at each bit position have a bounded
    // number of collisions (approximately 8 at default load factor).
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
C
CyC2018 已提交
573 574 575 576
}
```

```java
C
CyC2018 已提交
577 578
public final int hashCode() {
    return Objects.hashCode(key) ^ Objects.hashCode(value);
C
CyC2018 已提交
579 580 581
}
```

C
CyC2018 已提交
582
**4.2 取模**
C
CyC2018 已提交
583

C
CyC2018 已提交
584
令 x = 1<<4,即 x 为 2 的 4 次方,它具有以下性质:
C
CyC2018 已提交
585 586

```
C
CyC2018 已提交
587 588
x   : 00010000
x-1 : 00001111
C
CyC2018 已提交
589 590
```

C
CyC2018 已提交
591
令一个数 y 与 x-1 做与运算,可以去除 y 位级表示的第 4 位以上数:
C
CyC2018 已提交
592 593

```
C
CyC2018 已提交
594 595 596
y       : 10110010
x-1     : 00001111
y&(x-1) : 00000010
C
CyC2018 已提交
597 598
```

C
CyC2018 已提交
599
这个性质和 y 对 x 取模效果是一样的:
C
CyC2018 已提交
600 601

```
C
CyC2018 已提交
602 603 604
y   : 10110010
x   : 00010000
y%x : 00000010
C
CyC2018 已提交
605 606
```

C
CyC2018 已提交
607
我们知道,位运算的代价比求模运算小的多,因此在进行这种计算时用位运算的话能带来更高的性能。
C
CyC2018 已提交
608

C
CyC2018 已提交
609
确定桶下标的最后一步是将 key 的 hash 值对桶个数取模:hash%capacity,如果能保证 capacity 为 2 的 n 次方,那么就可以将这个操作转换为位运算。
C
CyC2018 已提交
610 611

```java
C
CyC2018 已提交
612 613
static int indexFor(int h, int length) {
    return h & (length-1);
C
CyC2018 已提交
614 615 616
}
```

C
CyC2018 已提交
617
### 5. 扩容-基本原理
C
CyC2018 已提交
618

C
CyC2018 已提交
619
设 HashMap 的 table 长度为 M,需要存储的键值对数量为 N,如果哈希函数满足均匀性的要求,那么每条链表的长度大约为 N/M,因此平均查找次数的复杂度为 O(N/M)。
C
CyC2018 已提交
620

C
CyC2018 已提交
621
为了让查找的成本降低,应该尽可能使得 N/M 尽可能小,因此需要保证 M 尽可能大,也就是说 table 要尽可能大。HashMap 采用动态扩容来根据当前的 N 值来调整 M 值,使得空间效率和时间效率都能得到保证。
C
CyC2018 已提交
622

C
CyC2018 已提交
623
和扩容相关的参数主要有:capacity、size、threshold 和 load_factor。
C
CyC2018 已提交
624

C
CyC2018 已提交
625 626 627 628 629 630
| 参数 | 含义 |
| :--: | :-- |
| capacity | table 的容量大小,默认为 16。需要注意的是 capacity 必须保证为 2 的 n 次方。|
| size | 键值对数量。 |
| threshold | size 的临界值,当 size 大于等于 threshold 就必须进行扩容操作。 |
| loadFactor | 装载因子,table 能够使用的比例,threshold = capacity * loadFactor。|
C
CyC2018 已提交
631 632

```java
C
CyC2018 已提交
633
static final int DEFAULT_INITIAL_CAPACITY = 16;
C
CyC2018 已提交
634

C
CyC2018 已提交
635
static final int MAXIMUM_CAPACITY = 1 << 30;
C
CyC2018 已提交
636

C
CyC2018 已提交
637
static final float DEFAULT_LOAD_FACTOR = 0.75f;
C
CyC2018 已提交
638

C
CyC2018 已提交
639
transient Entry[] table;
C
CyC2018 已提交
640

C
CyC2018 已提交
641
transient int size;
C
CyC2018 已提交
642

C
CyC2018 已提交
643
int threshold;
C
CyC2018 已提交
644

C
CyC2018 已提交
645
final float loadFactor;
C
CyC2018 已提交
646

C
CyC2018 已提交
647
transient int modCount;
C
CyC2018 已提交
648 649
```

C
CyC2018 已提交
650
从下面的添加元素代码中可以看出,当需要扩容时,令 capacity 为原来的两倍。
C
CyC2018 已提交
651 652

```java
C
CyC2018 已提交
653 654 655 656 657
void addEntry(int hash, K key, V value, int bucketIndex) {
    Entry<K,V> e = table[bucketIndex];
    table[bucketIndex] = new Entry<>(hash, key, value, e);
    if (size++ >= threshold)
        resize(2 * table.length);
C
CyC2018 已提交
658 659 660
}
```

C
CyC2018 已提交
661
扩容使用 resize() 实现,需要注意的是,扩容操作同样需要把 oldTable 的所有键值对重新插入 newTable 中,因此这一步是很费时的。
C
CyC2018 已提交
662 663

```java
C
CyC2018 已提交
664 665 666 667 668 669 670 671 672 673 674
void resize(int newCapacity) {
    Entry[] oldTable = table;
    int oldCapacity = oldTable.length;
    if (oldCapacity == MAXIMUM_CAPACITY) {
        threshold = Integer.MAX_VALUE;
        return;
    }
    Entry[] newTable = new Entry[newCapacity];
    transfer(newTable);
    table = newTable;
    threshold = (int)(newCapacity * loadFactor);
C
CyC2018 已提交
675 676
}

C
CyC2018 已提交
677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692
void transfer(Entry[] newTable) {
    Entry[] src = table;
    int newCapacity = newTable.length;
    for (int j = 0; j < src.length; j++) {
        Entry<K,V> e = src[j];
        if (e != null) {
            src[j] = null;
            do {
                Entry<K,V> next = e.next;
                int i = indexFor(e.hash, newCapacity);
                e.next = newTable[i];
                newTable[i] = e;
                e = next;
            } while (e != null);
        }
    }
C
CyC2018 已提交
693 694 695
}
```

C
CyC2018 已提交
696
### 6. 扩容-重新计算桶下标
C
CyC2018 已提交
697

C
CyC2018 已提交
698
在进行扩容时,需要把键值对重新放到对应的桶上。HashMap 使用了一个特殊的机制,可以降低重新计算桶下标的操作。
C
CyC2018 已提交
699

C
CyC2018 已提交
700
假设原数组长度 capacity 为 16,扩容之后 new capacity 为 32:
C
CyC2018 已提交
701 702

```html
C
CyC2018 已提交
703 704
capacity     : 00010000
new capacity : 00100000
C
CyC2018 已提交
705 706
```

C
CyC2018 已提交
707
对于一个 Key,
C
CyC2018 已提交
708

C
CyC2018 已提交
709 710
- 它的哈希值如果在第 5 位上为 0,那么取模得到的结果和之前一样;
- 如果为 1,那么得到的结果为原来的结果 +16。
C
CyC2018 已提交
711

C
CyC2018 已提交
712
### 7. 计算数组容量
C
CyC2018 已提交
713

C
CyC2018 已提交
714
HashMap 构造函数允许用户传入的容量不是 2 的 n 次方,因为它可以自动地将传入的容量转换为 2 的 n 次方。
C
CyC2018 已提交
715

C
CyC2018 已提交
716
先考虑如何求一个数的掩码,对于 10010000,它的掩码为 11111111,可以使用以下方法得到:
C
CyC2018 已提交
717 718

```
C
CyC2018 已提交
719 720 721
mask |= mask >> 1    11011000
mask |= mask >> 2    11111110
mask |= mask >> 4    11111111
C
CyC2018 已提交
722 723
```

C
CyC2018 已提交
724
mask+1 是大于原始数字的最小的 2 的 n 次方。
C
CyC2018 已提交
725

C
CyC2018 已提交
726
```
C
CyC2018 已提交
727 728
num     10010000
mask+1 100000000
C
CyC2018 已提交
729 730
```

C
CyC2018 已提交
731
以下是 HashMap 中计算数组容量的代码:
C
CyC2018 已提交
732 733

```java
C
CyC2018 已提交
734 735 736 737 738 739 740 741
static final int tableSizeFor(int cap) {
    int n = cap - 1;
    n |= n >>> 1;
    n |= n >>> 2;
    n |= n >>> 4;
    n |= n >>> 8;
    n |= n >>> 16;
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
C
CyC2018 已提交
742 743 744
}
```

C
CyC2018 已提交
745
### 8. 链表转红黑树
C
CyC2018 已提交
746

C
CyC2018 已提交
747
从 JDK 1.8 开始,一个桶存储的链表长度大于 8 时会将链表转换为红黑树。
C
CyC2018 已提交
748

C
CyC2018 已提交
749
### 9. 与 HashTable 的比较
C
CyC2018 已提交
750

C
CyC2018 已提交
751 752 753 754
- HashTable 使用 synchronized 来进行同步。
- HashMap 可以插入键为 null 的 Entry。
- HashMap 的迭代器是 fail-fast 迭代器。
- HashMap 不能保证随着时间的推移 Map 中的元素次序是不变的。
C
CyC2018 已提交
755

C
CyC2018 已提交
756
## ConcurrentHashMap
C
CyC2018 已提交
757

C
CyC2018 已提交
758
### 1. 存储结构
C
CyC2018 已提交
759 760

```java
C
CyC2018 已提交
761 762 763 764 765
static final class HashEntry<K,V> {
    final int hash;
    final K key;
    volatile V value;
    volatile HashEntry<K,V> next;
C
CyC2018 已提交
766 767 768
}
```

C
CyC2018 已提交
769
ConcurrentHashMap 和 HashMap 实现上类似,最主要的差别是 ConcurrentHashMap 采用了分段锁(Segment),每个分段锁维护着几个桶(HashEntry),多个线程可以同时访问不同分段锁上的桶,从而使其并发度更高(并发度就是 Segment 的个数)。
C
CyC2018 已提交
770

C
CyC2018 已提交
771
Segment 继承自 ReentrantLock。
C
CyC2018 已提交
772

C
CyC2018 已提交
773
```java
C
CyC2018 已提交
774
static final class Segment<K,V> extends ReentrantLock implements Serializable {
C
CyC2018 已提交
775

C
CyC2018 已提交
776
    private static final long serialVersionUID = 2249069246763182397L;
C
CyC2018 已提交
777

C
CyC2018 已提交
778 779
    static final int MAX_SCAN_RETRIES =
        Runtime.getRuntime().availableProcessors() > 1 ? 64 : 1;
C
CyC2018 已提交
780

C
CyC2018 已提交
781
    transient volatile HashEntry<K,V>[] table;
C
CyC2018 已提交
782

C
CyC2018 已提交
783
    transient int count;
C
CyC2018 已提交
784

C
CyC2018 已提交
785
    transient int modCount;
C
CyC2018 已提交
786

C
CyC2018 已提交
787
    transient int threshold;
C
CyC2018 已提交
788

C
CyC2018 已提交
789
    final float loadFactor;
C
CyC2018 已提交
790 791 792 793
}
```

```java
C
CyC2018 已提交
794
final Segment<K,V>[] segments;
C
CyC2018 已提交
795 796
```

C
CyC2018 已提交
797
默认的并发级别为 16,也就是说默认创建 16 个 Segment。
C
CyC2018 已提交
798 799

```java
C
CyC2018 已提交
800
static final int DEFAULT_CONCURRENCY_LEVEL = 16;
C
CyC2018 已提交
801 802
```

C
CyC2018 已提交
803
![](index_files/3fdfc89d-719e-4d93-b518-29fa612b3b18.png)
C
CyC2018 已提交
804

C
CyC2018 已提交
805
### 2. size 操作
C
CyC2018 已提交
806

C
CyC2018 已提交
807
每个 Segment 维护了一个 count 变量来统计该 Segment 中的键值对个数。
C
CyC2018 已提交
808

C
CyC2018 已提交
809 810
```java
/**
C
CyC2018 已提交
811 812 813 814
 * The number of elements. Accessed only either within locks
 * or among other volatile reads that maintain visibility.
 */
transient int count;
C
CyC2018 已提交
815
```
C
CyC2018 已提交
816

C
CyC2018 已提交
817
在执行 size 操作时,需要遍历所有 Segment 然后把 count 累计起来。
C
CyC2018 已提交
818

C
CyC2018 已提交
819
ConcurrentHashMap 在执行 size 操作时先尝试不加锁,如果连续两次不加锁操作得到的结果一致,那么可以认为这个结果是正确的。
C
CyC2018 已提交
820

C
CyC2018 已提交
821
尝试次数使用 RETRIES_BEFORE_LOCK 定义,该值为 2,retries 初始值为 -1,因此尝试次数为 3。
C
CyC2018 已提交
822

C
CyC2018 已提交
823
如果尝试的次数超过 3 次,就需要对每个 Segment 加锁。
C
CyC2018 已提交
824 825 826

```java

C
CyC2018 已提交
827
/**
C
CyC2018 已提交
828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874
 * Number of unsynchronized retries in size and containsValue
 * methods before resorting to locking. This is used to avoid
 * unbounded retries if tables undergo continuous modification
 * which would make it impossible to obtain an accurate result.
 */
static final int RETRIES_BEFORE_LOCK = 2;

public int size() {
    // Try a few times to get accurate count. On failure due to
    // continuous async changes in table, resort to locking.
    final Segment<K,V>[] segments = this.segments;
    int size;
    boolean overflow; // true if size overflows 32 bits
    long sum;         // sum of modCounts
    long last = 0L;   // previous sum
    int retries = -1; // first iteration isn't retry
    try {
        for (;;) {
            // 超过尝试次数,则对每个 Segment 加锁
            if (retries++ == RETRIES_BEFORE_LOCK) {
                for (int j = 0; j < segments.length; ++j)
                    ensureSegment(j).lock(); // force creation
            }
            sum = 0L;
            size = 0;
            overflow = false;
            for (int j = 0; j < segments.length; ++j) {
                Segment<K,V> seg = segmentAt(segments, j);
                if (seg != null) {
                    sum += seg.modCount;
                    int c = seg.count;
                    if (c < 0 || (size += c) < 0)
                        overflow = true;
                }
            }
            // 连续两次得到的结果一致,则认为这个结果是正确的
            if (sum == last)
                break;
            last = sum;
        }
    } finally {
        if (retries > RETRIES_BEFORE_LOCK) {
            for (int j = 0; j < segments.length; ++j)
                segmentAt(segments, j).unlock();
        }
    }
    return overflow ? Integer.MAX_VALUE : size;
C
CyC2018 已提交
875 876 877
}
```

C
CyC2018 已提交
878
### 3. JDK 1.8 的改动
C
CyC2018 已提交
879

C
CyC2018 已提交
880
JDK 1.7 使用分段锁机制来实现并发更新操作,核心类为 Segment,它继承自重入锁 ReentrantLock,并发度与 Segment 数量相等。
C
CyC2018 已提交
881

C
CyC2018 已提交
882
JDK 1.8 使用了 CAS 操作来支持更高的并发度,在 CAS 操作失败时使用内置锁 synchronized。
C
CyC2018 已提交
883

C
CyC2018 已提交
884
并且 JDK 1.8 的实现也在链表过长时会转换为红黑树。
C
CyC2018 已提交
885

C
CyC2018 已提交
886
## LinkedHashMap
C
CyC2018 已提交
887

C
CyC2018 已提交
888
### 存储结构
C
CyC2018 已提交
889

C
CyC2018 已提交
890
继承自 HashMap,因此具有和 HashMap 一样的快速查找特性。
C
CyC2018 已提交
891 892

```java
C
CyC2018 已提交
893
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>
C
CyC2018 已提交
894 895
```

C
CyC2018 已提交
896
内部维护了一个双向链表,用来维护插入顺序或者 LRU 顺序。
C
CyC2018 已提交
897 898 899

```java
/**
C
CyC2018 已提交
900 901 902
 * The head (eldest) of the doubly linked list.
 */
transient LinkedHashMap.Entry<K,V> head;
C
CyC2018 已提交
903 904

/**
C
CyC2018 已提交
905 906 907
 * The tail (youngest) of the doubly linked list.
 */
transient LinkedHashMap.Entry<K,V> tail;
C
CyC2018 已提交
908 909
```

C
CyC2018 已提交
910
accessOrder 决定了顺序,默认为 false,此时维护的是插入顺序。
C
CyC2018 已提交
911 912

```java
C
CyC2018 已提交
913
final boolean accessOrder;
C
CyC2018 已提交
914 915
```

C
CyC2018 已提交
916
LinkedHashMap 最重要的是以下用于维护顺序的函数,它们会在 put、get 等方法中调用。
C
CyC2018 已提交
917 918

```java
C
CyC2018 已提交
919 920
void afterNodeAccess(Node<K,V> p) { }
void afterNodeInsertion(boolean evict) { }
C
CyC2018 已提交
921 922
```

C
CyC2018 已提交
923
### afterNodeAccess()
C
CyC2018 已提交
924

C
CyC2018 已提交
925
当一个节点被访问时,如果 accessOrder 为 true,则会将该节点移到链表尾部。也就是说指定为 LRU 顺序之后,在每次访问一个节点时,会将这个节点移到链表尾部,保证链表尾部是最近访问的节点,那么链表首部就是最近最久未使用的节点。
C
CyC2018 已提交
926 927

```java
C
CyC2018 已提交
928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950
void afterNodeAccess(Node<K,V> e) { // move node to last
    LinkedHashMap.Entry<K,V> last;
    if (accessOrder && (last = tail) != e) {
        LinkedHashMap.Entry<K,V> p =
            (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
        p.after = null;
        if (b == null)
            head = a;
        else
            b.after = a;
        if (a != null)
            a.before = b;
        else
            last = b;
        if (last == null)
            head = p;
        else {
            p.before = last;
            last.after = p;
        }
        tail = p;
        ++modCount;
    }
C
CyC2018 已提交
951 952 953
}
```

C
CyC2018 已提交
954
### afterNodeInsertion()
C
CyC2018 已提交
955

C
CyC2018 已提交
956
在 put 等操作之后执行,当 removeEldestEntry() 方法返回 true 时会移除最晚的节点,也就是链表首部节点 first。
C
CyC2018 已提交
957

C
CyC2018 已提交
958
evict 只有在构建 Map 的时候才为 false,在这里为 true。
C
CyC2018 已提交
959 960

```java
C
CyC2018 已提交
961 962 963 964 965 966
void afterNodeInsertion(boolean evict) { // possibly remove eldest
    LinkedHashMap.Entry<K,V> first;
    if (evict && (first = head) != null && removeEldestEntry(first)) {
        K key = first.key;
        removeNode(hash(key), key, null, false, true);
    }
C
CyC2018 已提交
967 968 969
}
```

C
CyC2018 已提交
970
removeEldestEntry() 默认为 false,如果需要让它为 true,需要继承 LinkedHashMap 并且覆盖这个方法的实现,这在实现 LRU 的缓存中特别有用,通过移除最近最久未使用的节点,从而保证缓存空间足够,并且缓存的数据都是热点数据。
C
CyC2018 已提交
971 972

```java
C
CyC2018 已提交
973 974
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
    return false;
C
CyC2018 已提交
975
}
C
CyC2018 已提交
976 977
```

C
CyC2018 已提交
978
### LRU 缓存
C
CyC2018 已提交
979

C
CyC2018 已提交
980
以下是使用 LinkedHashMap 实现的一个 LRU 缓存:
C
CyC2018 已提交
981

C
CyC2018 已提交
982 983 984
- 设定最大缓存空间 MAX_ENTRIES  为 3;
- 使用 LinkedHashMap 的构造函数将 accessOrder 设置为 true,开启 LRU 顺序;
- 覆盖 removeEldestEntry() 方法实现,在节点多于 MAX_ENTRIES 就会将最近最久未使用的数据移除。
C
CyC2018 已提交
985 986

```java
C
CyC2018 已提交
987 988
class LRUCache<K, V> extends LinkedHashMap<K, V> {
    private static final int MAX_ENTRIES = 3;
C
CyC2018 已提交
989

C
CyC2018 已提交
990 991 992
    protected boolean removeEldestEntry(Map.Entry eldest) {
        return size() > MAX_ENTRIES;
    }
C
CyC2018 已提交
993

C
CyC2018 已提交
994 995 996
    LRUCache() {
        super(MAX_ENTRIES, 0.75f, true);
    }
C
CyC2018 已提交
997 998 999 1000
}
```

```java
C
CyC2018 已提交
1001 1002 1003 1004 1005 1006 1007 1008
public static void main(String[] args) {
    LRUCache<Integer, String> cache = new LRUCache<>();
    cache.put(1, "a");
    cache.put(2, "b");
    cache.put(3, "c");
    cache.get(1);
    cache.put(4, "d");
    System.out.println(cache.keySet());
C
CyC2018 已提交
1009 1010 1011 1012
}
```

```html
C
CyC2018 已提交
1013
[3, 1, 4]
C
CyC2018 已提交
1014 1015
```

C
CyC2018 已提交
1016
## WeakHashMap
C
CyC2018 已提交
1017

C
CyC2018 已提交
1018
### 存储结构
C
CyC2018 已提交
1019

C
CyC2018 已提交
1020
WeakHashMap 的 Entry 继承自 WeakReference,被 WeakReference 关联的对象在下一次垃圾回收时会被回收。
C
CyC2018 已提交
1021

C
CyC2018 已提交
1022
WeakHashMap 主要用来实现缓存,通过使用 WeakHashMap 来引用缓存对象,由 JVM 对这部分缓存进行回收。
C
CyC2018 已提交
1023 1024

```java
C
CyC2018 已提交
1025
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V>
C
CyC2018 已提交
1026 1027
```

C
CyC2018 已提交
1028
### ConcurrentCache
C
CyC2018 已提交
1029

C
CyC2018 已提交
1030
Tomcat 中的 ConcurrentCache 使用了 WeakHashMap 来实现缓存功能。
C
CyC2018 已提交
1031

C
CyC2018 已提交
1032
ConcurrentCache 采取的是分代缓存:
C
CyC2018 已提交
1033

C
CyC2018 已提交
1034 1035 1036 1037
- 经常使用的对象放入 eden 中,eden 使用 ConcurrentHashMap 实现,不用担心会被回收(伊甸园);
- 不常用的对象放入 longterm,longterm 使用 WeakHashMap 实现,这些老对象会被垃圾收集器回收。
- 当调用  get() 方法时,会先从 eden 区获取,如果没有找到的话再到 longterm 获取,当从 longterm 获取到就把对象放入 eden 中,从而保证经常被访问的节点不容易被回收。
- 当调用 put() 方法时,如果 eden 的大小超过了 size,那么就将 eden 中的所有对象都放入 longterm 中,利用虚拟机回收掉一部分不经常使用的对象。
C
CyC2018 已提交
1038 1039

```java
C
CyC2018 已提交
1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070
public final class ConcurrentCache<K, V> {

    private final int size;

    private final Map<K, V> eden;

    private final Map<K, V> longterm;

    public ConcurrentCache(int size) {
        this.size = size;
        this.eden = new ConcurrentHashMap<>(size);
        this.longterm = new WeakHashMap<>(size);
    }

    public V get(K k) {
        V v = this.eden.get(k);
        if (v == null) {
            v = this.longterm.get(k);
            if (v != null)
                this.eden.put(k, v);
        }
        return v;
    }

    public void put(K k, V v) {
        if (this.eden.size() >= size) {
            this.longterm.putAll(this.eden);
            this.eden.clear();
        }
        this.eden.put(k, v);
    }
C
CyC2018 已提交
1071 1072 1073
}
```

C
CyC2018 已提交
1074
# 附录
C
CyC2018 已提交
1075

C
CyC2018 已提交
1076
Collection 绘图源码:
C
CyC2018 已提交
1077 1078 1079 1080

```
@startuml

C
CyC2018 已提交
1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108
interface Collection
interface Set
interface List
interface Queue
interface SortSet

class HashSet
class LinkedHashSet
class TreeSet
class ArrayList
class Vector
class LinkedList
class PriorityQueue


Collection <|-- Set
Collection <|-- List
Collection <|-- Queue
Set <|-- SortSet

Set <|.. HashSet
Set <|.. LinkedHashSet
SortSet <|.. TreeSet
List <|.. ArrayList
List <|.. Vector
List <|.. LinkedList
Queue <|.. LinkedList
Queue <|.. PriorityQueue
C
CyC2018 已提交
1109 1110 1111 1112

@enduml
```

C
CyC2018 已提交
1113
Map 绘图源码
C
CyC2018 已提交
1114 1115 1116 1117

```
@startuml

C
CyC2018 已提交
1118 1119
interface Map
interface SortMap
C
CyC2018 已提交
1120

C
CyC2018 已提交
1121 1122 1123 1124
class HashTable
class LinkedHashMap
class HashMap
class TreeMap
C
CyC2018 已提交
1125

C
CyC2018 已提交
1126 1127 1128 1129 1130
Map <|.. HashTable
Map <|.. LinkedHashMap
Map <|.. HashMap
Map <|-- SortMap
SortMap <|.. TreeMap
C
CyC2018 已提交
1131 1132 1133 1134 1135 1136 1137 1138 1139

@enduml
```

迭代器类图

```
@startuml

C
CyC2018 已提交
1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154
interface Iterable
interface Collection
interface List
interface Set
interface Queue
interface Iterator
interface ListIterator

Iterable <|-- Collection
Collection <|.. List
Collection <|.. Set
Collection <|-- Queue
Iterator <-- Iterable
Iterator <|.. ListIterator
ListIterator <-- List
C
CyC2018 已提交
1155 1156 1157

@enduml
```
C
CyC2018 已提交
1158

C
CyC2018 已提交
1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180
# 参考资料

- Eckel B. Java 编程思想 [M]. 机械工业出版社, 2002.
[Java Collection Framework](https://www.w3resource.com/java-tutorial/java-collections.php)
[Iterator 模式](https://openhome.cc/Gossip/DesignPattern/IteratorPattern.htm)
[Java 8 系列之重新认识 HashMap](https://tech.meituan.com/java_hashmap.html)
[What is difference between HashMap and Hashtable in Java?](http://javarevisited.blogspot.hk/2010/10/difference-between-hashmap-and.html)
[Java 集合之 HashMap](http://www.zhangchangle.com/2018/02/07/Java%E9%9B%86%E5%90%88%E4%B9%8BHashMap/)
[The principle of ConcurrentHashMap analysis](http://www.programering.com/a/MDO3QDNwATM.html)
[探索 ConcurrentHashMap 高并发性的实现机制](https://www.ibm.com/developerworks/cn/java/java-lo-concurrenthashmap/)
[HashMap 相关面试题及其解答](https://www.jianshu.com/p/75adf47958a7)
[Java 集合细节(二):asList 的缺陷](http://wiki.jikexueyuan.com/project/java-enhancement/java-thirtysix.html)
[Java Collection Framework – The LinkedList Class](http://javaconceptoftheday.com/java-collection-framework-linkedlist-class/)

---bottom---CyC---
![](index_files/VP4n3i8m34Ntd28NQ4_0KCJ2q044Oez.png)
![](index_files/SoWkIImgAStDuUBAp2j9BKfBJ4vLy4q.png)
![](index_files/SoWkIImgAStDuUBAp2j9BKfBJ4vLy0G.png)
![](index_files/49495c95-52e5-4c9a-b27b-92cf235ff5ec.png)
![](index_files/8fe838e3-ef77-4f63-bf45-417b6bc5c6bb.png)
![](index_files/49d6de7b-0d0d-425c-9e49-a1559dc23b10.png)
![](index_files/3fdfc89d-719e-4d93-b518-29fa612b3b18.png)