|
| 1 | +<!-- GFM-TOC --> |
| 2 | +* [概览](#概览) |
| 3 | + * [1. List](#1-list) |
| 4 | + * [2. Set](#2-set) |
| 5 | + * [3. Queue](#3-queue) |
| 6 | + * [4. Map](#4-map) |
| 7 | + * [5. Java 1.0/1.1 容器](#5-java-1011-容器) |
| 8 | +* [容器中的设计模式](#容器中的设计模式) |
| 9 | + * [1. 迭代器模式](#1-迭代器模式) |
| 10 | + * [2. 适配器模式](#2-适配器模式) |
| 11 | +* [散列](#散列) |
| 12 | +* [源码分析](#源码分析) |
| 13 | + * [1. ArraList](#1-arralist) |
| 14 | + * [2. Vector 与 Stack](#2-vector-与-stack) |
| 15 | + * [3. LinkedList](#3-linkedlist) |
| 16 | + * [4. TreeMap](#4-treemap) |
| 17 | + * [5. HashMap](#5-hashmap) |
| 18 | + * [6. LinkedHashMap](#6-linkedhashmap) |
| 19 | + * [7. ConcurrentHashMap](#7-concurrenthashmap) |
| 20 | +* [参考资料](#参考资料) |
| 21 | +<!-- GFM-TOC --> |
| 22 | + |
| 23 | + |
| 24 | +# 概览 |
| 25 | + |
| 26 | + |
| 27 | + |
| 28 | +容器主要包括 Collection 和 Map 两种,Collection 又包含了 List、Set 以及 Queue。 |
| 29 | + |
| 30 | +## 1. List |
| 31 | + |
| 32 | +- ArrayList:基于动态数组实现,支持随机访问; |
| 33 | + |
| 34 | +- LinkedList:基于双向循环链表实现,只能顺序访问,但是可以快速地在链表中间插入和删除元素。不仅如此,LinkedList 还可以用作栈、队列和双端队列。 |
| 35 | + |
| 36 | +## 2. Set |
| 37 | + |
| 38 | +- HashSet:基于 Hash 实现,支持快速查找,但是失去有序性; |
| 39 | + |
| 40 | +- TreeSet:基于红黑树实现,保持有序,但是查找效率不如 HashSet; |
| 41 | + |
| 42 | +- LinkedListHashSet:具有 HashSet 的查找效率,且内部使用链表维护元素的插入顺序,因此具有有序性。 |
| 43 | + |
| 44 | +## 3. Queue |
| 45 | + |
| 46 | +只有两个实现:LinkedList 和 PriorityQueue,其中 LinkedList 支持双向队列,PriorityQueue 是基于堆结构实现。 |
| 47 | + |
| 48 | +## 4. Map |
| 49 | + |
| 50 | +- HashMap:基于 Hash 实现 |
| 51 | + |
| 52 | +- LinkedHashMap:使用链表来维护元素的顺序,顺序为插入顺序或者最近最少使用(LRU)顺序 |
| 53 | + |
| 54 | +- TreeMap:基于红黑树实现 |
| 55 | + |
| 56 | +- ConcurrentHashMap:线程安全 Map,不涉及类似于 HashTable 的同步加锁 |
| 57 | + |
| 58 | +## 5. Java 1.0/1.1 容器 |
| 59 | + |
| 60 | +对于旧的容器,我们决不应该使用它们,只需要对它们进行了解。 |
| 61 | + |
| 62 | +- Vector:和 ArrayList 类似,但它是线程安全的 |
| 63 | + |
| 64 | +- HashTable:和 HashMap 类似,但它是线程安全的 |
| 65 | + |
| 66 | +# 容器中的设计模式 |
| 67 | + |
| 68 | +## 1. 迭代器模式 |
| 69 | + |
| 70 | +从概览图可以看到,每个集合类都有一个 Iterator 对象,可以通过这个迭代器对象来遍历集合中的元素。 |
| 71 | + |
| 72 | +[Java 中的迭代器模式 ](https://github.com/CyC2018/InterviewNotes/blob/master/notes/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F.md#92-java-%E5%86%85%E7%BD%AE%E7%9A%84%E8%BF%AD%E4%BB%A3%E5%99%A8) |
| 73 | + |
| 74 | +## 2. 适配器模式 |
| 75 | + |
| 76 | +java.util.Arrays#asList() 可以把数组类型转换为 List 类型。 |
| 77 | + |
| 78 | +```java |
| 79 | + List list = Arrays.asList(1, 2, 3); |
| 80 | + int[] arr = {1, 2, 3}; |
| 81 | + list = Arrays.asList(arr); |
| 82 | +``` |
| 83 | + |
| 84 | +# 散列 |
| 85 | + |
| 86 | +使用 hasCode() 来返回散列值,使用的是对象的地址。 |
| 87 | + |
| 88 | +而 equals() 是用来判断两个对象是否相等的,相等的两个对象散列值一定要相同,但是散列值相同的两个对象不一定相等。 |
| 89 | + |
| 90 | +相等必须满足以下五个性质: |
| 91 | + |
| 92 | +1. 自反性 |
| 93 | +2. 对称性 |
| 94 | +3. 传递性 |
| 95 | +4. 一致性(多次调用 x.equals(y),结果不变) |
| 96 | +5. 对任何不是 null 的对象 x 调用 x.equals(nul) 结果都为 false |
| 97 | + |
| 98 | +# 源码分析 |
| 99 | + |
| 100 | +建议先阅读 [ 算法 - 查找 ](https://github.com/CyC2018/InterviewNotes/blob/master/notes/%E7%AE%97%E6%B3%95.md#%E7%AC%AC%E4%B8%89%E7%AB%A0-%E6%9F%A5%E6%89%BE) 部分,对集合类源码的理解有很大帮助。 |
| 101 | + |
| 102 | +源码下载:[OpenJDK 1.7](http://download.java.net/openjdk/jdk7) |
| 103 | + |
| 104 | +## 1. ArraList |
| 105 | + |
| 106 | +[ArraList.java](https://github.com/CyC2018/JDK-Source-Code/tree/master/src/ArrayList.java) |
| 107 | + |
| 108 | +实现了 RandomAccess 接口,因此支持随机访问,这是理所当然的,因为 ArrayList 是基于数组实现的。 |
| 109 | + |
| 110 | +```java |
| 111 | +public class ArrayList<E> extends AbstractList<E> |
| 112 | + implements List<E>, RandomAccess, Cloneable, java.io.Serializable |
| 113 | +``` |
| 114 | + |
| 115 | +基于数组实现,保存元素的数组使用 transient 修饰,这是因为该数组不一定所有位置都占满元素,因此也就没必要全部都进行序列化。需要重写 writeObject() 和 readObject()。 |
| 116 | + |
| 117 | +```java |
| 118 | +private transient Object[] elementData; |
| 119 | +``` |
| 120 | + |
| 121 | +数组的默认大小为 10 |
| 122 | + |
| 123 | +```java |
| 124 | +public ArrayList(int initialCapacity) { |
| 125 | + super(); |
| 126 | + if (initialCapacity < 0) |
| 127 | + throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); |
| 128 | + this.elementData = new Object[initialCapacity]; |
| 129 | +} |
| 130 | + |
| 131 | +public ArrayList() { |
| 132 | + this(10); |
| 133 | +} |
| 134 | +``` |
| 135 | + |
| 136 | +删除元素时调用 System.arraycopy() 对元素进行复制,因此删除操作成本很高,最好在创建时就指定大概的容量大小,减少复制操作的执行次数。 |
| 137 | + |
| 138 | +```java |
| 139 | +public E remove(int index) { |
| 140 | + rangeCheck(index); |
| 141 | + |
| 142 | + modCount++; |
| 143 | + E oldValue = elementData(index); |
| 144 | + |
| 145 | + int numMoved = size - index - 1; |
| 146 | + if (numMoved > 0) |
| 147 | + System.arraycopy(elementData, index+1, elementData, index, numMoved); |
| 148 | + elementData[--size] = null; // Let gc do its work |
| 149 | + |
| 150 | + return oldValue; |
| 151 | +} |
| 152 | +``` |
| 153 | + |
| 154 | +添加元素时使用 ensureCapacity() 方法来保证容量足够,如果不够时,需要进行扩容,使得新容量为旧容量的 1.5 倍。 |
| 155 | + |
| 156 | +modCount 用来记录 ArrayList 发生变化的次数,因为每次在进行 add() 和 addAll() 时都需要调用 ensureCapacity(),因此直接在 ensureCapacity() 中对 modCount 进行修改。 |
| 157 | + |
| 158 | +```java |
| 159 | +public void ensureCapacity(int minCapacity) { |
| 160 | + if (minCapacity > 0) |
| 161 | + ensureCapacityInternal(minCapacity); |
| 162 | +} |
| 163 | + |
| 164 | +private void ensureCapacityInternal(int minCapacity) { |
| 165 | + modCount++; |
| 166 | + // overflow-conscious code |
| 167 | + if (minCapacity - elementData.length > 0) |
| 168 | + grow(minCapacity); |
| 169 | +} |
| 170 | + |
| 171 | +private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; |
| 172 | + |
| 173 | +private void grow(int minCapacity) { |
| 174 | + // overflow-conscious code |
| 175 | + int oldCapacity = elementData.length; |
| 176 | + int newCapacity = oldCapacity + (oldCapacity >> 1); |
| 177 | + if (newCapacity - minCapacity < 0) |
| 178 | + newCapacity = minCapacity; |
| 179 | + if (newCapacity - MAX_ARRAY_SIZE > 0) |
| 180 | + newCapacity = hugeCapacity(minCapacity); |
| 181 | + // minCapacity is usually close to size, so this is a win: |
| 182 | + elementData = Arrays.copyOf(elementData, newCapacity); |
| 183 | +} |
| 184 | + |
| 185 | +private static int hugeCapacity(int minCapacity) { |
| 186 | + if (minCapacity < 0) // overflow |
| 187 | + throw new OutOfMemoryError(); |
| 188 | + return (minCapacity > MAX_ARRAY_SIZE) ? |
| 189 | + Integer.MAX_VALUE : |
| 190 | + MAX_ARRAY_SIZE; |
| 191 | +} |
| 192 | +``` |
| 193 | + |
| 194 | +在进行序列化或者迭代等操作时,需要比较操作前后 modCount 是否改变,如果改变了需要抛出 ConcurrentModificationException。 |
| 195 | + |
| 196 | +```java |
| 197 | +private void writeObject(java.io.ObjectOutputStream s) |
| 198 | + throws java.io.IOException{ |
| 199 | + // Write out element count, and any hidden stuff |
| 200 | + int expectedModCount = modCount; |
| 201 | + s.defaultWriteObject(); |
| 202 | + |
| 203 | + // Write out array length |
| 204 | + s.writeInt(elementData.length); |
| 205 | + |
| 206 | + // Write out all elements in the proper order. |
| 207 | + for (int i=0; i<size; i++) |
| 208 | + s.writeObject(elementData[i]); |
| 209 | + |
| 210 | + if (modCount != expectedModCount) { |
| 211 | + throw new ConcurrentModificationException(); |
| 212 | + } |
| 213 | + |
| 214 | +} |
| 215 | +``` |
| 216 | + |
| 217 | +**和 Vector 的区别** |
| 218 | + |
| 219 | +1. Vector 和 ArrayList 几乎是完全相同的,唯一的区别在于 Vector 是同步的,因此开销就比 ArrayList 要大,访问要慢。最好使用 ArrayList 而不是 Vector,因为同步完全可以由程序员自己来控制; |
| 220 | +2. Vector 每次扩容请求其大小的 2 倍空间,而 ArrayList 是 1.5 倍。 |
| 221 | + |
| 222 | +为了使用线程安全的 ArrayList,可以使用 Collections.synchronizedList(new ArrayList<>()); 返回一个线程安全的 ArrayList,也可以使用 concurrent 并发包下的 CopyOnWriteArrayList 类; |
| 223 | + |
| 224 | +**和 LinkedList 的区别** |
| 225 | + |
| 226 | +1. ArrayList 基于动态数组实现,LinkedList 基于双向循环链表实现; |
| 227 | +2. ArrayList 支持随机访问,LinkedList 不支持; |
| 228 | +3. LinkedList 在任意位置添加删除元素更快。 |
| 229 | + |
| 230 | +## 2. Vector 与 Stack |
| 231 | + |
| 232 | +[Vector.java](https://github.com/CyC2018/JDK-Source-Code/tree/master/src/Vector.java) |
| 233 | + |
| 234 | +## 3. LinkedList |
| 235 | + |
| 236 | +[LinkedList.java](https://github.com/CyC2018/JDK-Source-Code/tree/master/src/LinkedList.java) |
| 237 | + |
| 238 | +## 4. TreeMap |
| 239 | + |
| 240 | +[TreeMap.java](https://github.com/CyC2018/JDK-Source-Code/tree/master/src/TreeMap.java) |
| 241 | + |
| 242 | +## 5. HashMap |
| 243 | + |
| 244 | +[HashMap.java](https://github.com/CyC2018/JDK-Source-Code/tree/master/src/HashMap.java) |
| 245 | + |
| 246 | +使用拉链法来解决冲突。 |
| 247 | + |
| 248 | +默认容量 capacity 为 16,需要注意的是容量必须保证为 2 的次方。容量就是 Entry[] table 数组的长度,size 是数组的实际使用量。 |
| 249 | + |
| 250 | +threshold 规定了一个 size 的临界值,size 必须小于 threshold,如果大于等于,就必须进行扩容操作。 |
| 251 | + |
| 252 | +threshold = capacity * load_factor,其中 load_factor 为 table 数组能够使用的比例,load_factor 过大会导致聚簇的出现,从而影响查询和插入的效率,详见算法笔记。 |
| 253 | + |
| 254 | +```java |
| 255 | +static final int DEFAULT_INITIAL_CAPACITY = 16; |
| 256 | + |
| 257 | +static final int MAXIMUM_CAPACITY = 1 << 30; |
| 258 | + |
| 259 | +static final float DEFAULT_LOAD_FACTOR = 0.75f; |
| 260 | + |
| 261 | +transient Entry[] table; |
| 262 | + |
| 263 | +transient int size; |
| 264 | + |
| 265 | +int threshold; |
| 266 | + |
| 267 | +final float loadFactor; |
| 268 | + |
| 269 | +transient int modCount; |
| 270 | +``` |
| 271 | + |
| 272 | +从下面的添加元素代码中可以看出,当需要扩容时,令 capacity 为原来的两倍。 |
| 273 | + |
| 274 | +```java |
| 275 | +void addEntry(int hash, K key, V value, int bucketIndex) { |
| 276 | + Entry<K,V> e = table[bucketIndex]; |
| 277 | + table[bucketIndex] = new Entry<>(hash, key, value, e); |
| 278 | + if (size++ >= threshold) |
| 279 | + resize(2 * table.length); |
| 280 | +} |
| 281 | +``` |
| 282 | + |
| 283 | +Entry 用来表示一个键值对元素,其中的 next 指针在序列化时会使用。 |
| 284 | + |
| 285 | +```java |
| 286 | +static class Entry<K,V> implements Map.Entry<K,V> { |
| 287 | + final K key; |
| 288 | + V value; |
| 289 | + Entry<K,V> next; |
| 290 | + final int hash; |
| 291 | +} |
| 292 | +``` |
| 293 | + |
| 294 | +get() 操作需要分成两种情况,key 为 null 和 不为 null,从中可以看出 HashMap 允许插入 null 作为键。 |
| 295 | + |
| 296 | +```java |
| 297 | +public V get(Object key) { |
| 298 | + if (key == null) |
| 299 | + return getForNullKey(); |
| 300 | + int hash = hash(key.hashCode()); |
| 301 | + for (Entry<K,V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) { |
| 302 | + Object k; |
| 303 | + if (e.hash == hash && ((k = e.key) == key || key.equals(k))) |
| 304 | + return e.value; |
| 305 | + } |
| 306 | + return null; |
| 307 | +} |
| 308 | +``` |
| 309 | + |
| 310 | +put() 操作也需要根据 key 是否为 null 做不同的处理,需要注意的是如果本来没有 key 为 null 的键值对,新插入一个 key 为 null 的键值对时默认是放在数组的 0 位置,这是因为 null 不能计算 hash 值,也就无法知道应该放在哪个链表上。 |
| 311 | + |
| 312 | +```java |
| 313 | +public V put(K key, V value) { |
| 314 | + if (key == null) |
| 315 | + return putForNullKey(value); |
| 316 | + int hash = hash(key.hashCode()); |
| 317 | + int i = indexFor(hash, table.length); |
| 318 | + for (Entry<K,V> e = table[i]; e != null; e = e.next) { |
| 319 | + Object k; |
| 320 | + if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { |
| 321 | + V oldValue = e.value; |
| 322 | + e.value = value; |
| 323 | + e.recordAccess(this); |
| 324 | + return oldValue; |
| 325 | + } |
| 326 | + } |
| 327 | + |
| 328 | + modCount++; |
| 329 | + addEntry(hash, key, value, i); |
| 330 | + return null; |
| 331 | +} |
| 332 | +``` |
| 333 | + |
| 334 | +```java |
| 335 | +private V putForNullKey(V value) { |
| 336 | + for (Entry<K,V> e = table[0]; e != null; e = e.next) { |
| 337 | + if (e.key == null) { |
| 338 | + V oldValue = e.value; |
| 339 | + e.value = value; |
| 340 | + e.recordAccess(this); |
| 341 | + return oldValue; |
| 342 | + } |
| 343 | + } |
| 344 | + modCount++; |
| 345 | + addEntry(0, null, value, 0); |
| 346 | + return null; |
| 347 | +} |
| 348 | +``` |
| 349 | + |
| 350 | +## 6. LinkedHashMap |
| 351 | + |
| 352 | +[LinkedHashMap.java](https://github.com/CyC2018/JDK-Source-Code/tree/master/src/HashMap.java) |
| 353 | + |
| 354 | +## 7. ConcurrentHashMap |
| 355 | + |
| 356 | +[ConcurrentHashMap.java](https://github.com/CyC2018/JDK-Source-Code/tree/master/src/HashMap.java) |
| 357 | + |
| 358 | +[ 探索 ConcurrentHashMap 高并发性的实现机制 ](https://www.ibm.com/developerworks/cn/java/java-lo-concurrenthashmap/) |
| 359 | + |
| 360 | +# 参考资料 |
| 361 | + |
| 362 | +- Java 编程思想 |
0 commit comments