沉淀再出发:java中的HashMap、ConcurrentHashMap和Hashtable的认识
沉淀再出发:java中的HashMap、ConcurrentHashMap和Hashtable的认识
一、前言
很多知识在学习或者使用了之后总是会忘记的,但是如果把这些只是背后的原理理解了,并且记忆下来,这样我们就不会忘记了,常用的方法有对比记忆,将几个易混的概念放到一起进行比较,对我们的学习和生活有很大的帮助,比如hashmap和hashtab这两个概念的对比和记忆。
二、HashMap的基础知识
2.1、HashMap的介绍
1 HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。 2 HashMap 继承于AbstractMap类,实现了Map、Cloneable、java.io.Serializable接口。 3 HashMap 的实现不是同步的,这意味着它不是线程安全的。它的key、value都可以为null。此外,HashMap中的映射不是有序的。
HashMap 的实例有两个参数影响其性能:“初始容量” 和 “加载因子”。容量是哈希表中桶的数量,初始容量是哈希表在创建时的容量。加载因子是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行 rehash 操作(即重建内部数据结构),从而哈希表将具有大约两倍的桶数。通常,默认加载因子是 0.75, 这是在时间和空间成本上寻求一种折衷。加载因子过高虽然减少了空间开销,但同时也增加了查询成本(在大多数 HashMap 类的操作中,包括 get 和 put 操作,都反映了这一点)。在设置初始容量时应该考虑到映射中所需的条目数及其加载因子,以便最大限度地减少 rehash 操作次数。如果初始容量大于最大条目数除以加载因子,则不会发生 rehash 操作。
2.2、HashMap源码解读
1 package java.util; 2 import java.io.*; 3 4 public class HashMap<K,V> 5 extends AbstractMap<K,V> 6 implements Map<K,V>, Cloneable, Serializable 7 { 8 9 // 默认的初始容量是16,必须是2的幂。 10 static final int DEFAULT_INITIAL_CAPACITY = 16; 11 12 // 最大容量(必须是2的幂且小于2的30次方,传入容量过大将被这个值替换) 13 static final int MAXIMUM_CAPACITY = 1 << 30; 14 15 // 默认加载因子 16 static final float DEFAULT_LOAD_FACTOR = 0.75f; 17 18 // 存储数据的Entry数组,长度是2的幂。 19 // HashMap是采用拉链法实现的,每一个Entry本质上是一个单向链表 20 transient Entry[] table; 21 22 // HashMap的大小,它是HashMap保存的键值对的数量 23 transient int size; 24 25 // HashMap的阈值,用于判断是否需要调整HashMap的容量(threshold = 容量*加载因子) 26 int threshold; 27 28 // 加载因子实际大小 29 final float loadFactor; 30 31 // HashMap被改变的次数 32 transient volatile int modCount; 33 34 // 指定“容量大小”和“加载因子”的构造函数 35 public HashMap(int initialCapacity, float loadFactor) { 36 if (initialCapacity < 0) 37 throw new IllegalArgumentException("Illegal initial capacity: " + 38 initialCapacity); 39 // HashMap的最大容量只能是MAXIMUM_CAPACITY 40 if (initialCapacity > MAXIMUM_CAPACITY) 41 initialCapacity = MAXIMUM_CAPACITY; 42 if (loadFactor <= 0 || Float.isNaN(loadFactor)) 43 throw new IllegalArgumentException("Illegal load factor: " + 44 loadFactor); 45 46 // 找出“大于initialCapacity”的最小的2的幂 47 int capacity = 1; 48 while (capacity < initialCapacity) 49 capacity <<= 1; 50 51 // 设置“加载因子” 52 this.loadFactor = loadFactor; 53 // 设置“HashMap阈值”,当HashMap中存储数据的数量达到threshold时,就需要将HashMap的容量加倍。 54 threshold = (int)(capacity * loadFactor); 55 // 创建Entry数组,用来保存数据 56 table = new Entry[capacity]; 57 init(); 58 } 59 60 61 // 指定“容量大小”的构造函数 62 public HashMap(int initialCapacity) { 63 this(initialCapacity, DEFAULT_LOAD_FACTOR); 64 } 65 66 // 默认构造函数。 67 public HashMap() { 68 // 设置“加载因子” 69 this.loadFactor = DEFAULT_LOAD_FACTOR; 70 // 设置“HashMap阈值”,当HashMap中存储数据的数量达到threshold时,就需要将HashMap的容量加倍。 71 threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR); 72 // 创建Entry数组,用来保存数据 73 table = new Entry[DEFAULT_INITIAL_CAPACITY]; 74 init(); 75 } 76 77 // 包含“子Map”的构造函数 78 public HashMap(Map<? extends K, ? extends V> m) { 79 this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1, 80 DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR); 81 // 将m中的全部元素逐个添加到HashMap中 82 putAllForCreate(m); 83 } 84 85 static int hash(int h) { 86 h ^= (h >>> 20) ^ (h >>> 12); 87 return h ^ (h >>> 7) ^ (h >>> 4); 88 } 89 90 // 返回索引值 91 // h & (length-1)保证返回值的小于length 92 static int indexFor(int h, int length) { 93 return h & (length-1); 94 } 95 96 public int size() { 97 return size; 98 } 99 100 public boolean isEmpty() { 101 return size == 0; 102 } 103 104 // 获取key对应的value 105 public V get(Object key) { 106 if (key == null) 107 return getForNullKey(); 108 // 获取key的hash值 109 int hash = hash(key.hashCode()); 110 // 在“该hash值对应的链表”上查找“键值等于key”的元素 111 for (Entry<K,V> e = table[indexFor(hash, table.length)]; 112 e != null; 113 e = e.next) { 114 Object k; 115 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) 116 return e.value; 117 } 118 return null; 119 } 120 121 // 获取“key为null”的元素的值 122 // HashMap将“key为null”的元素存储在table[0]位置! 123 private V getForNullKey() { 124 for (Entry<K,V> e = table[0]; e != null; e = e.next) { 125 if (e.key == null) 126 return e.value; 127 } 128 return null; 129 } 130 131 // HashMap是否包含key 132 public boolean containsKey(Object key) { 133 return getEntry(key) != null; 134 } 135 136 // 返回“键为key”的键值对 137 final Entry<K,V> getEntry(Object key) { 138 // 获取哈希值 139 // HashMap将“key为null”的元素存储在table[0]位置,“key不为null”的则调用hash()计算哈希值 140 int hash = (key == null) ? 0 : hash(key.hashCode()); 141 // 在“该hash值对应的链表”上查找“键值等于key”的元素 142 for (Entry<K,V> e = table[indexFor(hash, table.length)]; 143 e != null; 144 e = e.next) { 145 Object k; 146 if (e.hash == hash && 147 ((k = e.key) == key || (key != null && key.equals(k)))) 148 return e; 149 } 150 return null; 151 } 152 153 // 将“key-value”添加到HashMap中 154 public V put(K key, V value) { 155 // 若“key为null”,则将该键值对添加到table[0]中。 156 if (key == null) 157 return putForNullKey(value); 158 // 若“key不为null”,则计算该key的哈希值,然后将其添加到该哈希值对应的链表中。 159 int hash = hash(key.hashCode()); 160 int i = indexFor(hash, table.length); 161 for (Entry<K,V> e = table[i]; e != null; e = e.next) { 162 Object k; 163 // 若“该key”对应的键值对已经存在,则用新的value取代旧的value。然后退出! 164 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { 165 V oldValue = e.value; 166 e.value = value; 167 e.recordAccess(this); 168 return oldValue; 169 } 170 } 171 172 // 若“该key”对应的键值对不存在,则将“key-value”添加到table中 173 modCount++; 174 addEntry(hash, key, value, i); 175 return null; 176 } 177 178 // putForNullKey()的作用是将“key为null”键值对添加到table[0]位置 179 private V putForNullKey(V value) { 180 for (Entry<K,V> e = table[0]; e != null; e = e.next) { 181 if (e.key == null) { 182 V oldValue = e.value; 183 e.value = value; 184 e.recordAccess(this); 185 return oldValue; 186 } 187 } 188 // 这里的完全不会被执行到! 189 modCount++; 190 addEntry(0, null, value, 0); 191 return null; 192 } 193 194 // 创建HashMap对应的“添加方法”, 195 // 它和put()不同。putForCreate()是内部方法,它被构造函数等调用,用来创建HashMap 196 // 而put()是对外提供的往HashMap中添加元素的方法。 197 private void putForCreate(K key, V value) { 198 int hash = (key == null) ? 0 : hash(key.hashCode()); 199 int i = indexFor(hash, table.length); 200 201 // 若该HashMap表中存在“键值等于key”的元素,则替换该元素的value值 202 for (Entry<K,V> e = table[i]; e != null; e = e.next) { 203 Object k; 204 if (e.hash == hash && 205 ((k = e.key) == key || (key != null && key.equals(k)))) { 206 e.value = value; 207 return; 208 } 209 } 210 211 // 若该HashMap表中不存在“键值等于key”的元素,则将该key-value添加到HashMap中 212 createEntry(hash, key, value, i); 213 } 214 215 // 将“m”中的全部元素都添加到HashMap中。 216 // 该方法被内部的构造HashMap的方法所调用。 217 private void putAllForCreate(Map<? extends K, ? extends V> m) { 218 // 利用迭代器将元素逐个添加到HashMap中 219 for (Iterator<? extends Map.Entry<? extends K, ? extends V>> i = m.entrySet().iterator(); i.hasNext(); ) { 220 Map.Entry<? extends K, ? extends V> e = i.next(); 221 putForCreate(e.getKey(), e.getValue()); 222 } 223 } 224 225 // 重新调整HashMap的大小,newCapacity是调整后的单位 226 void resize(int newCapacity) { 227 Entry[] oldTable = table; 228 int oldCapacity = oldTable.length; 229 if (oldCapacity == MAXIMUM_CAPACITY) { 230 threshold = Integer.MAX_VALUE; 231 return; 232 } 233 234 // 新建一个HashMap,将“旧HashMap”的全部元素添加到“新HashMap”中, 235 // 然后,将“新HashMap”赋值给“旧HashMap”。 236 Entry[] newTable = new Entry[newCapacity]; 237 transfer(newTable); 238 table = newTable; 239 threshold = (int)(newCapacity * loadFactor); 240 } 241 242 // 将HashMap中的全部元素都添加到newTable中 243 void transfer(Entry[] newTable) { 244 Entry[] src = table; 245 int newCapacity = newTable.length; 246 for (int j = 0; j < src.length; j++) { 247 Entry<K,V> e = src[j]; 248 if (e != null) { 249 src[j] = null; 250 do { 251 Entry<K,V> next = e.next; 252 int i = indexFor(e.hash, newCapacity); 253 e.next = newTable[i]; 254 newTable[i] = e; 255 e = next; 256 } while (e != null); 257 } 258 } 259 } 260 261 // 将"m"的全部元素都添加到HashMap中 262 public void putAll(Map<? extends K, ? extends V> m) { 263 // 有效性判断 264 int numKeysToBeAdded = m.size(); 265 if (numKeysToBeAdded == 0) 266 return; 267 268 // 计算容量是否足够, 269 // 若“当前实际容量 < 需要的容量”,则将容量x2。 270 if (numKeysToBeAdded > threshold) { 271 int targetCapacity = (int)(numKeysToBeAdded / loadFactor + 1); 272 if (targetCapacity > MAXIMUM_CAPACITY) 273 targetCapacity = MAXIMUM_CAPACITY; 274 int newCapacity = table.length; 275 while (newCapacity < targetCapacity) 276 newCapacity <<= 1; 277 if (newCapacity > table.length) 278 resize(newCapacity); 279 } 280 281 // 通过迭代器,将“m”中的元素逐个添加到HashMap中。 282 for (Iterator<? extends Map.Entry<? extends K, ? extends V>> i = m.entrySet().iterator(); i.hasNext(); ) { 283 Map.Entry<? extends K, ? extends V> e = i.next(); 284 put(e.getKey(), e.getValue()); 285 } 286 } 287 288 // 删除“键为key”元素 289 public V remove(Object key) { 290 Entry<K,V> e = removeEntryForKey(key); 291 return (e == null ? null : e.value); 292 } 293 294 // 删除“键为key”的元素 295 final Entry<K,V> removeEntryForKey(Object key) { 296 // 获取哈希值。若key为null,则哈希值为0;否则调用hash()进行计算 297 int hash = (key == null) ? 0 : hash(key.hashCode()); 298 int i = indexFor(hash, table.length); 299 Entry<K,V> prev = table[i]; 300 Entry<K,V> e = prev; 301 302 // 删除链表中“键为key”的元素 303 // 本质是“删除单向链表中的节点” 304 while (e != null) { 305 Entry<K,V> next = e.next; 306 Object k; 307 if (e.hash == hash && 308 ((k = e.key) == key || (key != null && key.equals(k)))) { 309 modCount++; 310 size--; 311 if (prev == e) 312 table[i] = next; 313 else 314 prev.next = next; 315 e.recordRemoval(this); 316 return e; 317 } 318 prev = e; 319 e = next; 320 } 321 322 return e; 323 } 324 325 // 删除“键值对” 326 final Entry<K,V> removeMapping(Object o) { 327 if (!(o instanceof Map.Entry)) 328 return null; 329 330 Map.Entry<K,V> entry = (Map.Entry<K,V>) o; 331 Object key = entry.getKey(); 332 int hash = (key == null) ? 0 : hash(key.hashCode()); 333 int i = indexFor(hash, table.length); 334 Entry<K,V> prev = table[i]; 335 Entry<K,V> e = prev; 336 337 // 删除链表中的“键值对e” 338 // 本质是“删除单向链表中的节点” 339 while (e != null) { 340 Entry<K,V> next = e.next; 341 if (e.hash == hash && e.equals(entry)) { 342 modCount++; 343 size--; 344 if (prev == e) 345 table[i] = next; 346 else 347 prev.next = next; 348 e.recordRemoval(this); 349 return e; 350 } 351 prev = e; 352 e = next; 353 } 354 355 return e; 356 } 357 358 // 清空HashMap,将所有的元素设为null 359 public void clear() { 360 modCount++; 361 Entry[] tab = table; 362 for (int i = 0; i < tab.length; i++) 363 tab[i] = null; 364 size = 0; 365 } 366 367 // 是否包含“值为value”的元素 368 public boolean containsValue(Object value) { 369 // 若“value为null”,则调用containsNullValue()查找 370 if (value == null) 371 return containsNullValue(); 372 373 // 若“value不为null”,则查找HashMap中是否有值为value的节点。 374 Entry[] tab = table; 375 for (int i = 0; i < tab.length ; i++) 376 for (Entry e = tab[i] ; e != null ; e = e.next) 377 if (value.equals(e.value)) 378 return true; 379 return false; 380 } 381 382 // 是否包含null值 383 private boolean containsNullValue() { 384 Entry[] tab = table; 385 for (int i = 0; i < tab.length ; i++) 386 for (Entry e = tab[i] ; e != null ; e = e.next) 387 if (e.value == null) 388 return true; 389 return false; 390 } 391 392 // 克隆一个HashMap,并返回Object对象 393 public Object clone() { 394 HashMap<K,V> result = null; 395 try { 396 result = (HashMap<K,V>)super.clone(); 397 } catch (CloneNotSupportedException e) { 398 // assert false; 399 } 400 result.table = new Entry[table.length]; 401 result.entrySet = null; 402 result.modCount = 0; 403 result.size = 0; 404 result.init(); 405 // 调用putAllForCreate()将全部元素添加到HashMap中 406 result.putAllForCreate(this); 407 408 return result; 409 } 410 411 // Entry是单向链表。 412 // 它是 “HashMap链式存储法”对应的链表。 413 // 它实现了Map.Entry 接口,即实现getKey(), getValue(), setValue(V value), equals(Object o), hashCode()这些函数 414 static class Entry<K,V> implements Map.Entry<K,V> { 415 final K key; 416 V value; 417 // 指向下一个节点 418 Entry<K,V> next; 419 final int hash; 420 421 // 构造函数。 422 // 输入参数包括"哈希值(h)", "键(k)", "值(v)", "下一节点(n)" 423 Entry(int h, K k, V v, Entry<K,V> n) { 424 value = v; 425 next = n; 426 key = k; 427 hash = h; 428 } 429 430 public final K getKey() { 431 return key; 432 } 433 434 public final V getValue() { 435 return value; 436 } 437 438 public final V setValue(V newValue) { 439 V oldValue = value; 440 value = newValue; 441 return oldValue; 442 } 443 444 // 判断两个Entry是否相等 445 // 若两个Entry的“key”和“value”都相等,则返回true。 446 // 否则,返回false 447 public final boolean equals(Object o) { 448 if (!(o instanceof Map.Entry)) 449 return false; 450 Map.Entry e = (Map.Entry)o; 451 Object k1 = getKey(); 452 Object k2 = e.getKey(); 453 if (k1 == k2 || (k1 != null && k1.equals(k2))) { 454 Object v1 = getValue(); 455 Object v2 = e.getValue(); 456 if (v1 == v2 || (v1 != null && v1.equals(v2))) 457 return true; 458 } 459 return false; 460 } 461 462 // 实现hashCode() 463 public final int hashCode() { 464 return (key==null ? 0 : key.hashCode()) ^ 465 (value==null ? 0 : value.hashCode()); 466 } 467 468 public final String toString() { 469 return getKey() + "=" + getValue(); 470 } 471 472 // 当向HashMap中添加元素时,绘调用recordAccess()。 473 // 这里不做任何处理 474 void recordAccess(HashMap<K,V> m) { 475 } 476 477 // 当从HashMap中删除元素时,绘调用recordRemoval()。 478 // 这里不做任何处理 479 void recordRemoval(HashMap<K,V> m) { 480 } 481 } 482 483 // 新增Entry。将“key-value”插入指定位置,bucketIndex是位置索引。 484 void addEntry(int hash, K key, V value, int bucketIndex) { 485 // 保存“bucketIndex”位置的值到“e”中 486 Entry<K,V> e = table[bucketIndex]; 487 // 设置“bucketIndex”位置的元素为“新Entry”, 488 // 设置“e”为“新Entry的下一个节点” 489 table[bucketIndex] = new Entry<K,V>(hash, key, value, e); 490 // 若HashMap的实际大小 不小于 “阈值”,则调整HashMap的大小 491 if (size++ >= threshold) 492 resize(2 * table.length); 493 } 494 495 // 创建Entry。将“key-value”插入指定位置,bucketIndex是位置索引。 496 // 它和addEntry的区别是: 497 // (01) addEntry()一般用在 新增Entry可能导致“HashMap的实际容量”超过“阈值”的情况下。 498 // 例如,我们新建一个HashMap,然后不断通过put()向HashMap中添加元素; 499 // put()是通过addEntry()新增Entry的。 500 // 在这种情况下,我们不知道何时“HashMap的实际容量”会超过“阈值”; 501 // 因此,需要调用addEntry() 502 // (02) createEntry() 一般用在 新增Entry不会导致“HashMap的实际容量”超过“阈值”的情况下。 503 // 例如,我们调用HashMap“带有Map”的构造函数,它绘将Map的全部元素添加到HashMap中; 504 // 但在添加之前,我们已经计算好“HashMap的容量和阈值”。也就是,可以确定“即使将Map中 505 // 的全部元素添加到HashMap中,都不会超过HashMap的阈值”。 506 // 此时,调用createEntry()即可。 507 void createEntry(int hash, K key, V value, int bucketIndex) { 508 // 保存“bucketIndex”位置的值到“e”中 509 Entry<K,V> e = table[bucketIndex]; 510 // 设置“bucketIndex”位置的元素为“新Entry”, 511 // 设置“e”为“新Entry的下一个节点” 512 table[bucketIndex] = new Entry<K,V>(hash, key, value, e); 513 size++; 514 } 515 516 // HashIterator是HashMap迭代器的抽象出来的父类,实现了公共了函数。 517 // 它包含“key迭代器(KeyIterator)”、“Value迭代器(ValueIterator)”和“Entry迭代器(EntryIterator)”3个子类。 518 private abstract class HashIterator<E> implements Iterator<E> { 519 // 下一个元素 520 Entry<K,V> next; 521 // expectedModCount用于实现fast-fail机制。 522 int expectedModCount; 523 // 当前索引 524 int index; 525 // 当前元素 526 Entry<K,V> current; 527 528 HashIterator() { 529 expectedModCount = modCount; 530 if (size > 0) { // advance to first entry 531 Entry[] t = table; 532 // 将next指向table中第一个不为null的元素。 533 // 这里利用了index的初始值为0,从0开始依次向后遍历,直到找到不为null的元素就退出循环。 534 while (index < t.length && (next = t[index++]) == null) 535 ; 536 } 537 } 538 539 public final boolean hasNext() { 540 return next != null; 541 } 542 543 // 获取下一个元素 544 final Entry<K,V> nextEntry() { 545 if (modCount != expectedModCount) 546 throw new ConcurrentModificationException(); 547 Entry<K,V> e = next; 548 if (e == null) 549 throw new NoSuchElementException(); 550 551 // 注意!!! 552 // 一个Entry就是一个单向链表 553 // 若该Entry的下一个节点不为空,就将next指向下一个节点; 554 // 否则,将next指向下一个链表(也是下一个Entry)的不为null的节点。 555 if ((next = e.next) == null) { 556 Entry[] t = table; 557 while (index < t.length && (next = t[index++]) == null) 558 ; 559 } 560 current = e; 561 return e; 562 } 563 564 // 删除当前元素 565 public void remove() { 566 if (current == null) 567 throw new IllegalStateException(); 568 if (modCount != expectedModCount) 569 throw new ConcurrentModificationException(); 570 Object k = current.key; 571 current = null; 572 HashMap.this.removeEntryForKey(k); 573 expectedModCount = modCount; 574 } 575 576 } 577 578 // value的迭代器 579 private final class ValueIterator extends HashIterator<V> { 580 public V next() { 581 return nextEntry().value; 582 } 583 } 584 585 // key的迭代器 586 private final class KeyIterator extends HashIterator<K> { 587 public K next() { 588 return nextEntry().getKey(); 589 } 590 } 591 592 // Entry的迭代器 593 private final class EntryIterator extends HashIterator<Map.Entry<K,V>> { 594 public Map.Entry<K,V> next() { 595 return nextEntry(); 596 } 597 } 598 599 // 返回一个“key迭代器” 600 Iterator<K> newKeyIterator() { 601 return new KeyIterator(); 602 } 603 // 返回一个“value迭代器” 604 Iterator<V> newValueIterator() { 605 return new ValueIterator(); 606 } 607 // 返回一个“entry迭代器” 608 Iterator<Map.Entry<K,V>> newEntryIterator() { 609 return new EntryIterator(); 610 } 611 612 // HashMap的Entry对应的集合 613 private transient Set<Map.Entry<K,V>> entrySet = null; 614 615 // 返回“key的集合”,实际上返回一个“KeySet对象” 616 public Set<K> keySet() { 617 Set<K> ks = keySet; 618 return (ks != null ? ks : (keySet = new KeySet())); 619 } 620 621 // Key对应的集合 622 // KeySet继承于AbstractSet,说明该集合中没有重复的Key。 623 private final class KeySet extends AbstractSet<K> { 624 public Iterator<K> iterator() { 625 return newKeyIterator(); 626 } 627 public int size() { 628 return size; 629 } 630 public boolean contains(Object o) { 631 return containsKey(o); 632 } 633 public boolean remove(Object o) { 634 return HashMap.this.removeEntryForKey(o) != null; 635 } 636 public void clear() { 637 HashMap.this.clear(); 638 } 639 } 640 641 // 返回“value集合”,实际上返回的是一个Values对象 642 public Collection<V> values() { 643 Collection<V> vs = values; 644 return (vs != null ? vs : (values = new Values())); 645 } 646 647 // “value集合” 648 // Values继承于AbstractCollection,不同于“KeySet继承于AbstractSet”, 649 // Values中的元素能够重复。因为不同的key可以指向相同的value。 650 private final class Values extends AbstractCollection<V> { 651 public Iterator<V> iterator() { 652 return newValueIterator(); 653 } 654 public int size() { 655 return size; 656 } 657 public boolean contains(Object o) { 658 return containsValue(o); 659 } 660 public void clear() { 661 HashMap.this.clear(); 662 } 663 } 664 665 // 返回“HashMap的Entry集合” 666 public Set<Map.Entry<K,V>> entrySet() { 667 return entrySet0(); 668 } 669 670 // 返回“HashMap的Entry集合”,它实际是返回一个EntrySet对象 671 private Set<Map.Entry<K,V>> entrySet0() { 672 Set<Map.Entry<K,V>> es = entrySet; 673 return es != null ? es : (entrySet = new EntrySet()); 674 } 675 676 // EntrySet对应的集合 677 // EntrySet继承于AbstractSet,说明该集合中没有重复的EntrySet。 678 private final class EntrySet extends AbstractSet<Map.Entry<K,V>> { 679 public Iterator<Map.Entry<K,V>> iterator() { 680 return newEntryIterator(); 681 } 682 public boolean contains(Object o) { 683 if (!(o instanceof Map.Entry)) 684 return false; 685 Map.Entry<K,V> e = (Map.Entry<K,V>) o; 686 Entry<K,V> candidate = getEntry(e.getKey()); 687 return candidate != null && candidate.equals(e); 688 } 689 public boolean remove(Object o) { 690 return removeMapping(o) != null; 691 } 692 public int size() { 693 return size; 694 } 695 public void clear() { 696 HashMap.this.clear(); 697 } 698 } 699 700 // java.io.Serializable的写入函数 701 // 将HashMap的“总的容量,实际容量,所有的Entry”都写入到输出流中 702 private void writeObject(java.io.ObjectOutputStream s) 703 throws IOException 704 { 705 Iterator<Map.Entry<K,V>> i = 706 (size > 0) ? entrySet0().iterator() : null; 707 708 // Write out the threshold, loadfactor, and any hidden stuff 709 s.defaultWriteObject(); 710 711 // Write out number of buckets 712 s.writeInt(table.length); 713 714 // Write out size (number of Mappings) 715 s.writeInt(size); 716 717 // Write out keys and values (alternating) 718 if (i != null) { 719 while (i.hasNext()) { 720 Map.Entry<K,V> e = i.next(); 721 s.writeObject(e.getKey()); 722 s.writeObject(e.getValue()); 723 } 724 } 725 } 726 727 728 private static final long serialVersionUID = 362498820763181265L; 729 730 // java.io.Serializable的读取函数:根据写入方式读出 731 // 将HashMap的“总的容量,实际容量,所有的Entry”依次读出 732 private void readObject(java.io.ObjectInputStream s) 733 throws IOException, ClassNotFoundException 734 { 735 // Read in the threshold, loadfactor, and any hidden stuff 736 s.defaultReadObject(); 737 738 // Read in number of buckets and allocate the bucket array; 739 int numBuckets = s.readInt(); 740 table = new Entry[numBuckets]; 741 742 init(); // Give subclass a chance to do its thing. 743 744 // Read in size (number of Mappings) 745 int size = s.readInt(); 746 747 // Read the keys and values, and put the mappings in the HashMap 748 for (int i=0; i<size; i++) { 749 K key = (K) s.readObject(); 750 V value = (V) s.readObject(); 751 putForCreate(key, value); 752 } 753 } 754 755 // 返回“HashMap总的容量” 756 int capacity() { return table.length; } 757 // 返回“HashMap的加载因子” 758 float loadFactor() { return loadFactor; } 759 }
但是在jdk1.8之后,HashMap加入了红黑树机制,在一个单向链表的节点大于8的情况下,就把这个链表转换成红黑树。
同时让我们看看HashMap和Map之间的关系:
1 (1) HashMap继承于AbstractMap类,实现了Map接口。Map是"key-value键值对"接口,AbstractMap实现了"键值对"的通用函数接口。 2 (2) HashMap是通过"拉链法"实现的哈希表。它包括几个重要的成员变量:table, size, threshold, loadFactor, modCount。 3 table是一个Entry[]数组类型,而Entry实际上就是一个单向链表。哈希表的"key-value键值对"都是存储在Entry数组中的。 4 size是HashMap的大小,它是HashMap保存的键值对的数量。 5 threshold是HashMap的阈值,用于判断是否需要调整HashMap的容量。
threshold的值="容量*加载因子",当HashMap中存储数据的数量达到threshold时,就需要将HashMap的容量加倍。 6 loadFactor就是加载因子。 7 modCount是用来实现fail-fast机制的。java.util包下面的所有的集合类都是快速失败(fail-fast)的,
而java.util.concurrent包下面的所有的类都是安全失败(fail-safe)的。
快速失败的迭代器会抛出ConcurrentModificationException异常,而安全失败的迭代器永远不会抛出这样的异常。 8 当多个线程对同一个集合进行操作的时候,某线程访问集合的过程中,
该集合的内容被其他线程所改变(即其它线程通过add、remove、clear等方法,改变了modCount的值);
这时,就会抛出ConcurrentModificationException异常,产生fail-fast事件。 9 fail-fast机制,是一种错误检测机制。它只能被用来检测错误,因为JDK并不保证fail-fast机制一定会发生。
若在多线程环境下使用fail-fast机制的集合,建议使用“java.util.concurrent包下的类”去取代“java.util包下的类”。
下面我们看看在jdk1.8之中的HashMap源码实现方式:
1 /* 2 * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved. 3 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11 * 12 * 13 * 14 * 15 * 16 * 17 * 18 * 19 * 20 * 21 * 22 * 23 * 24 */ 25 26 package java.util; 27 28 import java.io.IOException; 29 import java.io.InvalidObjectException; 30 import java.io.Serializable; 31 import java.lang.reflect.ParameterizedType; 32 import java.lang.reflect.Type; 33 import java.util.function.BiConsumer; 34 import java.util.function.BiFunction; 35 import java.util.function.Consumer; 36 import java.util.function.Function; 37 38 /** 39 * Hash table based implementation of the <tt>Map</tt> interface. This 40 * implementation provides all of the optional map operations, and permits 41 * <tt>null</tt> values and the <tt>null</tt> key. (The <tt>HashMap</tt> 42 * class is roughly equivalent to <tt>Hashtable</tt>, except that it is 43 * unsynchronized and permits nulls.) This class makes no guarantees as to 44 * the order of the map; in particular, it does not guarantee that the order 45 * will remain constant over time. 46 * 47 * <p>This implementation provides constant-time performance for the basic 48 * operations (<tt>get</tt> and <tt>put</tt>), assuming the hash function 49 * disperses the elements properly among the buckets. Iteration over 50 * collection views requires time proportional to the "capacity" of the 51 * <tt>HashMap</tt> instance (the number of buckets) plus its size (the number 52 * of key-value mappings). Thus, it's very important not to set the initial 53 * capacity too high (or the load factor too low) if iteration performance is 54 * important. 55 * 56 * <p>An instance of <tt>HashMap</tt> has two parameters that affect its 57 * performance: <i>initial capacity</i> and <i>load factor</i>. The 58 * <i>capacity</i> is the number of buckets in the hash table, and the initial 59 * capacity is simply the capacity at the time the hash table is created. The 60 * <i>load factor</i> is a measure of how full the hash table is allowed to 61 * get before its capacity is automatically increased. When the number of 62 * entries in the hash table exceeds the product of the load factor and the 63 * current capacity, the hash table is <i>rehashed</i> (that is, internal data 64 * structures are rebuilt) so that the hash table has approximately twice the 65 * number of buckets. 66 * 67 * <p>As a general rule, the default load factor (.75) offers a good 68 * tradeoff between time and space costs. Higher values decrease the 69 * space overhead but increase the lookup cost (reflected in most of 70 * the operations of the <tt>HashMap</tt> class, including 71 * <tt>get</tt> and <tt>put</tt>). The expected number of entries in 72 * the map and its load factor should be taken into account when 73 * setting its initial capacity, so as to minimize the number of 74 * rehash operations. If the initial capacity is greater than the 75 * maximum number of entries divided by the load factor, no rehash 76 * operations will ever occur. 77 * 78 * <p>If many mappings are to be stored in a <tt>HashMap</tt> 79 * instance, creating it with a sufficiently large capacity will allow 80 * the mappings to be stored more efficiently than letting it perform 81 * automatic rehashing as needed to grow the table. Note that using 82 * many keys with the same {@code hashCode()} is a sure way to slow 83 * down performance of any hash table. To ameliorate impact, when keys 84 * are {@link Comparable}, this class may use comparison order among 85 * keys to help break ties. 86 * 87 * <p><strong>Note that this implementation is not synchronized.</strong> 88 * If multiple threads access a hash map concurrently, and at least one of 89 * the threads modifies the map structurally, it <i>must</i> be 90 * synchronized externally. (A structural modification is any operation 91 * that adds or deletes one or more mappings; merely changing the value 92 * associated with a key that an instance already contains is not a 93 * structural modification.) This is typically accomplished by 94 * synchronizing on some object that naturally encapsulates the map. 95 * 96 * If no such object exists, the map should be "wrapped" using the 97 * {@link Collections#synchronizedMap Collections.synchronizedMap} 98 * method. This is best done at creation time, to prevent accidental 99 * unsynchronized access to the map:<pre> 100 * Map m = Collections.synchronizedMap(new HashMap(...));</pre> 101 * 102 * <p>The iterators returned by all of this class's "collection view methods" 103 * are <i>fail-fast</i>: if the map is structurally modified at any time after 104 * the iterator is created, in any way except through the iterator's own 105 * <tt>remove</tt> method, the iterator will throw a 106 * {@link ConcurrentModificationException}. Thus, in the face of concurrent 107 * modification, the iterator fails quickly and cleanly, rather than risking 108 * arbitrary, non-deterministic behavior at an undetermined time in the 109 * future. 110 * 111 * <p>Note that the fail-fast behavior of an iterator cannot be guaranteed 112 * as it is, generally speaking, impossible to make any hard guarantees in the 113 * presence of unsynchronized concurrent modification. Fail-fast iterators 114 * throw <tt>ConcurrentModificationException</tt> on a best-effort basis. 115 * Therefore, it would be wrong to write a program that depended on this 116 * exception for its correctness: <i>the fail-fast behavior of iterators 117 * should be used only to detect bugs.</i> 118 * 119 * <p>This class is a member of the 120 * <a href="{@docRoot}/../technotes/guides/collections/index.html"> 121 * Java Collections Framework</a>. 122 * 123 * @param <K> the type of keys maintained by this map 124 * @param <V> the type of mapped values 125 * 126 * @author Doug Lea 127 * @author Josh Bloch 128 * @author Arthur van Hoff 129 * @author Neal Gafter 130 * @see Object#hashCode() 131 * @see Collection 132 * @see Map 133 * @see TreeMap 134 * @see Hashtable 135 * @since 1.2 136 */ 137 public class HashMap<K,V> extends AbstractMap<K,V> 138 implements Map<K,V>, Cloneable, Serializable { 139 140 private static final long serialVersionUID = 362498820763181265L; 141 142 /* 143 * Implementation notes. 144 * 145 * This map usually acts as a binned (bucketed) hash table, but 146 * when bins get too large, they are transformed into bins of 147 * TreeNodes, each structured similarly to those in 148 * java.util.TreeMap. Most methods try to use normal bins, but 149 * relay to TreeNode methods when applicable (simply by checking 150 * instanceof a node). Bins of TreeNodes may be traversed and 151 * used like any others, but additionally support faster lookup 152 * when overpopulated. However, since the vast majority of bins in 153 * normal use are not overpopulated, checking for existence of 154 * tree bins may be delayed in the course of table methods. 155 * 156 * Tree bins (i.e., bins whose elements are all TreeNodes) are 157 * ordered primarily by hashCode, but in the case of ties, if two 158 * elements are of the same "class C implements Comparable<C>", 159 * type then their compareTo method is used for ordering. (We 160 * conservatively check generic types via reflection to validate 161 * this -- see method comparableClassFor). The added complexity 162 * of tree bins is worthwhile in providing worst-case O(log n) 163 * operations when keys either have distinct hashes or are 164 * orderable, Thus, performance degrades gracefully under 165 * accidental or malicious usages in which hashCode() methods 166 * return values that are poorly distributed, as well as those in 167 * which many keys share a hashCode, so long as they are also 168 * Comparable. (If neither of these apply, we may waste about a 169 * factor of two in time and space compared to taking no 170 * precautions. But the only known cases stem from poor user 171 * programming practices that are already so slow that this makes 172 * little difference.) 173 * 174 * Because TreeNodes are about twice the size of regular nodes, we 175 * use them only when bins contain enough nodes to warrant use 176 * (see TREEIFY_THRESHOLD). And when they become too small (due to 177 * removal or resizing) they are converted back to plain bins. In 178 * usages with well-distributed user hashCodes, tree bins are 179 * rarely used. Ideally, under random hashCodes, the frequency of 180 * nodes in bins follows a Poisson distribution 181 * (http://en.wikipedia.org/wiki/Poisson_distribution) with a 182 * parameter of about 0.5 on average for the default resizing 183 * threshold of 0.75, although with a large variance because of 184 * resizing granularity. Ignoring variance, the expected 185 * occurrences of list size k are (exp(-0.5) * pow(0.5, k) / 186 * factorial(k)). The first values are: 187 * 188 * 0: 0.60653066 189 * 1: 0.30326533 190 * 2: 0.07581633 191 * 3: 0.01263606 192 * 4: 0.00157952 193 * 5: 0.00015795 194 * 6: 0.00001316 195 * 7: 0.00000094 196 * 8: 0.00000006 197 * more: less than 1 in ten million 198 * 199 * The root of a tree bin is normally its first node. However, 200 * sometimes (currently only upon Iterator.remove), the root might 201 * be elsewhere, but can be recovered following parent links 202 * (method TreeNode.root()). 203 * 204 * All applicable internal methods accept a hash code as an 205 * argument (as normally supplied from a public method), allowing 206 * them to call each other without recomputing user hashCodes. 207 * Most internal methods also accept a "tab" argument, that is 208 * normally the current table, but may be a new or old one when 209 * resizing or converting. 210 * 211 * When bin lists are treeified, split, or untreeified, we keep 212 * them in the same relative access/traversal order (i.e., field 213 * Node.next) to better preserve locality, and to slightly 214 * simplify handling of splits and traversals that invoke 215 * iterator.remove. When using comparators on insertion, to keep a 216 * total ordering (or as close as is required here) across 217 * rebalancings, we compare classes and identityHashCodes as 218 * tie-breakers. 219 * 220 * The use and transitions among plain vs tree modes is 221 * complicated by the existence of subclass LinkedHashMap. See 222 * below for hook methods defined to be invoked upon insertion, 223 * removal and access that allow LinkedHashMap internals to 224 * otherwise remain independent of these mechanics. (This also 225 * requires that a map instance be passed to some utility methods 226 * that may create new nodes.) 227 * 228 * The concurrent-programming-like SSA-based coding style helps 229 * avoid aliasing errors amid all of the twisty pointer operations. 230 */ 231 232 /** 233 * The default initial capacity - MUST be a power of two. 234 */ 235 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 236 237 /** 238 * The maximum capacity, used if a higher value is implicitly specified 239 * by either of the constructors with arguments. 240 * MUST be a power of two <= 1<<30. 241 */ 242 static final int MAXIMUM_CAPACITY = 1 << 30; 243 244 /** 245 * The load factor used when none specified in constructor. 246 */ 247 static final float DEFAULT_LOAD_FACTOR = 0.75f; 248 249 /** 250 * The bin count threshold for using a tree rather than list for a 251 * bin. Bins are converted to trees when adding an element to a 252 * bin with at least this many nodes. The value must be greater 253 * than 2 and should be at least 8 to mesh with assumptions in 254 * tree removal about conversion back to plain bins upon 255 * shrinkage. 256 */ 257 static final int TREEIFY_THRESHOLD = 8; 258 259 /** 260 * The bin count threshold for untreeifying a (split) bin during a 261 * resize operation. Should be less than TREEIFY_THRESHOLD, and at 262 * most 6 to mesh with shrinkage detection under removal. 263 */ 264 static final int UNTREEIFY_THRESHOLD = 6; 265 266 /** 267 * The smallest table capacity for which bins may be treeified. 268 * (Otherwise the table is resized if too many nodes in a bin.) 269 * Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts 270 * between resizing and treeification thresholds. 271 */ 272 static final int MIN_TREEIFY_CAPACITY = 64; 273 274 /** 275 * Basic hash bin node, used for most entries. (See below for 276 * TreeNode subclass, and in LinkedHashMap for its Entry subclass.) 277 */ 278 static class Node<K,V> implements Map.Entry<K,V> { 279 final int hash; 280 final K key; 281 V value; 282 Node<K,V> next; 283 284 Node(int hash, K key, V value, Node<K,V> next) { 285 this.hash = hash; 286 this.key = key; 287 this.value = value; 288 this.next = next; 289 } 290 291 public final K getKey() { return key; } 292 public final V getValue() { return value; } 293 public final String toString() { return key + "=" + value; } 294 295 public final int hashCode() { 296 return Objects.hashCode(key) ^ Objects.hashCode(value); 297 } 298 299 public final V setValue(V newValue) { 300 V oldValue = value; 301 value = newValue; 302 return oldValue; 303 } 304 305 public final boolean equals(Object o) { 306 if (o == this) 307 return true; 308 if (o instanceof Map.Entry) { 309 Map.Entry<?,?> e = (Map.Entry<?,?>)o; 310 if (Objects.equals(key, e.getKey()) && 311 Objects.equals(value, e.getValue())) 312 return true; 313 } 314 return false; 315 } 316 } 317 318 /* ---------------- Static utilities -------------- */ 319 320 /** 321 * Computes key.hashCode() and spreads (XORs) higher bits of hash 322 * to lower. Because the table uses power-of-two masking, sets of 323 * hashes that vary only in bits above the current mask will 324 * always collide. (Among known examples are sets of Float keys 325 * holding consecutive whole numbers in small tables.) So we 326 * apply a transform that spreads the impact of higher bits 327 * downward. There is a tradeoff between speed, utility, and 328 * quality of bit-spreading. Because many common sets of hashes 329 * are already reasonably distributed (so don't benefit from 330 * spreading), and because we use trees to handle large sets of 331 * collisions in bins, we just XOR some shifted bits in the 332 * cheapest possible way to reduce systematic lossage, as well as 333 * to incorporate impact of the highest bits that would otherwise 334 * never be used in index calculations because of table bounds. 335 */ 336 static final int hash(Object key) { 337 int h; 338 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); 339 } 340 341 /** 342 * Returns x's Class if it is of the form "class C implements 343 * Comparable<C>", else null. 344 */ 345 static Class<?> comparableClassFor(Object x) { 346 if (x instanceof Comparable) { 347 Class<?> c; Type[] ts, as; Type t; ParameterizedType p; 348 if ((c = x.getClass()) == String.class) // bypass checks 349 return c; 350 if ((ts = c.getGenericInterfaces()) != null) { 351 for (int i = 0; i < ts.length; ++i) { 352 if (((t = ts[i]) instanceof ParameterizedType) && 353 ((p = (ParameterizedType)t).getRawType() == 354 Comparable.class) && 355 (as = p.getActualTypeArguments()) != null && 356 as.length == 1 && as[0] == c) // type arg is c 357 return c; 358 } 359 } 360 } 361 return null; 362 } 363 364 /** 365 * Returns k.compareTo(x) if x matches kc (k's screened comparable 366 * class), else 0. 367 */ 368 @SuppressWarnings({"rawtypes","unchecked"}) // for cast to Comparable 369 static int compareComparables(Class<?> kc, Object k, Object x) { 370 return (x == null || x.getClass() != kc ? 0 : 371 ((Comparable)k).compareTo(x)); 372 } 373 374 /** 375 * Returns a power of two size for the given target capacity. 376 */ 377 static final int tableSizeFor(int cap) { 378 int n = cap - 1; 379 n |= n >>> 1; 380 n |= n >>> 2; 381 n |= n >>> 4; 382 n |= n >>> 8; 383 n |= n >>> 16; 384 return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; 385 } 386 387 /* ---------------- Fields -------------- */ 388 389 /** 390 * The table, initialized on first use, and resized as 391 * necessary. When allocated, length is always a power of two. 392 * (We also tolerate length zero in some operations to allow 393 * bootstrapping mechanics that are currently not needed.) 394 */ 395 transient Node<K,V>[] table; 396 397 /** 398 * Holds cached entrySet(). Note that AbstractMap fields are used 399 * for keySet() and values(). 400 */ 401 transient Set<Map.Entry<K,V>> entrySet; 402 403 /** 404 * The number of key-value mappings contained in this map. 405 */ 406 transient int size; 407 408 /** 409 * The number of times this HashMap has been structurally modified 410 * Structural modifications are those that change the number of mappings in 411 * the HashMap or otherwise modify its internal structure (e.g., 412 * rehash). This field is used to make iterators on Collection-views of 413 * the HashMap fail-fast. (See ConcurrentModificationException). 414 */ 415 transient int modCount; 416 417 /** 418 * The next size value at which to resize (capacity * load factor). 419 * 420 * @serial 421 */ 422 // (The javadoc description is true upon serialization. 423 // Additionally, if the table array has not been allocated, this 424 // field holds the initial array capacity, or zero signifying 425 // DEFAULT_INITIAL_CAPACITY.) 426 int threshold; 427 428 /** 429 * The load factor for the hash table. 430 * 431 * @serial 432 */ 433 final float loadFactor; 434 435 /* ---------------- Public operations -------------- */ 436 437 /** 438 * Constructs an empty <tt>HashMap</tt> with the specified initial 439 * capacity and load factor. 440 * 441 * @param initialCapacity the initial capacity 442 * @param loadFactor the load factor 443 * @throws IllegalArgumentException if the initial capacity is negative 444 * or the load factor is nonpositive 445 */ 446 public HashMap(int initialCapacity, float loadFactor) { 447 if (initialCapacity < 0) 448 throw new IllegalArgumentException("Illegal initial capacity: " + 449 initialCapacity); 450 if (initialCapacity > MAXIMUM_CAPACITY) 451 initialCapacity = MAXIMUM_CAPACITY; 452 if (loadFactor <= 0 || Float.isNaN(loadFactor)) 453 throw new IllegalArgumentException("Illegal load factor: " + 454 loadFactor); 455 this.loadFactor = loadFactor; 456 this.threshold = tableSizeFor(initialCapacity); 457 } 458 459 /** 460 * Constructs an empty <tt>HashMap</tt> with the specified initial 461 * capacity and the default load factor (0.75). 462 * 463 * @param initialCapacity the initial capacity. 464 * @throws IllegalArgumentException if the initial capacity is negative. 465 */ 466 public HashMap(int initialCapacity) { 467 this(initialCapacity, DEFAULT_LOAD_FACTOR); 468 } 469 470 /** 471 * Constructs an empty <tt>HashMap</tt> with the default initial capacity 472 * (16) and the default load factor (0.75). 473 */ 474 public HashMap() { 475 this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted 476 } 477 478 /** 479 * Constructs a new <tt>HashMap</tt> with the same mappings as the 480 * specified <tt>Map</tt>. The <tt>HashMap</tt> is created with 481 * default load factor (0.75) and an initial capacity sufficient to 482 * hold the mappings in the specified <tt>Map</tt>. 483 * 484 * @param m the map whose mappings are to be placed in this map 485 * @throws NullPointerException if the specified map is null 486 */ 487 public HashMap(Map<? extends K, ? extends V> m) { 488 this.loadFactor = DEFAULT_LOAD_FACTOR; 489 putMapEntries(m, false); 490 } 491 492 /** 493 * Implements Map.putAll and Map constructor 494 * 495 * @param m the map 496 * @param evict false when initially constructing this map, else 497 * true (relayed to method afterNodeInsertion). 498 */ 499 final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) { 500 int s = m.size(); 501 if (s > 0) { 502 if (table == null) { // pre-size 503 float ft = ((float)s / loadFactor) + 1.0F; 504 int t = ((ft < (float)MAXIMUM_CAPACITY) ? 505 (int)ft : MAXIMUM_CAPACITY); 506 if (t > threshold) 507 threshold = tableSizeFor(t); 508 } 509 else if (s > threshold) 510 resize(); 511 for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) { 512 K key = e.getKey(); 513 V value = e.getValue(); 514 putVal(hash(key), key, value, false, evict); 515 } 516 } 517 } 518 519 /** 520 * Returns the number of key-value mappings in this map. 521 * 522 * @return the number of key-value mappings in this map 523 */ 524 public int size() { 525 return size; 526 } 527 528 /** 529 * Returns <tt>true</tt> if this map contains no key-value mappings. 530 * 531 * @return <tt>true</tt> if this map contains no key-value mappings 532 */ 533 public boolean isEmpty() { 534 return size == 0; 535 } 536 537 /** 538 * Returns the value to which the specified key is mapped, 539 * or {@code null} if this map contains no mapping for the key. 540 * 541 * <p>More formally, if this map contains a mapping from a key 542 * {@code k} to a value {@code v} such that {@code (key==null ? k==null : 543 * key.equals(k))}, then this method returns {@code v}; otherwise 544 * it returns {@code null}. (There can be at most one such mapping.) 545 * 546 * <p>A return value of {@code null} does not <i>necessarily</i> 547 * indicate that the map contains no mapping for the key; it's also 548 * possible that the map explicitly maps the key to {@code null}. 549 * The {@link #containsKey containsKey} operation may be used to 550 * distinguish these two cases. 551 * 552 * @see #put(Object, Object) 553 */ 554 public V get(Object key) { 555 Node<K,V> e; 556 return (e = getNode(hash(key), key)) == null ? null : e.value; 557 } 558 559 /** 560 * Implements Map.get and related methods 561 * 562 * @param hash hash for key 563 * @param key the key 564 * @return the node, or null if none 565 */ 566 final Node<K,V> getNode(int hash, Object key) { 567 Node<K,V>[] tab; Node<K,V> first, e; int n; K k; 568 if ((tab = table) != null && (n = tab.length) > 0 && 569 (first = tab[(n - 1) & hash]) != null) { 570 if (first.hash == hash && // always check first node 571 ((k = first.key) == key || (key != null && key.equals(k)))) 572 return first; 573 if ((e = first.next) != null) { 574 if (first instanceof TreeNode) 575 return ((TreeNode<K,V>)first).getTreeNode(hash, key); 576 do { 577 if (e.hash == hash && 578 ((k = e.key) == key || (key != null && key.equals(k)))) 579 return e; 580 } while ((e = e.next) != null); 581 } 582 } 583 return null; 584 } 585 586 /** 587 * Returns <tt>true</tt> if this map contains a mapping for the 588 * specified key. 589 * 590 * @param key The key whose presence in this map is to be tested 591 * @return <tt>true</tt> if this map contains a mapping for the specified 592 * key. 593 */ 594 public boolean containsKey(Object key) { 595 return getNode(hash(key), key) != null; 596 } 597 598 /** 599 * Associates the specified value with the specified key in this map. 600 * If the map previously contained a mapping for the key, the old 601 * value is replaced. 602 * 603 * @param key key with which the specified value is to be associated 604 * @param value value to be associated with the specified key 605 * @return the previous value associated with <tt>key</tt>, or 606 * <tt>null</tt> if there was no mapping for <tt>key</tt>. 607 * (A <tt>null</tt> return can also indicate that the map 608 * previously associated <tt>null</tt> with <tt>key</tt>.) 609 */ 610 public V put(K key, V value) { 611 return putVal(hash(key), key, value, false, true); 612 } 613 614 /** 615 * Implements Map.put and related methods 616 * 617 * @param hash hash for key 618 * @param key the key 619 * @param value the value to put 620 * @param onlyIfAbsent if true, don't change existing value 621 * @param evict if false, the table is in creation mode. 622 * @return previous value, or null if none 623 */ 624 final V putVal(int hash, K key, V value, boolean onlyIfAbsent, 625 boolean evict) { 626 Node<K,V>[] tab; Node<K,V> p; int n, i; 627 if ((tab = table) == null || (n = tab.length) == 0) 628 n = (tab = resize()).length; 629 if ((p = tab[i = (n - 1) & hash]) == null) 630 tab[i] = newNode(hash, key, value, null); 631 else { 632 Node<K,V> e; K k; 633 if (p.hash == hash && 634 ((k = p.key) == key || (key != null && key.equals(k)))) 635 e = p; 636 else if (p instanceof TreeNode) 637 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); 638 else { 639 for (int binCount = 0; ; ++binCount) { 640 if ((e = p.next) == null) { 641 p.next = newNode(hash, key, value, null); 642 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st 643 treeifyBin(tab, hash); 644 break; 645 } 646 if (e.hash == hash && 647 ((k = e.key) == key || (key != null && key.equals(k)))) 648 break; 649 p = e; 650 } 651 } 652 if (e != null) { // existing mapping for key 653 V oldValue = e.value; 654 if (!onlyIfAbsent || oldValue == null) 655 e.value = value; 656 afterNodeAccess(e); 657 return oldValue; 658 } 659 } 660 ++modCount; 661 if (++size > threshold) 662 resize(); 663 afterNodeInsertion(evict); 664 return null; 665 } 666 667 /** 668 * Initializes or doubles table size. If null, allocates in 669 * accord with initial capacity target held in field threshold. 670 * Otherwise, because we are using power-of-two expansion, the 671 * elements from each bin must either stay at same index, or move 672 * with a power of two offset in the new table. 673 * 674 * @return the table 675 */ 676 final Node<K,V>[] resize() { 677 Node<K,V>[] oldTab = table; 678 int oldCap = (oldTab == null) ? 0 : oldTab.length; 679 int oldThr = threshold; 680 int newCap, newThr = 0; 681 if (oldCap > 0) { 682 if (oldCap >= MAXIMUM_CAPACITY) { 683 threshold = Integer.MAX_VALUE; 684 return oldTab; 685 } 686 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && 687 oldCap >= DEFAULT_INITIAL_CAPACITY) 688 newThr = oldThr << 1; // double threshold 689 } 690 else if (oldThr > 0) // initial capacity was placed in threshold 691 newCap = oldThr; 692 else { // zero initial threshold signifies using defaults 693 newCap = DEFAULT_INITIAL_CAPACITY; 694 newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); 695 } 696 if (newThr == 0) { 697 float ft = (float)newCap * loadFactor; 698 newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? 699 (int)ft : Integer.MAX_VALUE); 700 } 701 threshold = newThr; 702 @SuppressWarnings({"rawtypes","unchecked"}) 703 Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; 704 table = newTab; 705 if (oldTab != null) { 706 for (int j = 0; j < oldCap; ++j) { 707 Node<K,V> e; 708 if ((e = oldTab[j]) != null) { 709 oldTab[j] = null; 710 if (e.next == null) 711 newTab[e.hash & (newCap - 1)] = e; 712 else if (e instanceof TreeNode) 713 ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); 714 else { // preserve order 715 Node<K,V> loHead = null, loTail = null; 716 Node<K,V> hiHead = null, hiTail = null; 717 Node<K,V> next; 718 do { 719 next = e.next; 720 if ((e.hash & oldCap) == 0) { 721 if (loTail == null) 722 loHead = e; 723 else 724 loTail.next = e; 725 loTail = e; 726 } 727 else { 728 if (hiTail == null) 729 hiHead = e; 730 else 731 hiTail.next = e; 732 hiTail = e; 733 } 734 } while ((e = next) != null); 735 if (loTail != null) { 736 loTail.next = null; 737 newTab[j] = loHead; 738 } 739 if (hiTail != null) { 740 hiTail.next = null; 741 newTab[j + oldCap] = hiHead; 742 } 743 } 744 } 745 } 746 } 747 return newTab; 748 } 749 750 /** 751 * Replaces all linked nodes in bin at index for given hash unless 752 * table is too small, in which case resizes instead. 753 */ 754 final void treeifyBin(Node<K,V>[] tab, int hash) { 755 int n, index; Node<K,V> e; 756 if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) 757 resize(); 758 else if ((e = tab[index = (n - 1) & hash]) != null) { 759 TreeNode<K,V> hd = null, tl = null; 760 do { 761 TreeNode<K,V> p = replacementTreeNode(e, null); 762 if (tl == null) 763 hd = p; 764 else { 765 p.prev = tl; 766 tl.next = p; 767 } 768 tl = p; 769 } while ((e = e.next) != null); 770 if ((tab[index] = hd) != null) 771 hd.treeify(tab); 772 } 773 } 774 775 /** 776 * Copies all of the mappings from the specified map to this map. 777 * These mappings will replace any mappings that this map had for 778 * any of the keys currently in the specified map. 779 * 780 * @param m mappings to be stored in this map 781 * @throws NullPointerException if the specified map is null 782 */ 783 public void putAll(Map<? extends K, ? extends V> m) { 784 putMapEntries(m, true); 785 } 786 787 /** 788 * Removes the mapping for the specified key from this map if present. 789 * 790 * @param key key whose mapping is to be removed from the map 791 * @return the previous value associated with <tt>key</tt>, or 792 * <tt>null</tt> if there was no mapping for <tt>key</tt>. 793 * (A <tt>null</tt> return can also indicate that the map 794 * previously associated <tt>null</tt> with <tt>key</tt>.) 795 */ 796 public V remove(Object key) { 797 Node<K,V> e; 798 return (e = removeNode(hash(key), key, null, false, true)) == null ? 799 null : e.value; 800 } 801 802 /** 803 * Implements Map.remove and related methods 804 * 805 * @param hash hash for key 806 * @param key the key 807 * @param value the value to match if matchValue, else ignored 808 * @param matchValue if true only remove if value is equal 809 * @param movable if false do not move other nodes while removing 810 * @return the node, or null if none 811 */ 812 final Node<K,V> removeNode(int hash, Object key, Object value, 813 boolean matchValue, boolean movable) { 814 Node<K,V>[] tab; Node<K,V> p; int n, index; 815 if ((tab = table) != null && (n = tab.length) > 0 && 816 (p = tab[index = (n - 1) & hash]) != null) { 817 Node<K,V> node = null, e; K k; V v; 818 if (p.hash == hash && 819 ((k = p.key) == key || (key != null && key.equals(k)))) 820 node = p; 821 else if ((e = p.next) != null) { 822 if (p instanceof TreeNode) 823 node = ((TreeNode<K,V>)p).getTreeNode(hash, key); 824 else { 825 do { 826 if (e.hash == hash && 827 ((k = e.key) == key || 828 (key != null && key.equals(k)))) { 829 node = e; 830 break; 831 } 832 p = e; 833 } while ((e = e.next) != null); 834 } 835 } 836 if (node != null && (!matchValue || (v = node.value) == value || 837 (value != null && value.equals(v)))) { 838 if (node instanceof TreeNode) 839 ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable); 840 else if (node == p) 841 tab[index] = node.next; 842 else 843 p.next = node.next; 844 ++modCount; 845 --size; 846 afterNodeRemoval(node); 847 return node; 848 } 849 } 850 return null; 851 } 852 853 /** 854 * Removes all of the mappings from this map. 855 * The map will be empty after this call returns. 856 */ 857 public void clear() { 858 Node<K,V>[] tab; 859 modCount++; 860 if ((tab = table) != null && size > 0) { 861 size = 0; 862 for (int i = 0; i < tab.length; ++i) 863 tab[i] = null; 864 } 865 } 866 867 /** 868 * Returns <tt>true</tt> if this map maps one or more keys to the 869 * specified value. 870 * 871 * @param value value whose presence in this map is to be tested 872 * @return <tt>true</tt> if this map maps one or more keys to the 873 * specified value 874 */ 875 public boolean containsValue(Object value) { 876 Node<K,V>[] tab; V v; 877 if ((tab = table) != null && size > 0) { 878 for (int i = 0; i < tab.length; ++i) { 879 for (Node<K,V> e = tab[i]; e != null; e = e.next) { 880 if ((v = e.value) == value || 881 (value != null && value.equals(v))) 882 return true; 883 } 884 } 885 } 886 return false; 887 } 888 889 /** 890 * Returns a {@link Set} view of the keys contained in this map. 891 * The set is backed by the map, so changes to the map are 892 * reflected in the set, and vice-versa. If the map is modified 893 * while an iteration over the set is in progress (except through 894 * the iterator's own <tt>remove</tt> operation), the results of 895 * the iteration are undefined. The set supports element removal, 896 * which removes the corresponding mapping from the map, via the 897 * <tt>Iterator.remove</tt>, <tt>Set.remove</tt>, 898 * <tt>removeAll</tt>, <tt>retainAll</tt>, and <tt>clear</tt> 899 * operations. It does not support the <tt>add</tt> or <tt>addAll</tt> 900 * operations. 901 * 902 * @return a set view of the keys contained in this map 903 */ 904 public Set<K> keySet() { 905 Set<K> ks; 906 return (ks = keySet) == null ? (keySet = new KeySet()) : ks; 907 } 908 909 final class KeySet extends AbstractSet<K> { 910 public final int size() { return size; } 911 public final void clear() { HashMap.this.clear(); } 912 public final Iterator<K> iterator() { return new KeyIterator(); } 913 public final boolean contains(Object o) { return containsKey(o); } 914 public final boolean remove(Object key) { 915 return removeNode(hash(key), key, null, false, true) != null; 916 } 917 public final Spliterator<K> spliterator() { 918 return new KeySpliterator<>(HashMap.this, 0, -1, 0, 0); 919 } 920 public final void forEach(Consumer<? super K> action) { 921 Node<K,V>[] tab; 922 if (action == null) 923 throw new NullPointerException(); 924 if (size > 0 && (tab = table) != null) { 925 int mc = modCount; 926 for (int i = 0; i < tab.length; ++i) { 927 for (Node<K,V> e = tab[i]; e != null; e = e.next) 928 action.accept(e.key); 929 } 930 if (modCount != mc) 931 throw new ConcurrentModificationException(); 932 } 933 } 934 } 935 936 /** 937 * Returns a {@link Collection} view of the values contained in this map. 938 * The collection is backed by the map, so changes to the map are 939 * reflected in the collection, and vice-versa. If the map is 940 * modified while an iteration over the collection is in progress 941 * (except through the iterator's own <tt>remove</tt> operation), 942 * the results of the iteration are undefined. The collection 943 * supports element removal, which removes the corresponding 944 * mapping from the map, via the <tt>Iterator.remove</tt>, 945 * <tt>Collection.remove</tt>, <tt>removeAll</tt>, 946 * <tt>retainAll</tt> and <tt>clear</tt> operations. It does not 947 * support the <tt>add</tt> or <tt>addAll</tt> operations. 948 * 949 * @return a view of the values contained in this map 950 */ 951 public Collection<V> values() { 952 Collection<V> vs; 953 return (vs = values) == null ? (values = new Values()) : vs; 954 } 955 956 final class Values extends AbstractCollection<V> { 957 public final int size() { return size; } 958 public final void clear() { HashMap.this.clear(); } 959 public final Iterator<V> iterator() { return new ValueIterator(); } 960 public final boolean contains(Object o) { return containsValue(o); } 961 public final Spliterator<V> spliterator() { 962 return new ValueSpliterator<>(HashMap.this, 0, -1, 0, 0); 963 } 964 public final void forEach(Consumer<? super V> action) { 965 Node<K,V>[] tab; 966 if (action == null) 967 throw new NullPointerException(); 968 if (size > 0 && (tab = table) != null) { 969 int mc = modCount; 970 for (int i = 0; i < tab.length; ++i) { 971 for (Node<K,V> e = tab[i]; e != null; e = e.next) 972 action.accept(e.value); 973 } 974 if (modCount != mc) 975 throw new ConcurrentModificationException(); 976 } 977 } 978 } 979 980 /** 981 * Returns a {@link Set} view of the mappings contained in this map. 982 * The set is backed by the map, so changes to the map are 983 * reflected in the set, and vice-versa. If the map is modified 984 * while an iteration over the set is in progress (except through 985 * the iterator's own <tt>remove</tt> operation, or through the 986 * <tt>setValue</tt> operation on a map entry returned by the 987 * iterator) the results of the iteration are undefined. The set 988 * supports element removal, which removes the corresponding 989 * mapping from the map, via the <tt>Iterator.remove</tt>, 990 * <tt>Set.remove</tt>, <tt>removeAll</tt>, <tt>retainAll</tt> and 991 * <tt>clear</tt> operations. It does not support the 992 * <tt>add</tt> or <tt>addAll</tt> operations. 993 * 994 * @return a set view of the mappings contained in this map 995 */ 996 public Set<Map.Entry<K,V>> entrySet() { 997 Set<Map.Entry<K,V>> es; 998 return (es = entrySet) == null ? (entrySet = new EntrySet()) : es; 999 } 1000 1001 final class EntrySet extends AbstractSet<Map.Entry<K,V>> { 1002 public final int size() { return size; } 1003 public final void clear() { HashMap.this.clear(); } 1004 public final Iterator<Map.Entry<K,V>> iterator() { 1005 return new EntryIterator(); 1006 } 1007 public final boolean contains(Object o) { 1008 if (!(o instanceof Map.Entry)) 1009 return false; 1010 Map.Entry<?,?> e = (Map.Entry<?,?>) o; 1011 Object key = e.getKey(); 1012 Node<K,V> candidate = getNode(hash(key), key); 1013 return candidate != null && candidate.equals(e); 1014 } 1015 public final boolean remove(Object o) { 1016 if (o instanceof Map.Entry) { 1017 Map.Entry<?,?> e = (Map.Entry<?,?>) o; 1018 Object key = e.getKey(); 1019 Object value = e.getValue(); 1020 return removeNode(hash(key), key, value, true, true) != null; 1021 } 1022 return false; 1023 } 1024 public final Spliterator<Map.Entry<K,V>> spliterator() { 1025 return new EntrySpliterator<>(HashMap.this, 0, -1, 0, 0); 1026 } 1027 public final void forEach(Consumer<? super Map.Entry<K,V>> action) { 1028 Node<K,V>[] tab; 1029 if (action == null) 1030 throw new NullPointerException(); 1031 if (size > 0 && (tab = table) != null) { 1032 int mc = modCount; 1033 for (int i = 0; i < tab.length; ++i) { 1034 for (Node<K,V> e = tab[i]; e != null; e = e.next) 1035 action.accept(e); 1036 } 1037 if (modCount != mc) 1038 throw new ConcurrentModificationException(); 1039 } 1040 } 1041 } 1042 1043 // Overrides of JDK8 Map extension methods 1044 1045 @Override 1046 public V getOrDefault(Object key, V defaultValue) { 1047 Node<K,V> e; 1048 return (e = getNode(hash(key), key)) == null ? defaultValue : e.value; 1049 } 1050 1051 @Override 1052 public V putIfAbsent(K key, V value) { 1053 return putVal(hash(key), key, value, true, true); 1054 } 1055 1056 @Override 1057 public boolean remove(Object key, Object value) { 1058 return removeNode(hash(key), key, value, true, true) != null; 1059 } 1060 1061 @Override 1062 public boolean replace(K key, V oldValue, V newValue) { 1063 Node<K,V> e; V v; 1064 if ((e = getNode(hash(key), key)) != null && 1065 ((v = e.value) == oldValue || (v != null && v.equals(oldValue)))) { 1066 e.value = newValue; 1067 afterNodeAccess(e); 1068 return true; 1069 } 1070 return false; 1071 } 1072 1073 @Override 1074 public V replace(K key, V value) { 1075 Node<K,V> e; 1076 if ((e = getNode(hash(key), key)) != null) { 1077 V oldValue = e.value; 1078 e.value = value; 1079 afterNodeAccess(e); 1080 return oldValue; 1081 } 1082 return null; 1083 } 1084 1085 @Override 1086 public V computeIfAbsent(K key, 1087 Function<? super K, ? extends V> mappingFunction) { 1088 if (mappingFunction == null) 1089 throw new NullPointerException(); 1090 int hash = hash(key); 1091 Node<K,V>[] tab; Node<K,V> first; int n, i; 1092 int binCount = 0; 1093 TreeNode<K,V> t = null; 1094 Node<K,V> old = null; 1095 if (size > threshold || (tab = table) == null || 1096 (n = tab.length) == 0) 1097 n = (tab = resize()).length; 1098 if ((first = tab[i = (n - 1) & hash]) != null) { 1099 if (first instanceof TreeNode) 1100 old = (t = (TreeNode<K,V>)first).getTreeNode(hash, key); 1101 else { 1102 Node<K,V> e = first; K k; 1103 do { 1104 if (e.hash == hash && 1105 ((k = e.key) == key || (key != null && key.equals(k)))) { 1106 old = e; 1107 break; 1108 } 1109 ++binCount; 1110 } while ((e = e.next) != null); 1111 } 1112 V oldValue; 1113 if (old != null && (oldValue = old.value) != null) { 1114 afterNodeAccess(old); 1115 return oldValue; 1116 } 1117 } 1118 V v = mappingFunction.apply(key); 1119 if (v == null) { 1120 return null; 1121 } else if (old != null) { 1122 old.value = v; 1123 afterNodeAccess(old); 1124 return v; 1125 } 1126 else if (t != null) 1127 t.putTreeVal(this, tab, hash, key, v); 1128 else { 1129 tab[i] = newNode(hash, key, v, first); 1130 if (binCount >= TREEIFY_THRESHOLD - 1) 1131 treeifyBin(tab, hash); 1132 } 1133 ++modCount; 1134 ++size; 1135 afterNodeInsertion(true); 1136 return v; 1137 } 1138 1139 public V computeIfPresent(K key, 1140 BiFunction<? super K, ? super V, ? extends V> remappingFunction) { 1141 if (remappingFunction == null) 1142 throw new NullPointerException(); 1143 Node<K,V> e; V oldValue; 1144 int hash = hash(key); 1145 if ((e = getNode(hash, key)) != null && 1146 (oldValue = e.value) != null) { 1147 V v = remappingFunction.apply(key, oldValue); 1148 if (v != null) { 1149 e.value = v; 1150 afterNodeAccess(e); 1151 return v; 1152 } 1153 else 1154 removeNode(hash, key, null, false, true); 1155 } 1156 return null; 1157 } 1158 1159 @Override 1160 public V compute(K key, 1161 BiFunction<? super K, ? super V, ? extends V> remappingFunction) { 1162 if (remappingFunction == null) 1163 throw new NullPointerException(); 1164 int hash = hash(key); 1165 Node<K,V>[] tab; Node<K,V> first; int n, i; 1166 int binCount = 0; 1167 TreeNode<K,V> t = null; 1168 Node<K,V> old = null; 1169 if (size > threshold || (tab = table) == null || 1170 (n = tab.length) == 0) 1171 n = (tab = resize()).length; 1172 if ((first = tab[i = (n - 1) & hash]) != null) { 1173 if (first instanceof TreeNode) 1174 old = (t = (TreeNode<K,V>)first).getTreeNode(hash, key); 1175 else { 1176 Node<K,V> e = first; K k; 1177 do { 1178 if (e.hash == hash && 1179 ((k = e.key) == key || (key != null && key.equals(k)))) { 1180 old = e; 1181 break; 1182 } 1183 ++binCount; 1184 } while ((e = e.next) != null); 1185 } 1186 } 1187 V oldValue = (old == null) ? null : old.value; 1188 V v = remappingFunction.apply(key, oldValue); 1189 if (old != null) { 1190 if (v != null) { 1191 old.value = v; 1192 afterNodeAccess(old); 1193 } 1194 else 1195 removeNode(hash, key, null, false, true); 1196 } 1197 else if (v != null) { 1198 if (t != null) 1199 t.putTreeVal(this, tab, hash, key, v); 1200 else { 1201 tab[i] = newNode(hash, key, v, first); 1202 if (binCount >= TREEIFY_THRESHOLD - 1) 1203 treeifyBin(tab, hash); 1204 } 1205 ++modCount; 1206 ++size; 1207 afterNodeInsertion(true); 1208 } 1209 return v; 1210 } 1211 1212 @Override 1213 public V merge(K key, V value, 1214 BiFunction<? super V, ? super V, ? extends V> remappingFunction) { 1215 if (value == null) 1216 throw new NullPointerException(); 1217 if (remappingFunction == null) 1218 throw new NullPointerException(); 1219 int hash = hash(key); 1220 Node<K,V>[] tab; Node<K,V> first; int n, i; 1221 int binCount = 0; 1222 TreeNode<K,V> t = null; 1223 Node<K,V> old = null; 1224 if (size > threshold || (tab = table) == null || 1225 (n = tab.length) == 0) 1226 n = (tab = resize()).length; 1227 if ((first = tab[i = (n - 1) & hash]) != null) { 1228 if (first instanceof TreeNode) 1229 old = (t = (TreeNode<K,V>)first).getTreeNode(hash, key); 1230 else { 1231 Node<K,V> e = first; K k; 1232 do { 1233 if (e.hash == hash && 1234 ((k = e.key) == key || (key != null && key.equals(k)))) { 1235 old = e; 1236 break; 1237 } 1238 ++binCount; 1239 } while ((e = e.next) != null); 1240 } 1241 } 1242 if (old != null) { 1243 V v; 1244 if (old.value != null) 1245 v = remappingFunction.apply(old.value, value); 1246 else 1247 v = value; 1248 if (v != null) { 1249 old.value = v; 1250 afterNodeAccess(old); 1251 } 1252 else 1253 removeNode(hash, key, null, false, true); 1254 return v; 1255 } 1256 if (value != null) { 1257 if (t != null) 1258 t.putTreeVal(this, tab, hash, key, value); 1259 else { 1260 tab[i] = newNode(hash, key, value, first); 1261 if (binCount >= TREEIFY_THRESHOLD - 1) 1262 treeifyBin(tab, hash); 1263 } 1264 ++modCount; 1265 ++size; 1266 afterNodeInsertion(true); 1267 } 1268 return value; 1269 } 1270 1271 @Override 1272 public void forEach(BiConsumer<? super K, ? super V> action) { 1273 Node<K,V>[] tab; 1274 if (action == null) 1275 throw new NullPointerException(); 1276 if (size > 0 && (tab = table) != null) { 1277 int mc = modCount; 1278 for (int i = 0; i < tab.length; ++i) { 1279 for (Node<K,V> e = tab[i]; e != null; e = e.next) 1280 action.accept(e.key, e.value); 1281 } 1282 if (modCount != mc) 1283 throw new ConcurrentModificationException(); 1284 } 1285 } 1286 1287 @Override 1288 public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) { 1289 Node<K,V>[] tab; 1290 if (function == null) 1291 throw new NullPointerException(); 1292 if (size > 0 && (tab = table) != null) { 1293 int mc = modCount; 1294 for (int i = 0; i < tab.length; ++i) { 1295 for (Node<K,V> e = tab[i]; e != null; e = e.next) { 1296 e.value = function.apply(e.key, e.value); 1297 } 1298 } 1299 if (modCount != mc) 1300 throw new ConcurrentModificationException(); 1301 } 1302 } 1303 1304 /* ------------------------------------------------------------ */ 1305 // Cloning and serialization 1306 1307 /** 1308 * Returns a shallow copy of this <tt>HashMap</tt> instance: the keys and 1309 * values themselves are not cloned. 1310 * 1311 * @return a shallow copy of this map 1312 */ 1313 @SuppressWarnings("unchecked") 1314 @Override 1315 public Object clone() { 1316 HashMap<K,V> result; 1317 try { 1318 result = (HashMap<K,V>)super.clone(); 1319 } catch (CloneNotSupportedException e) { 1320 // this shouldn't happen, since we are Cloneable 1321 throw new InternalError(e); 1322 } 1323 result.reinitialize(); 1324 result.putMapEntries(this, false); 1325 return result; 1326 } 1327 1328 // These methods are also used when serializing HashSets 1329 final float loadFactor() { return loadFactor; } 1330 final int capacity() { 1331 return (table != null) ? table.length : 1332 (threshold > 0) ? threshold : 1333 DEFAULT_INITIAL_CAPACITY; 1334 } 1335 1336 /** 1337 * Save the state of the <tt>HashMap</tt> instance to a stream (i.e., 1338 * serialize it). 1339 * 1340 * @serialData The <i>capacity</i> of the HashMap (the length of the 1341 * bucket array) is emitted (int), followed by the 1342 * <i>size</i> (an int, the number of key-value 1343 * mappings), followed by the key (Object) and value (Object) 1344 * for each key-value mapping. The key-value mappings are 1345 * emitted in no particular order. 1346 */ 1347 private void writeObject(java.io.ObjectOutputStream s) 1348 throws IOException { 1349 int buckets = capacity(); 1350 // Write out the threshold, loadfactor, and any hidden stuff 1351 s.defaultWriteObject(); 1352 s.writeInt(buckets); 1353 s.writeInt(size); 1354 internalWriteEntries(s); 1355 } 1356 1357 /** 1358 * Reconstitute the {@code HashMap} instance from a stream (i.e., 1359 * deserialize it). 1360 */ 1361 private void readObject(java.io.ObjectInputStream s) 1362 throws IOException, ClassNotFoundException { 1363 // Read in the threshold (ignored), loadfactor, and any hidden stuff 1364 s.defaultReadObject(); 1365 reinitialize(); 1366 if (loadFactor <= 0 || Float.isNaN(loadFactor)) 1367 throw new InvalidObjectException("Illegal load factor: " + 1368 loadFactor); 1369 s.readInt(); // Read and ignore number of buckets 1370 int mappings = s.readInt(); // Read number of mappings (size) 1371 if (mappings < 0) 1372 throw new InvalidObjectException("Illegal mappings count: " + 1373 mappings); 1374 else if (mappings > 0) { // (if zero, use defaults) 1375 // Size the table using given load factor only if within 1376 // range of 0.25...4.0 1377 float lf = Math.min(Math.max(0.25f, loadFactor), 4.0f); 1378 float fc = (float)mappings / lf + 1.0f; 1379 int cap = ((fc < DEFAULT_INITIAL_CAPACITY) ? 1380 DEFAULT_INITIAL_CAPACITY : 1381 (fc >= MAXIMUM_CAPACITY) ? 1382 MAXIMUM_CAPACITY : 1383 tableSizeFor((int)fc)); 1384 float ft = (float)cap * lf; 1385 threshold = ((cap < MAXIMUM_CAPACITY && ft < MAXIMUM_CAPACITY) ? 1386 (int)ft : Integer.MAX_VALUE); 1387 @SuppressWarnings({"rawtypes","unchecked"}) 1388 Node<K,V>[] tab = (Node<K,V>[])new Node[cap]; 1389 table = tab; 1390 1391 // Read the keys and values, and put the mappings in the HashMap 1392 for (int i = 0; i < mappings; i++) { 1393 @SuppressWarnings("unchecked") 1394 K key = (K) s.readObject(); 1395 @SuppressWarnings("unchecked") 1396 V value = (V) s.readObject(); 1397 putVal(hash(key), key, value, false, false); 1398 } 1399 } 1400 } 1401 1402 /* ------------------------------------------------------------ */ 1403 // iterators 1404 1405 abstract class HashIterator { 1406 Node<K,V> next; // next entry to return 1407 Node<K,V> current; // current entry 1408 int expectedModCount; // for fast-fail 1409 int index; // current slot 1410 1411 HashIterator() { 1412 expectedModCount = modCount; 1413 Node<K,V>[] t = table; 1414 current = next = null; 1415 index = 0; 1416 if (t != null && size > 0) { // advance to first entry 1417 do {} while (index < t.length && (next = t[index++]) == null); 1418 } 1419 } 1420 1421 public final boolean hasNext() { 1422 return next != null; 1423 } 1424 1425 final Node<K,V> nextNode() { 1426 Node<K,V>[] t; 1427 Node<K,V> e = next; 1428 if (modCount != expectedModCount) 1429 throw new ConcurrentModificationException(); 1430 if (e == null) 1431 throw new NoSuchElementException(); 1432 if ((next = (current = e).next) == null && (t = table) != null) { 1433 do {} while (index < t.length && (next = t[index++]) == null); 1434 } 1435 return e; 1436 } 1437 1438 public final void remove() { 1439 Node<K,V> p = current; 1440 if (p == null) 1441 throw new IllegalStateException(); 1442 if (modCount != expectedModCount) 1443 throw new ConcurrentModificationException(); 1444 current = null; 1445 K key = p.key; 1446 removeNode(hash(key), key, null, false, false); 1447 expectedModCount = modCount; 1448 } 1449 } 1450 1451 final class KeyIterator extends HashIterator 1452 implements Iterator<K> { 1453 public final K next() { return nextNode().key; } 1454 } 1455 1456 final class ValueIterator extends HashIterator 1457 implements Iterator<V> { 1458 public final V next() { return nextNode().value; } 1459 } 1460 1461 final class EntryIterator extends HashIterator 1462 implements Iterator<Map.Entry<K,V>> { 1463 public final Map.Entry<K,V> next() { return nextNode(); } 1464 } 1465 1466 /* ------------------------------------------------------------ */ 1467 // spliterators 1468 1469 static class HashMapSpliterator<K,V> { 1470 final HashMap<K,V> map; 1471 Node<K,V> current; // current node 1472 int index; // current index, modified on advance/split 1473 int fence; // one past last index 1474 int est; // size estimate 1475 int expectedModCount; // for comodification checks 1476 1477 HashMapSpliterator(HashMap<K,V> m, int origin, 1478 int fence, int est, 1479 int expectedModCount) { 1480 this.map = m; 1481 this.index = origin; 1482 this.fence = fence; 1483 this.est = est; 1484 this.expectedModCount = expectedModCount; 1485 } 1486 1487 final int getFence() { // initialize fence and size on first use 1488 int hi; 1489 if ((hi = fence) < 0) { 1490 HashMap<K,V> m = map; 1491 est = m.size; 1492 expectedModCount = m.modCount; 1493 Node<K,V>[] tab = m.table; 1494 hi = fence = (tab == null) ? 0 : tab.length; 1495 } 1496 return hi; 1497 } 1498 1499 public final long estimateSize() { 1500 getFence(); // force init 1501 return (long) est; 1502 } 1503 } 1504 1505 static final class KeySpliterator<K,V> 1506 extends HashMapSpliterator<K,V> 1507 implements Spliterator<K> { 1508 KeySpliterator(HashMap<K,V> m, int origin, int fence, int est, 1509 int expectedModCount) { 1510 super(m, origin, fence, est, expectedModCount); 1511 } 1512 1513 public KeySpliterator<K,V> trySplit() { 1514 int hi = getFence(), lo = index, mid = (lo + hi) >>> 1; 1515 return (lo >= mid || current != null) ? null : 1516 new KeySpliterator<>(map, lo, index = mid, est >>>= 1, 1517 expectedModCount); 1518 } 1519 1520 public void forEachRemaining(Consumer<? super K> action) { 1521 int i, hi, mc; 1522 if (action == null) 1523 throw new NullPointerException(); 1524 HashMap<K,V> m = map; 1525 Node<K,V>[] tab = m.table; 1526 if ((hi = fence) < 0) { 1527 mc = expectedModCount = m.modCount; 1528 hi = fence = (tab == null) ? 0 : tab.length; 1529 } 1530 else 1531 mc = expectedModCount; 1532 if (tab != null && tab.length >= hi && 1533 (i = index) >= 0 && (i < (index = hi) || current != null)) { 1534 Node<K,V> p = current; 1535 current = null; 1536 do { 1537 if (p == null) 1538 p = tab[i++]; 1539 else { 1540 action.accept(p.key); 1541 p = p.next; 1542 } 1543 } while (p != null || i < hi); 1544 if (m.modCount != mc) 1545 throw new ConcurrentModificationException(); 1546 } 1547 } 1548 1549 public boolean tryAdvance(Consumer<? super K> action) { 1550 int hi; 1551 if (action == null) 1552 throw new NullPointerException(); 1553 Node<K,V>[] tab = map.table; 1554 if (tab != null && tab.length >= (hi = getFence()) && index >= 0) { 1555 while (current != null || index < hi) { 1556 if (current == null) 1557 current = tab[index++]; 1558 else { 1559 K k = current.key; 1560 current = current.next; 1561 action.accept(k); 1562 if (map.modCount != expectedModCount) 1563 throw new ConcurrentModificationException(); 1564 return true; 1565 } 1566 } 1567 } 1568 return false; 1569 } 1570 1571 public int characteristics() { 1572 return (fence < 0 || est == map.size ? Spliterator.SIZED : 0) | 1573 Spliterator.DISTINCT; 1574 } 1575 } 1576 1577 static final class ValueSpliterator<K,V> 1578 extends HashMapSpliterator<K,V> 1579 implements Spliterator<V> { 1580 ValueSpliterator(HashMap<K,V> m, int origin, int fence, int est, 1581 int expectedModCount) { 1582 super(m, origin, fence, est, expectedModCount); 1583 } 1584 1585 public ValueSpliterator<K,V> trySplit() { 1586 int hi = getFence(), lo = index, mid = (lo + hi) >>> 1; 1587 return (lo >= mid || current != null) ? null : 1588 new ValueSpliterator<>(map, lo, index = mid, est >>>= 1, 1589 expectedModCount); 1590 } 1591 1592 public void forEachRemaining(Consumer<? super V> action) { 1593 int i, hi, mc; 1594 if (action == null) 1595 throw new NullPointerException(); 1596 HashMap<K,V> m = map; 1597 Node<K,V>[] tab = m.table; 1598 if ((hi = fence) < 0) { 1599 mc = expectedModCount = m.modCount; 1600 hi = fence = (tab == null) ? 0 : tab.length; 1601 } 1602 else 1603 mc = expectedModCount; 1604 if (tab != null && tab.length >= hi && 1605 (i = index) >= 0 && (i < (index = hi) || current != null)) { 1606 Node<K,V> p = current; 1607 current = null; 1608 do { 1609 if (p == null) 1610 p = tab[i++]; 1611 else { 1612 action.accept(p.value); 1613 p = p.next; 1614 } 1615 } while (p != null || i < hi); 1616 if (m.modCount != mc) 1617 throw new ConcurrentModificationException(); 1618 } 1619 } 1620 1621 public boolean tryAdvance(Consumer<? super V> action) { 1622 int hi; 1623 if (action == null) 1624 throw new NullPointerException(); 1625 Node<K,V>[] tab = map.table; 1626 if (tab != null && tab.length >= (hi = getFence()) && index >= 0) { 1627 while (current != null || index < hi) { 1628 if (current == null) 1629 current = tab[index++]; 1630 else { 1631 V v = current.value; 1632 current = current.next; 1633 action.accept(v); 1634 if (map.modCount != expectedModCount) 1635 throw new ConcurrentModificationException(); 1636 return true; 1637 } 1638 } 1639 } 1640 return false; 1641 } 1642 1643 public int characteristics() { 1644 return (fence < 0 || est == map.size ? Spliterator.SIZED : 0); 1645 } 1646 } 1647 1648 static final class EntrySpliterator<K,V> 1649 extends HashMapSpliterator<K,V> 1650 implements Spliterator<Map.Entry<K,V>> { 1651 EntrySpliterator(HashMap<K,V> m, int origin, int fence, int est, 1652 int expectedModCount) { 1653 super(m, origin, fence, est, expectedModCount); 1654 } 1655 1656 public EntrySpliterator<K,V> trySplit() { 1657 int hi = getFence(), lo = index, mid = (lo + hi) >>> 1; 1658 return (lo >= mid || current != null) ? null : 1659 new EntrySpliterator<>(map, lo, index = mid, est >>>= 1, 1660 expectedModCount); 1661 } 1662 1663 public void forEachRemaining(Consumer<? super Map.Entry<K,V>> action) { 1664 int i, hi, mc; 1665 if (action == null) 1666 throw new NullPointerException(); 1667 HashMap<K,V> m = map; 1668 Node<K,V>[] tab = m.table; 1669 if ((hi = fence) < 0) { 1670 mc = expectedModCount = m.modCount; 1671 hi = fence = (tab == null) ? 0 : tab.length; 1672 } 1673 else 1674 mc = expectedModCount; 1675 if (tab != null && tab.length >= hi && 1676 (i = index) >= 0 && (i < (index = hi) || current != null)) { 1677 Node<K,V> p = current; 1678 current = null; 1679 do { 1680 if (p == null) 1681 p = tab[i++]; 1682 else { 1683 action.accept(p); 1684 p = p.next; 1685 } 1686 } while (p != null || i < hi); 1687 if (m.modCount != mc) 1688 throw new ConcurrentModificationException(); 1689 } 1690 } 1691 1692 public boolean tryAdvance(Consumer<? super Map.Entry<K,V>> action) { 1693 int hi; 1694 if (action == null) 1695 throw new NullPointerException(); 1696 Node<K,V>[] tab = map.table; 1697 if (tab != null && tab.length >= (hi = getFence()) && index >= 0) { 1698 while (current != null || index < hi) { 1699 if (current == null) 1700 current = tab[index++]; 1701 else { 1702 Node<K,V> e = current; 1703 current = current.next; 1704 action.accept(e); 1705 if (map.modCount != expectedModCount) 1706 throw new ConcurrentModificationException(); 1707 return true; 1708 } 1709 } 1710 } 1711 return false; 1712 } 1713 1714 public int characteristics() { 1715 return (fence < 0 || est == map.size ? Spliterator.SIZED : 0) | 1716 Spliterator.DISTINCT; 1717 } 1718 } 1719 1720 /* ------------------------------------------------------------ */ 1721 // LinkedHashMap support 1722 1723 1724 /* 1725 * The following package-protected methods are designed to be 1726 * overridden by LinkedHashMap, but not by any other subclass. 1727 * Nearly all other internal methods are also package-protected 1728 * but are declared final, so can be used by LinkedHashMap, view 1729 * classes, and HashSet. 1730 */ 1731 1732 // Create a regular (non-tree) node 1733 Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) { 1734 return new Node<>(hash, key, value, next); 1735 } 1736 1737 // For conversion from TreeNodes to plain nodes 1738 Node<K,V> replacementNode(Node<K,V> p, Node<K,V> next) { 1739 return new Node<>(p.hash, p.key, p.value, next); 1740 } 1741 1742 // Create a tree bin node 1743 TreeNode<K,V> newTreeNode(int hash, K key, V value, Node<K,V> next) { 1744 return new TreeNode<>(hash, key, value, next); 1745 } 1746 1747 // For treeifyBin 1748 TreeNode<K,V> replacementTreeNode(Node<K,V> p, Node<K,V> next) { 1749 return new TreeNode<>(p.hash, p.key, p.value, next); 1750 } 1751 1752 /** 1753 * Reset to initial default state. Called by clone and readObject. 1754 */ 1755 void reinitialize() { 1756 table = null; 1757 entrySet = null; 1758 keySet = null; 1759 values = null; 1760 modCount = 0; 1761 threshold = 0; 1762 size = 0; 1763 } 1764 1765 // Callbacks to allow LinkedHashMap post-actions 1766 void afterNodeAccess(Node<K,V> p) { } 1767 void afterNodeInsertion(boolean evict) { } 1768 void afterNodeRemoval(Node<K,V> p) { } 1769 1770 // Called only from writeObject, to ensure compatible ordering. 1771 void internalWriteEntries(java.io.ObjectOutputStream s) throws IOException { 1772 Node<K,V>[] tab; 1773 if (size > 0 && (tab = table) != null) { 1774 for (int i = 0; i < tab.length; ++i) { 1775 for (Node<K,V> e = tab[i]; e != null; e = e.next) { 1776 s.writeObject(e.key); 1777 s.writeObject(e.value); 1778 } 1779 } 1780 } 1781 } 1782 1783 /* ------------------------------------------------------------ */ 1784 // Tree bins 1785 1786 /** 1787 * Entry for Tree bins. Extends LinkedHashMap.Entry (which in turn 1788 * extends Node) so can be used as extension of either regular or 1789 * linked node. 1790 */ 1791 static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> { 1792 TreeNode<K,V> parent; // red-black tree links 1793 TreeNode<K,V> left; 1794 TreeNode<K,V> right; 1795 TreeNode<K,V> prev; // needed to unlink next upon deletion 1796 boolean red; 1797 TreeNode(int hash, K key, V val, Node<K,V> next) { 1798 super(hash, key, val, next); 1799 } 1800 1801 /** 1802 * Returns root of tree containing this node. 1803 */ 1804 final TreeNode<K,V> root() { 1805 for (TreeNode<K,V> r = this, p;;) { 1806 if ((p = r.parent) == null) 1807 return r; 1808 r = p; 1809 } 1810 } 1811 1812 /** 1813 * Ensures that the given root is the first node of its bin. 1814 */ 1815 static <K,V> void moveRootToFront(Node<K,V>[] tab, TreeNode<K,V> root) { 1816 int n; 1817 if (root != null && tab != null && (n = tab.length) > 0) { 1818 int index = (n - 1) & root.hash; 1819 TreeNode<K,V> first = (TreeNode<K,V>)tab[index]; 1820 if (root != first) { 1821 Node<K,V> rn; 1822 tab[index] = root; 1823 TreeNode<K,V> rp = root.prev; 1824 if ((rn = root.next) != null) 1825 ((TreeNode<K,V>)rn).prev = rp; 1826 if (rp != null) 1827 rp.next = rn; 1828 if (first != null) 1829 first.prev = root; 1830 root.next = first; 1831 root.prev = null; 1832 } 1833 assert checkInvariants(root); 1834 } 1835 } 1836 1837 /** 1838 * Finds the node starting at root p with the given hash and key. 1839 * The kc argument caches comparableClassFor(key) upon first use 1840 * comparing keys. 1841 */ 1842 final TreeNode<K,V> find(int h, Object k, Class<?> kc) { 1843 TreeNode<K,V> p = this; 1844 do { 1845 int ph, dir; K pk; 1846 TreeNode<K,V> pl = p.left, pr = p.right, q; 1847 if ((ph = p.hash) > h) 1848 p = pl; 1849 else if (ph < h) 1850 p = pr; 1851 else if ((pk = p.key) == k || (k != null && k.equals(pk))) 1852 return p; 1853 else if (pl == null) 1854 p = pr; 1855 else if (pr == null) 1856 p = pl; 1857 else if ((kc != null || 1858 (kc = comparableClassFor(k)) != null) && 1859 (dir = compareComparables(kc, k, pk)) != 0) 1860 p = (dir < 0) ? pl : pr; 1861 else if ((q = pr.find(h, k, kc)) != null) 1862 return q; 1863 else 1864 p = pl; 1865 } while (p != null); 1866 return null; 1867 } 1868 1869 /** 1870 * Calls find for root node. 1871 */ 1872 final TreeNode<K,V> getTreeNode(int h, Object k) { 1873 return ((parent != null) ? root() : this).find(h, k, null); 1874 } 1875 1876 /** 1877 * Tie-breaking utility for ordering insertions when equal 1878 * hashCodes and non-comparable. We don't require a total 1879 * order, just a consistent insertion rule to maintain 1880 * equivalence across rebalancings. Tie-breaking further than 1881 * necessary simplifies testing a bit. 1882 */ 1883 static int tieBreakOrder(Object a, Object b) { 1884 int d; 1885 if (a == null || b == null || 1886 (d = a.getClass().getName(). 1887 compareTo(b.getClass().getName())) == 0) 1888 d = (System.identityHashCode(a) <= System.identityHashCode(b) ? 1889 -1 : 1); 1890 return d; 1891 } 1892 1893 /** 1894 * Forms tree of the nodes linked from this node. 1895 * @return root of tree 1896 */ 1897 final void treeify(Node<K,V>[] tab) { 1898 TreeNode<K,V> root = null; 1899 for (TreeNode<K,V> x = this, next; x != null; x = next) { 1900 next = (TreeNode<K,V>)x.next; 1901 x.left = x.right = null; 1902 if (root == null) { 1903 x.parent = null; 1904 x.red = false; 1905 root = x; 1906 } 1907 else { 1908 K k = x.key; 1909 int h = x.hash; 1910 Class<?> kc = null; 1911 for (TreeNode<K,V> p = root;;) { 1912 int dir, ph; 1913 K pk = p.key; 1914 if ((ph = p.hash) > h) 1915 dir = -1; 1916 else if (ph < h) 1917 dir = 1; 1918 else if ((kc == null && 1919 (kc = comparableClassFor(k)) == null) || 1920 (dir = compareComparables(kc, k, pk)) == 0) 1921 dir = tieBreakOrder(k, pk); 1922 1923 TreeNode<K,V> xp = p; 1924 if ((p = (dir <= 0) ? p.left : p.right) == null) { 1925 x.parent = xp; 1926 if (dir <= 0) 1927 xp.left = x; 1928 else 1929 xp.right = x; 1930 root = balanceInsertion(root, x); 1931 break; 1932 } 1933 } 1934 } 1935 } 1936 moveRootToFront(tab, root); 1937 } 1938 1939 /** 1940 * Returns a list of non-TreeNodes replacing those linked from 1941 * this node. 1942 */ 1943 final Node<K,V> untreeify(HashMap<K,V> map) { 1944 Node<K,V> hd = null, tl = null; 1945 for (Node<K,V> q = this; q != null; q = q.next) { 1946 Node<K,V> p = map.replacementNode(q, null); 1947 if (tl == null) 1948 hd = p; 1949 else 1950 tl.next = p; 1951 tl = p; 1952 } 1953 return hd; 1954 } 1955 1956 /** 1957 * Tree version of putVal. 1958 */ 1959 final TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab, 1960 int h, K k, V v) { 1961 Class<?> kc = null; 1962 boolean searched = false; 1963 TreeNode<K,V> root = (parent != null) ? root() : this; 1964 for (TreeNode<K,V> p = root;;) { 1965 int dir, ph; K pk; 1966 if ((ph = p.hash) > h) 1967 dir = -1; 1968 else if (ph < h) 1969 dir = 1; 1970 else if ((pk = p.key) == k || (k != null && k.equals(pk))) 1971 return p; 1972 else if ((kc == null && 1973 (kc = comparableClassFor(k)) == null) || 1974 (dir = compareComparables(kc, k, pk)) == 0) { 1975 if (!searched) { 1976 TreeNode<K,V> q, ch; 1977 searched = true; 1978 if (((ch = p.left) != null && 1979 (q = ch.find(h, k, kc)) != null) || 1980 ((ch = p.right) != null && 1981 (q = ch.find(h, k, kc)) != null)) 1982 return q; 1983 } 1984 dir = tieBreakOrder(k, pk); 1985 } 1986 1987 TreeNode<K,V> xp = p; 1988 if ((p = (dir <= 0) ? p.left : p.right) == null) { 1989 Node<K,V> xpn = xp.next; 1990 TreeNode<K,V> x = map.newTreeNode(h, k, v, xpn); 1991 if (dir <= 0) 1992 xp.left = x; 1993 else 1994 xp.right = x; 1995 xp.next = x; 1996 x.parent = x.prev = xp; 1997 if (xpn != null) 1998 ((TreeNode<K,V>)xpn).prev = x; 1999 moveRootToFront(tab, balanceInsertion(root, x)); 2000 return null; 2001 } 2002 } 2003 } 2004 2005 /** 2006 * Removes the given node, that must be present before this call. 2007 * This is messier than typical red-black deletion code because we 2008 * cannot swap the contents of an interior node with a leaf 2009 * successor that is pinned by "next" pointers that are accessible 2010 * independently during traversal. So instead we swap the tree 2011 * linkages. If the current tree appears to have too few nodes, 2012 * the bin is converted back to a plain bin. (The test triggers 2013 * somewhere between 2 and 6 nodes, depending on tree structure). 2014 */ 2015 final void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab, 2016 boolean movable) { 2017 int n; 2018 if (tab == null || (n = tab.length) == 0) 2019 return; 2020 int index = (n - 1) & hash; 2021 TreeNode<K,V> first = (TreeNode<K,V>)tab[index], root = first, rl; 2022 TreeNode<K,V> succ = (TreeNode<K,V>)next, pred = prev; 2023 if (pred == null) 2024 tab[index] = first = succ; 2025 else 2026 pred.next = succ; 2027 if (succ != null) 2028 succ.prev = pred; 2029 if (first == null) 2030 return; 2031 if (root.parent != null) 2032 root = root.root(); 2033 if (root == null || root.right == null || 2034 (rl = root.left) == null || rl.left == null) { 2035 tab[index] = first.untreeify(map); // too small 2036 return; 2037 } 2038 TreeNode<K,V> p = this, pl = left, pr = right, replacement; 2039 if (pl != null && pr != null) { 2040 TreeNode<K,V> s = pr, sl; 2041 while ((sl = s.left) != null) // find successor 2042 s = sl; 2043 boolean c = s.red; s.red = p.red; p.red = c; // swap colors 2044 TreeNode<K,V> sr = s.right; 2045 TreeNode<K,V> pp = p.parent; 2046 if (s == pr) { // p was s's direct parent 2047 p.parent = s; 2048 s.right = p; 2049 } 2050 else { 2051 TreeNode<K,V> sp = s.parent; 2052 if ((p.parent = sp) != null) { 2053 if (s == sp.left) 2054 sp.left = p; 2055 else 2056 sp.right = p; 2057 } 2058 if ((s.right = pr) != null) 2059 pr.parent = s; 2060 } 2061 p.left = null; 2062 if ((p.right = sr) != null) 2063 sr.parent = p; 2064 if ((s.left = pl) != null) 2065 pl.parent = s; 2066 if ((s.parent = pp) == null) 2067 root = s; 2068 else if (p == pp.left) 2069 pp.left = s; 2070 else 2071 pp.right = s; 2072 if (sr != null) 2073 replacement = sr; 2074 else 2075 replacement = p; 2076 } 2077 else if (pl != null) 2078 replacement = pl; 2079 else if (pr != null) 2080 replacement = pr; 2081 else 2082 replacement = p; 2083 if (replacement != p) { 2084 TreeNode<K,V> pp = replacement.parent = p.parent; 2085 if (pp == null) 2086 root = replacement; 2087 else if (p == pp.left) 2088 pp.left = replacement; 2089 else 2090 pp.right = replacement; 2091 p.left = p.right = p.parent = null; 2092 } 2093 2094 TreeNode<K,V> r = p.red ? root : balanceDeletion(root, replacement); 2095 2096 if (replacement == p) { // detach 2097 TreeNode<K,V> pp = p.parent; 2098 p.parent = null; 2099 if (pp != null) { 2100 if (p == pp.left) 2101 pp.left = null; 2102 else if (p == pp.right) 2103 pp.right = null; 2104 } 2105 } 2106 if (movable) 2107 moveRootToFront(tab, r); 2108 } 2109 2110 /** 2111 * Splits nodes in a tree bin into lower and upper tree bins, 2112 * or untreeifies if now too small. Called only from resize; 2113 * see above discussion about split bits and indices. 2114 * 2115 * @param map the map 2116 * @param tab the table for recording bin heads 2117 * @param index the index of the table being split 2118 * @param bit the bit of hash to split on 2119 */ 2120 final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) { 2121 TreeNode<K,V> b = this; 2122 // Relink into lo and hi lists, preserving order 2123 TreeNode<K,V> loHead = null, loTail = null; 2124 TreeNode<K,V> hiHead = null, hiTail = null; 2125 int lc = 0, hc = 0; 2126 for (TreeNode<K,V> e = b, next; e != null; e = next) { 2127 next = (TreeNode<K,V>)e.next; 2128 e.next = null; 2129 if ((e.hash & bit) == 0) { 2130 if ((e.prev = loTail) == null) 2131 loHead = e; 2132 else 2133 loTail.next = e; 2134 loTail = e; 2135 ++lc; 2136 } 2137 else { 2138 if ((e.prev = hiTail) == null) 2139 hiHead = e; 2140 else 2141 hiTail.next = e; 2142 hiTail = e; 2143 ++hc; 2144 } 2145 } 2146 2147 if (loHead != null) { 2148 if (lc <= UNTREEIFY_THRESHOLD) 2149 tab[index] = loHead.untreeify(map); 2150 else { 2151 tab[index] = loHead; 2152 if (hiHead != null) // (else is already treeified) 2153 loHead.treeify(tab); 2154 } 2155 } 2156 if (hiHead != null) { 2157 if (hc <= UNTREEIFY_THRESHOLD) 2158 tab[index + bit] = hiHead.untreeify(map); 2159 else { 2160 tab[index + bit] = hiHead; 2161 if (loHead != null) 2162 hiHead.treeify(tab); 2163 } 2164 } 2165 } 2166 2167 /* ------------------------------------------------------------ */ 2168 // Red-black tree methods, all adapted from CLR 2169 2170 static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root, 2171 TreeNode<K,V> p) { 2172 TreeNode<K,V> r, pp, rl; 2173 if (p != null && (r = p.right) != null) { 2174 if ((rl = p.right = r.left) != null) 2175 rl.parent = p; 2176 if ((pp = r.parent = p.parent) == null) 2177 (root = r).red = false; 2178 else if (pp.left == p) 2179 pp.left = r; 2180 else 2181 pp.right = r; 2182 r.left = p; 2183 p.parent = r; 2184 } 2185 return root; 2186 } 2187 2188 static <K,V> TreeNode<K,V> rotateRight(TreeNode<K,V> root, 2189 TreeNode<K,V> p) { 2190 TreeNode<K,V> l, pp, lr; 2191 if (p != null && (l = p.left) != null) { 2192 if ((lr = p.left = l.right) != null) 2193 lr.parent = p; 2194 if ((pp = l.parent = p.parent) == null) 2195 (root = l).red = false; 2196 else if (pp.right == p) 2197 pp.right = l; 2198 else 2199 pp.left = l; 2200 l.right = p; 2201 p.parent = l; 2202 } 2203 return root; 2204 } 2205 2206 static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root, 2207 TreeNode<K,V> x) { 2208 x.red = true; 2209 for (TreeNode<K,V> xp, xpp, xppl, xppr;;) { 2210 if ((xp = x.parent) == null) { 2211 x.red = false; 2212 return x; 2213 } 2214 else if (!xp.red || (xpp = xp.parent) == null) 2215 return root; 2216 if (xp == (xppl = xpp.left)) { 2217 if ((xppr = xpp.right) != null && xppr.red) { 2218 xppr.red = false; 2219 xp.red = false; 2220 xpp.red = true; 2221 x = xpp; 2222 } 2223 else { 2224 if (x == xp.right) { 2225 root = rotateLeft(root, x = xp); 2226 xpp = (xp = x.parent) == null ? null : xp.parent; 2227 } 2228 if (xp != null) { 2229 xp.red = false; 2230 if (xpp != null) { 2231 xpp.red = true; 2232 root = rotateRight(root, xpp); 2233 } 2234 } 2235 } 2236 } 2237 else { 2238 if (xppl != null && xppl.red) { 2239 xppl.red = false; 2240 xp.red = false; 2241 xpp.red = true; 2242 x = xpp; 2243 } 2244 else { 2245 if (x == xp.left) { 2246 root = rotateRight(root, x = xp); 2247 xpp = (xp = x.parent) == null ? null : xp.parent; 2248 } 2249 if (xp != null) { 2250 xp.red = false; 2251 if (xpp != null) { 2252 xpp.red = true; 2253 root = rotateLeft(root, xpp); 2254 } 2255 } 2256 } 2257 } 2258 } 2259 } 2260 2261 static <K,V> TreeNode<K,V> balanceDeletion(TreeNode<K,V> root, 2262 TreeNode<K,V> x) { 2263 for (TreeNode<K,V> xp, xpl, xpr;;) { 2264 if (x == null || x == root) 2265 return root; 2266 else if ((xp = x.parent) == null) { 2267 x.red = false; 2268 return x; 2269 } 2270 else if (x.red) { 2271 x.red = false; 2272 return root; 2273 } 2274 else if ((xpl = xp.left) == x) { 2275 if ((xpr = xp.right) != null && xpr.red) { 2276 xpr.red = false; 2277 xp.red = true; 2278 root = rotateLeft(root, xp); 2279 xpr = (xp = x.parent) == null ? null : xp.right; 2280 } 2281 if (xpr == null) 2282 x = xp; 2283 else { 2284 TreeNode<K,V> sl = xpr.left, sr = xpr.right; 2285 if ((sr == null || !sr.red) && 2286 (sl == null || !sl.red)) { 2287 xpr.red = true; 2288 x = xp; 2289 } 2290 else { 2291 if (sr == null || !sr.red) { 2292 if (sl != null) 2293 sl.red = false; 2294 xpr.red = true; 2295 root = rotateRight(root, xpr); 2296 xpr = (xp = x.parent) == null ? 2297 null : xp.right; 2298 } 2299 if (xpr != null) { 2300 xpr.red = (xp == null) ? false : xp.red; 2301 if ((sr = xpr.right) != null) 2302 sr.red = false; 2303 } 2304 if (xp != null) { 2305 xp.red = false; 2306 root = rotateLeft(root, xp); 2307 } 2308 x = root; 2309 } 2310 } 2311 } 2312 else { // symmetric 2313 if (xpl != null && xpl.red) { 2314 xpl.red = false; 2315 xp.red = true; 2316 root = rotateRight(root, xp); 2317 xpl = (xp = x.parent) == null ? null : xp.left; 2318 } 2319 if (xpl == null) 2320 x = xp; 2321 else { 2322 TreeNode<K,V> sl = xpl.left, sr = xpl.right; 2323 if ((sl == null || !sl.red) && 2324 (sr == null || !sr.red)) { 2325 xpl.red = true; 2326 x = xp; 2327 } 2328 else { 2329 if (sl == null || !sl.red) { 2330 if (sr != null) 2331 sr.red = false; 2332 xpl.red = true; 2333 root = rotateLeft(root, xpl); 2334 xpl = (xp = x.parent) == null ? 2335 null : xp.left; 2336 } 2337 if (xpl != null) { 2338 xpl.red = (xp == null) ? false : xp.red; 2339 if ((sl = xpl.left) != null) 2340 sl.red = false; 2341 } 2342 if (xp != null) { 2343 xp.red = false; 2344 root = rotateRight(root, xp); 2345 } 2346 x = root; 2347 } 2348 } 2349 } 2350 } 2351 } 2352 2353 /** 2354 * Recursive invariant check 2355 */ 2356 static <K,V> boolean checkInvariants(TreeNode<K,V> t) { 2357 TreeNode<K,V> tp = t.parent, tl = t.left, tr = t.right, 2358 tb = t.prev, tn = (TreeNode<K,V>)t.next; 2359 if (tb != null && tb.next != t) 2360 return false; 2361 if (tn != null && tn.prev != t) 2362 return false; 2363 if (tp != null && t != tp.left && t != tp.right) 2364 return false; 2365 if (tl != null && (tl.parent != t || tl.hash > t.hash)) 2366 return false; 2367 if (tr != null && (tr.parent != t || tr.hash < t.hash)) 2368 return false; 2369 if (t.red && tl != null && tl.red && tr != null && tr.red) 2370 return false; 2371 if (tl != null && !checkInvariants(tl)) 2372 return false; 2373 if (tr != null && !checkInvariants(tr)) 2374 return false; 2375 return true; 2376 } 2377 } 2378 2379 }
2.3、HashMap的基本思想
2.3.1、 确定哈希桶数组索引位置
不管增加、删除、查找键值对,定位到哈希桶数组的位置都是很关键的第一步。前面说过HashMap的数据结构是数组和链表(链地址法)的结合,所以我们当然希望这个HashMap里面的元素位置尽量分布均匀些,尽量使得每个位置上的元素数量只有一个,那么当我们用hash算法求得这个位置的时候,马上就可以知道对应位置的元素就是我们要的,不用遍历链表,大大优化了查询的效率。HashMap定位数组索引位置,直接决定了hash方法的离散性能。先看看源码的实现:
1 方法一: 2 static final int hash(Object key) { //jdk1.8 & jdk1.7 3 int h; 4 // h = key.hashCode() 为第一步 取hashCode值 5 // h ^ (h >>> 16) 为第二步 高位参与运算 6 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); 7 } 8 方法二: 9 static int indexFor(int h, int length) { 10 //jdk1.7的源码,jdk1.8没有这个方法,但是实现原理一样的 11 return h & (length-1); //第三步 取模运算 12 }
这里的Hash算法本质上就是三步:取key的hashCode值、高位运算、取模运算。
对于任意给定的对象,只要它的hashCode()返回值相同,那么程序调用方法一所计算得到的Hash码值总是相同的。我们首先想到的就是把hash值对数组长度取模运算,这样一来,元素的分布相对来说是比较均匀的。但是,模运算的消耗还是比较大的,在HashMap中是这样做的:调用方法二来计算该对象应该保存在table数组的哪个索引处。这个方法非常巧妙,它通过h & (table.length -1)来得到该对象的保存位,而HashMap底层数组的长度总是2的n次方,这是HashMap在速度上的优化。当length总是2的n次方时,h& (length-1)运算等价于对length取模,也就是h%length,但是&比%具有更高的效率。
在JDK1.8的实现中,优化了高位运算的算法,通过hashCode()的高16位异或低16位实现的:(h = k.hashCode()) ^ (h >>> 16),主要是从速度、功效、质量来考虑的,这么做可以在数组table的length比较小的时候,也能保证考虑到高低bit都参与到Hash的计算中,同时不会有太大的开销。
2.3.2、jdk1.8中的HashMap的put方法
2.3.3、扩容机制
扩容(resize)就是重新计算容量,向HashMap对象里不停的添加元素,而HashMap对象内部的数组无法装载更多的元素时,对象就需要扩大数组的长度,以便能装入更多的元素。当然Java里的数组是无法自动扩容的,方法是使用一个新的数组代替已有的容量小的数组。
我们分析下resize的源码,鉴于JDK1.8融入了红黑树,较复杂,为了便于理解我们仍然使用JDK1.7的代码,好理解一些,本质上区别不大,具体区别后文再说。
1 void resize(int newCapacity) { //传入新的容量 2 Entry[] oldTable = table; //引用扩容前的Entry数组 3 int oldCapacity = oldTable.length; 4 if (oldCapacity == MAXIMUM_CAPACITY) { //扩容前的数组大小如果已经达到最大(2^30)了 5 threshold = Integer.MAX_VALUE; //修改阈值为int的最大值(2^31-1),这样以后就不会扩容了 6 return; 7 } 8 9 Entry[] newTable = new Entry[newCapacity]; //初始化一个新的Entry数组 10 transfer(newTable); //!!将数据转移到新的Entry数组里 11 table = newTable; //HashMap的table属性引用新的Entry数组 12 threshold = (int)(newCapacity * loadFactor);//修改阈值 13 }
这里就是使用一个容量更大的数组来代替已有的容量小的数组,transfer()方法将原有Entry数组的元素拷贝到新的Entry数组里。
1 void transfer(Entry[] newTable) { 2 Entry[] src = table; //src引用了旧的Entry数组 3 int newCapacity = newTable.length; 4 for (int j = 0; j < src.length; j++) { //遍历旧的Entry数组 5 Entry<K,V> e = src[j]; //取得旧Entry数组的每个元素 6 if (e != null) { 7 src[j] = null;//释放旧Entry数组的对象引用(for循环后,旧的Entry数组不再引用任何对象) 8 do { 9 Entry<K,V> next = e.next; 10 int i = indexFor(e.hash, newCapacity); //!!重新计算每个元素在数组中的位置 11 e.next = newTable[i]; 12 newTable[i] = e; //将元素放在数组上 13 e = next; //访问下一个Entry链上的元素 14 } while (e != null); 15 } 16 } 17 }
newTable[i]的引用赋给了e.next,也就是使用了单链表的头插入方式,同一位置上新元素总会被放在链表的头部位置;这样先放在一个索引上的元素终会被放到Entry链的尾部(如果发生了hash冲突的话),这一点和Jdk1.8有区别,下文详解。在旧数组中同一条Entry链上的元素,通过重新计算索引位置后,有可能被放到了新数组的不同位置上。
下面举个例子说明下扩容过程。假设了我们的hash算法就是简单的用key mod 一下表的大小(也就是数组的长度)。其中的哈希桶数组table的size=2, 所以key = 3、7、5,put顺序依次为 5、7、3。在mod 2以后都冲突在table[1]这里了。这里假设负载因子 loadFactor=1,即当键值对的实际大小size 大于 table的实际大小时进行扩容。接下来的三个步骤是哈希桶数组 resize成4,然后所有的Node重新rehash的过程。
下面我们讲解下JDK1.8做的优化。经过观测可以发现,我们使用的是2次幂的扩展(指长度扩为原来2倍),所以,元素的位置要么是在原位置,要么是在原位置再移动2次幂的位置。下图n为table的长度,图(a)表示扩容前的key1和key2两种key确定索引位置的示例,图(b)表示扩容后key1和key2两种key确定索引位置的示例,其中hash1是key1对应的哈希与高位运算结果。
元素在重新计算hash之后,因为n变为2倍,那么n-1的mask范围在高位多1bit(红色),因此新的index就会发生这样的变化:
因此,我们在扩充HashMap的时候,不需要像JDK1.7的实现那样重新计算hash,只需要看看原来的hash值新增的那个bit是1还是0就好了,是0的话索引没变,是1的话索引变成“原索引+oldCap”,可以看看下图为16扩充为32的resize示意图:
这个设计确实非常的巧妙,既省去了重新计算hash值的时间,而且同时,由于新增的1bit是0还是1可以认为是随机的,因此resize的过程,均匀的把之前的冲突的节点分散到新的bucket了。这一块就是JDK1.8新增的优化点。有一点注意区别,JDK1.7中rehash的时候,旧链表迁移新链表的时候,如果在新表的数组索引位置相同,则链表元素会倒置,但是JDK1.8不会倒置。
1 (1) 扩容是一个特别耗性能的操作,所以当程序员在使用HashMap的时候,估算map的大小,初始化的时候给一个大致的数值,避免map进行频繁的扩容。 2 (2) 负载因子是可以修改的,也可以大于1,但是建议不要轻易修改,除非情况非常特殊。 3 (3) HashMap是线程不安全的,不要在并发的环境中同时操作HashMap,建议使用ConcurrentHashMap。 4 (4) JDK1.8引入红黑树大程度优化了HashMap的性能。
三、ConcurrentHashMap的介绍
HashMap是非线程安全的,这意味着不应该在多个线程中对这些Map进行修改操作,轻则会产生数据不一致的问题,甚至还会因为并发插入元素而导致链表成环(插入会触发扩容,而扩容操作需要将原数组中的元素rehash到新数组,这时并发操作就有可能产生链表的循环引用从而成环),这样在查找时就会发生死循环,影响到整个应用程序。
Collections.synchronizedMap(Map<K,V> m)可以将一个Map转换成线程安全的实现,其实也就是通过一个包装类,然后把所有功能都委托给传入的Map实现,而且包装类是基于synchronized关键字来保证线程安全的(Hashtable也是基于synchronized关键字),底层使用的是互斥锁(同一时间内只能由持有锁的线程访问,其他竞争线程进入睡眠状态),性能与吞吐量比较低。
1 public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) { 2 return new SynchronizedMap<>(m); 3 } 4 private static class SynchronizedMap<K,V> 5 implements Map<K,V>, Serializable { 6 private static final long serialVersionUID = 1978198479659022715L; 7 private final Map<K,V> m; // Backing Map 8 final Object mutex; // Object on which to synchronize 9 SynchronizedMap(Map<K,V> m) { 10 this.m = Objects.requireNonNull(m); 11 mutex = this; 12 } 13 SynchronizedMap(Map<K,V> m, Object mutex) { 14 this.m = m; 15 this.mutex = mutex; 16 } 17 public int size() { 18 synchronized (mutex) {return m.size();} 19 } 20 public boolean isEmpty() { 21 synchronized (mutex) {return m.isEmpty();} 22 } 23 ............ 24 }
然而ConcurrentHashMap的实现细节远没有这么简单,因此性能也要高上许多。它没有使用一个全局锁来锁住自己,而是采用了减少锁粒度的方法,尽量减少因为竞争锁而导致的阻塞与冲突,而且ConcurrentHashMap的检索操作是不需要锁的。
在Java 7中,ConcurrentHashMap把内部细分成了若干个小的HashMap,称之为段(Segment),默认被分为16个段。对于一个写操作而言,会先根据hash code进行寻址,得出该Entry应被存放在哪一个Segment,然后只要对该Segment加锁即可。理想情况下,一个默认的ConcurrentHashMap可以同时接受16个线程进行写操作(如果都是对不同Segment进行操作的话)。分段锁对于size()这样的全局操作来说就没有任何作用了,想要得出Entry的数量就需要遍历所有Segment,获得所有的锁,然后再统计总数。事实上,ConcurrentHashMap会先试图使用无锁的方式统计总数,这个尝试会进行3次,如果在相邻的2次计算中获得的Segment的modCount次数一致,代表这两次计算过程中都没有发生过修改操作,那么就可以当做最终结果返回,否则,就要获得所有Segment的锁,重新计算size。
而Java 8的ConcurrentHashMap,它与Java 7的实现差别较大。完全放弃了段的设计,而是变回与HashMap相似的设计,使用buckets数组与分离链接法(同样会在超过阈值时树化,对于构造红黑树的逻辑与HashMap差别不大,只不过需要额外使用CAS来保证线程安全),锁的粒度也被细分到每个数组元素(因为HashMap在Java 8中也实现了不少优化,即使碰撞严重,也能保证一定的性能,而且Segment不仅臃肿还有弱一致性的问题存在),所以它的并发级别与数组长度相关(Java 7则是与段数相关)。
3.1、ConcurrentHashMap散列函数
ConcurrentHashMap的散列函数与HashMap并没有什么区别,同样是把key的hash code的高16位与低16位进行异或运算(因为ConcurrentHashMap的buckets数组长度也永远是一个2的N次方),然后将扰乱后的hash code与数组的长度减一(实际可访问到的最大索引)进行与运算,得出的结果即是目标所在的位置。
1 // 2^31 - 1,int类型的最大值 2 // 该掩码表示节点hash的可用位,用来保证hash永远为一个正整数 3 static final int HASH_BITS = 0x7fffffff; 4 static final int spread(int h) { 5 return (h ^ (h >>> 16)) & HASH_BITS; 6 }
3.2、查找操作
下面是查找操作的源码:
1 public V get(Object key) { 2 Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek; 3 int h = spread(key.hashCode()); 4 if ((tab = table) != null && (n = tab.length) > 0 && 5 (e = tabAt(tab, (n - 1) & h)) != null) { 6 if ((eh = e.hash) == h) { 7 // 先尝试判断链表头是否为目标,如果是就直接返回 8 if ((ek = e.key) == key || (ek != null && key.equals(ek))) 9 return e.val; 10 } 11 else if (eh < 0) 12 // eh < 0代表这是一个特殊节点(TreeBin或ForwardingNode) 13 // 所以直接调用find()进行遍历查找 14 return (p = e.find(h, key)) != null ? p.val : null; 15 // 遍历链表 16 while ((e = e.next) != null) { 17 if (e.hash == h && 18 ((ek = e.key) == key || (ek != null && key.equals(ek)))) 19 return e.val; 20 } 21 } 22 return null; 23 }
一个普通的节点(链表节点)的hash不可能小于0(已经在spread()函数中修正过了),所以小于0的只可能是一个特殊节点,它不能用while循环中遍历链表的方式来进行遍历。TreeBin是红黑树的头部节点(红黑树的节点为TreeNode),它本身不含有key与value,而是指向一个TreeNode节点的链表与它们的根节点,同时使用CAS实现了一个读写锁,迫使Writer(持有这个锁)在树重构操作之前等待Reader完成。ForwardingNode是一个在数据转移过程(由扩容引起)中使用的临时节点,它会被插入到头部。它与TreeBin(和TreeNode)都是Node类的子类,为了判断出哪些是特殊节点,TreeBin和ForwardingNode的hash域都只是一个虚拟值:
1 static class Node<K,V> implements Map.Entry<K,V> { 2 final int hash; 3 final K key; 4 volatile V val; 5 volatile Node<K,V> next; 6 Node(int hash, K key, V val, Node<K,V> next) { 7 this.hash = hash; 8 this.key = key; 9 this.val = val; 10 this.next = next; 11 } 12 public final V setValue(V value) { 13 throw new UnsupportedOperationException(); 14 } 15 ...... 16 /** 17 * Virtualized support for map.get(); overridden in subclasses. 18 */ 19 Node<K,V> find(int h, Object k) { 20 Node<K,V> e = this; 21 if (k != null) { 22 do { 23 K ek; 24 if (e.hash == h && 25 ((ek = e.key) == k || (ek != null && k.equals(ek)))) 26 return e; 27 } while ((e = e.next) != null); 28 } 29 return null; 30 } 31 } 32 /* 33 * Encodings for Node hash fields. See above for explanation. 34 */ 35 static final int MOVED = -1; // hash for forwarding nodes 36 static final int TREEBIN = -2; // hash for roots of trees 37 static final int RESERVED = -3; // hash for transient reservations 38 static final class TreeBin<K,V> extends Node<K,V> { 39 .... 40 TreeBin(TreeNode<K,V> b) { 41 super(TREEBIN, null, null, null); 42 .... 43 } 44 45 .... 46 } 47 static final class ForwardingNode<K,V> extends Node<K,V> { 48 final Node<K,V>[] nextTable; 49 ForwardingNode(Node<K,V>[] tab) { 50 super(MOVED, null, null, null); 51 this.nextTable = tab; 52 } 53 ..... 54 }
我们在get()函数中并没有发现任何与锁相关的代码,那么它是怎么保证线程安全的呢?一个操作ConcurrentHashMap.get("a"),它的步骤基本分为以下几步:
1 根据散列函数计算出的索引访问table。 2 从table中取出头节点。 3 遍历头节点直到找到目标节点。 4 从目标节点中取出value并返回。
所以只要保证访问table与节点的操作总是能够返回最新的数据就可以了。ConcurrentHashMap并没有采用锁的方式,而是通过volatile关键字来保证它们的可见性。在代码中可以发现,table、Node.val和Node.next都是被volatile关键字所修饰的。
volatile关键字保证了多线程环境下变量的可见性与有序性,底层实现基于内存屏障(Memory Barrier)。为了优化性能,现代CPU工作时的指令执行顺序与应用程序的代码顺序其实是不一致的(有些编译器也会进行这种优化),也就是所谓的乱序执行技术。乱序执行可以提高CPU流水线的工作效率,只要保证数据符合程序逻辑上的正确性即可(遵循happens-before原则)。不过如今是多核时代,如果随便乱序而不提供防护措施那是会出问题的。每一个cpu上都会进行乱序优化,单cpu所保证的逻辑次序可能会被其他cpu所破坏。内存屏障就是针对此情况的防护措施。可以认为它是一个同步点(但它本身也是一条cpu指令)。例如在IA32指令集架构中引入的SFENCE指令,在该指令之前的所有写操作必须全部完成,读操作仍可以乱序执行。LFENCE指令则保证之前的所有读操作必须全部完成,另外还有粒度更粗的MFENCE指令保证之前的所有读写操作都必须全部完成。内存屏障就像是一个保护指令顺序的栅栏,保护后面的指令不被前面的指令跨越。将内存屏障插入到写操作与读操作之间,就可以保证之后的读操作可以访问到最新的数据,因为屏障前的写操作已经把数据写回到内存(根据缓存一致性协议,不会直接写回到内存,而是改变该cpu私有缓存中的状态,然后通知给其他cpu这个缓存行已经被修改过了,之后另一个cpu在读操作时就可以发现该缓存行已经是无效的了,这时它会从其他cpu中读取最新的缓存行,然后之前的cpu才会更改状态并写回到内存)。
例如,读一个被volatile修饰的变量V总是能够从JMM(Java Memory Model)主内存中获得最新的数据。因为内存屏障的原因,每次在使用变量V(通过JVM指令use,后面说的也都是JVM中的指令而不是cpu)之前都必须先执行load指令(把从主内存中得到的数据放入到工作内存),根据JVM的规定,load指令必须发生在read指令(从主内存中读取数据)之后,所以每次访问变量V都会先从主内存中读取。相对的,写操作也因为内存屏障保证的指令顺序,每次都会直接写回到主内存。不过volatile关键字并不能保证操作的原子性,对该变量进行并发的连续操作是非线程安全的,所幸ConcurrentHashMap只是用来确保访问到的变量是最新的,所以也不会发生什么问题。
出于性能考虑,Doug Lea(java.util.concurrent包的作者)直接通过Unsafe类来对table进行操作。Java号称是安全的编程语言,而保证安全的代价就是牺牲程序员自由操控内存的能力。像在C/C++中可以通过操作指针变量达到操作内存的目的(其实操作的是虚拟地址),但这种灵活性在新手手中也经常会带来一些愚蠢的错误,比如内存访问越界。Unsafe从字面意思可以看出是不安全的,它包含了许多本地方法(在JVM平台上运行的其他语言编写的程序,主要为C/C++,由JNI实现),这些方法支持了对指针的操作,所以它才被称为是不安全的。虽然不安全,但毕竟是由C/C++实现的,像一些与操作系统交互的操作肯定是快过Java的,毕竟Java与操作系统之间还隔了一层抽象(JVM),不过代价就是失去了JVM所带来的多平台可移植性(本质上也只是一个c/cpp文件,如果换了平台那就要重新编译)。
对table进行操作的函数有以下三个,都使用到了Unsafe(在java.util.concurrent包随处可见):
1 @SuppressWarnings("unchecked") 2 static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) { 3 // 从tab数组中获取一个引用,遵循Volatile语义 4 // 参数2是一个在tab中的偏移量,用来寻找目标对象 5 return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE); 6 } 7 static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i, 8 Node<K,V> c, Node<K,V> v) { 9 // 通过CAS操作将tab数组中位于参数2偏移量位置的值替换为v 10 // c是期望值,如果期望值与实际值不符,返回false 11 // 否则,v会成功地被设置到目标位置,返回true 12 return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v); 13 } 14 static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) { 15 // 设置tab数组中位于参数2偏移量位置的值,遵循Volatile语义 16 U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v); 17 } 18 初始化:ConcurrentHashMap与HashMap一样是Lazy的,buckets数组会在第一次访问put()函数时进行初始化,它的默认构造函数甚至是个空函数。 19 /** 20 * Creates a new, empty map with the default initial table size (16). 21 */ 22 public ConcurrentHashMap() { 23 }
但是有一点需要注意,ConcurrentHashMap是工作在多线程并发环境下的,如果有多个线程同时调用了put()函数该怎么办?这会导致重复初始化,所以必须要有对应的防护措施。ConcurrentHashMap声明了一个用于控制table的初始化与扩容的实例变量sizeCtl,默认值为0。当它是一个负数的时候,代表table正处于初始化或者扩容的状态。-1表示table正在进行初始化,-N则表示当前有N-1个线程正在进行扩容。在其他情况下,如果table还未初始化(table == null),sizeCtl表示table进行初始化的数组大小(所以从构造函数传入的initialCapacity在经过计算后会被赋给它)。如果table已经初始化过了,则表示下次触发扩容操作的阈值,算法stzeCtl = n - (n >>> 2),也就是n的75%,与默认负载因子(0.75)的HashMap一致。
private transient volatile int sizeCtl;
初始化table的操作位于函数initTable(),源码如下:
1 /** 2 * Initializes table, using the size recorded in sizeCtl. 3 */ 4 private final Node<K,V>[] initTable() { 5 Node<K,V>[] tab; int sc; 6 while ((tab = table) == null || tab.length == 0) { 7 // sizeCtl小于0,这意味着已经有其他线程进行初始化了 8 // 所以当前线程让出CPU时间片 9 if ((sc = sizeCtl) < 0) 10 Thread.yield(); // lost initialization race; just spin 11 // 否则,通过CAS操作尝试修改sizeCtl 12 else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { 13 try { 14 if ((tab = table) == null || tab.length == 0) { 15 // 默认构造函数,sizeCtl = 0,使用默认容量(16)进行初始化 16 // 否则,会根据sizeCtl进行初始化 17 int n = (sc > 0) ? sc : DEFAULT_CAPACITY; 18 @SuppressWarnings("unchecked") 19 Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n]; 20 table = tab = nt; 21 // 计算阈值,n的75% 22 sc = n - (n >>> 2); 23 } 24 } finally { 25 // 阈值赋给sizeCtl 26 sizeCtl = sc; 27 } 28 break; 29 } 30 } 31 return tab; 32 }
sizeCtl是一个volatile变量,只要有一个线程CAS操作成功,sizeCtl就会被暂时地修改为-1,这样其他线程就能够根据sizeCtl得知table是否已经处于初始化状态中,最后sizeCtl会被设置成阈值,用于触发扩容操作。
3.3、扩容
ConcurrentHashMap触发扩容的时机与HashMap类似,要么是在将链表转换成红黑树时判断table数组的长度是否小于阈值(64),如果小于就进行扩容而不是树化,要么就是在添加元素的时候,判断当前Entry数量是否超过阈值,如果超过就进行扩容。
1 private final void treeifyBin(Node<K,V>[] tab, int index) { 2 Node<K,V> b; int n, sc; 3 if (tab != null) { 4 // 小于MIN_TREEIFY_CAPACITY,进行扩容 5 if ((n = tab.length) < MIN_TREEIFY_CAPACITY) 6 tryPresize(n << 1); 7 else if ((b = tabAt(tab, index)) != null && b.hash >= 0) { 8 synchronized (b) { 9 // 将链表转换成红黑树... 10 } 11 } 12 } 13 } 14 ... 15 final V putVal(K key, V value, boolean onlyIfAbsent) { 16 ... 17 addCount(1L, binCount); // 计数 18 return null; 19 } 20 private final void addCount(long x, int check) { 21 // 计数... 22 if (check >= 0) { 23 Node<K,V>[] tab, nt; int n, sc; 24 // s(元素个数)大于等于sizeCtl,触发扩容 25 while (s >= (long)(sc = sizeCtl) && (tab = table) != null && 26 (n = tab.length) < MAXIMUM_CAPACITY) { 27 // 扩容标志位 28 int rs = resizeStamp(n); 29 // sizeCtl为负数,代表正有其他线程进行扩容 30 if (sc < 0) { 31 // 扩容已经结束,中断循环 32 if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 || 33 sc == rs + MAX_RESIZERS || (nt = nextTable) == null || 34 transferIndex <= 0) 35 break; 36 // 进行扩容,并设置sizeCtl,表示扩容线程 + 1 37 if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) 38 transfer(tab, nt); 39 } 40 // 触发扩容(第一个进行扩容的线程) 41 // 并设置sizeCtl告知其他线程 42 else if (U.compareAndSwapInt(this, SIZECTL, sc, 43 (rs << RESIZE_STAMP_SHIFT) + 2)) 44 transfer(tab, null); 45 // 统计个数,用于循环检测是否还需要扩容 46 s = sumCount(); 47 } 48 } 49 }
可以看到有关sizeCtl的操作牵涉到了大量的位运算,我们先来理解这些位运算的意义。首先是resizeStamp(),该函数返回一个用于数据校验的标志位,意思是对长度为n的table进行扩容。它将n的前导零(最高有效位之前的零的数量)和1 << 15做或运算,这时低16位的最高位为1,其他都为n的前导零。
1 static final int resizeStamp(int n) { 2 // RESIZE_STAMP_BITS = 16 3 return Integer.numberOfLeadingZeros(n) | (1 << (RESIZE_STAMP_BITS - 1)); 4 }
初始化sizeCtl(扩容操作被第一个线程首次进行)的算法为(rs << RESIZE_STAMP_SHIFT) + 2,首先RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS = 16,那么rs << 16等于将这个标志位移动到了高16位,这时最高位为1,所以sizeCtl此时是个负数,然后加二(至于为什么是2,还记得有关sizeCtl的说明吗?1代表初始化状态,所以实际的线程个数是要减去1的)代表当前有一个线程正在进行扩容,这样sizeCtl就被分割成了两部分,高16位是一个对n的数据校验的标志位,低16位表示参与扩容操作的线程个数 + 1。可能会有读者有所疑惑,更新进行扩容的线程数量的操作为什么是sc + 1而不是sc - 1,这是因为对sizeCtl的操作都是基于位运算的,所以不会关心它本身的数值是多少,只关心它在二进制上的数值,而sc + 1会在低16位上加1。
tryPresize()函数跟addCount()的后半段逻辑类似,不断地根据sizeCtl判断当前的状态,然后选择对应的策略。
1 private final void tryPresize(int size) { 2 // 对size进行修正 3 int c = (size >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY : 4 tableSizeFor(size + (size >>> 1) + 1); 5 int sc; 6 // sizeCtl是默认值或正整数 7 // 代表table还未初始化 8 // 或还没有其他线程正在进行扩容 9 while ((sc = sizeCtl) >= 0) { 10 Node<K,V>[] tab = table; int n; 11 if (tab == null || (n = tab.length) == 0) { 12 n = (sc > c) ? sc : c; 13 // 设置sizeCtl,告诉其他线程,table现在正处于初始化状态 14 if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { 15 try { 16 if (table == tab) { 17 @SuppressWarnings("unchecked") 18 Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n]; 19 table = nt; 20 // 计算下次触发扩容的阈值 21 sc = n - (n >>> 2); 22 } 23 } finally { 24 // 将阈值赋给sizeCtl 25 sizeCtl = sc; 26 } 27 } 28 } 29 // 没有超过阈值或者大于容量的上限,中断循环 30 else if (c <= sc || n >= MAXIMUM_CAPACITY) 31 break; 32 // 进行扩容,与addCount()后半段的逻辑一致 33 else if (tab == table) { 34 int rs = resizeStamp(n); 35 if (sc < 0) { 36 Node<K,V>[] nt; 37 if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 || 38 sc == rs + MAX_RESIZERS || (nt = nextTable) == null || 39 transferIndex <= 0) 40 break; 41 if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) 42 transfer(tab, nt); 43 } 44 else if (U.compareAndSwapInt(this, SIZECTL, sc, 45 (rs << RESIZE_STAMP_SHIFT) + 2)) 46 transfer(tab, null); 47 } 48 } 49 }
扩容操作的核心在于数据的转移,在单线程环境下数据的转移很简单,无非就是把旧数组中的数据迁移到新的数组。但是这在多线程环境下是行不通的,需要保证线程安全性,在扩容的时候其他线程也可能正在添加元素,这时又触发了扩容怎么办?有人可能会说,用一个互斥锁把数据转移操作的过程锁住不就好了?这确实是一种可行的解决方法,但同样也会带来极差的吞吐量。互斥锁会导致所有访问临界区的线程陷入阻塞状态,这会消耗额外的系统资源,内核需要保存这些线程的上下文并放到阻塞队列,持有锁的线程耗时越长,其他竞争线程就会一直被阻塞,因此吞吐量低下,导致响应时间缓慢。而且锁总是会伴随着死锁问题,一旦发生死锁,整个应用程序都会因此受到影响,所以加锁永远是最后的备选方案。Doug Lea没有选择直接加锁,而是基于CAS实现无锁的并发同步策略,令人佩服的是他不仅没有把其他线程拒之门外,甚至还邀请它们一起来协助工作。那么如何才能让多个线程协同工作呢?Doug Lea把整个table数组当做多个线程之间共享的任务队列,然后只需维护一个指针,当有一个线程开始进行数据转移,就会先移动指针,表示指针划过的这片bucket区域由该线程负责。这个指针被声明为一个volatile整型变量,它的初始位置位于table的尾部,即它等于table.length,很明显这个任务队列是逆向遍历的。
1 /** 2 * The next table index (plus one) to split while resizing. 3 */ 4 private transient volatile int transferIndex; 5 /** 6 * 一个线程需要负责的最小bucket数 7 */ 8 private static final int MIN_TRANSFER_STRIDE = 16; 9 10 /** 11 * The next table to use; non-null only while resizing. 12 */ 13 private transient volatile Node<K,V>[] nextTable;
一个已经迁移完毕的bucket会被替换成ForwardingNode节点,用来标记此bucket已经被其他线程迁移完毕了。ForwardingNode是一个特殊节点,可以通过hash域的虚拟值来识别它,它同样重写了find()函数,用来在新数组中查找目标。数据迁移的操作位于transfer()函数,多个线程之间依靠sizeCtl与transferIndex指针来协同工作,每个线程都有自己负责的区域,一个完成迁移的bucket会被设置为ForwardingNode,其他线程遇见这个特殊节点就跳过该bucket,处理下一个bucket。transfer()函数可以大致分为三部分,第一部分对后续需要使用的变量进行初始化:
1 /** 2 * Moves and/or copies the nodes in each bin to new table. See 3 * above for explanation. 4 */ 5 private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) { 6 int n = tab.length, stride; 7 // 根据当前机器的CPU数量来决定每个线程负责的bucket数 8 // 避免因为扩容线程过多,反而影响到性能 9 if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE) 10 stride = MIN_TRANSFER_STRIDE; // subdivide range 11 // 初始化nextTab,容量为旧数组的一倍 12 if (nextTab == null) { // initiating 13 try { 14 @SuppressWarnings("unchecked") 15 Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1]; 16 nextTab = nt; 17 } catch (Throwable ex) { // try to cope with OOME 18 sizeCtl = Integer.MAX_VALUE; 19 return; 20 } 21 nextTable = nextTab; 22 transferIndex = n; // 初始化指针 23 } 24 int nextn = nextTab.length; 25 ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab); 26 boolean advance = true; 27 boolean finishing = false; // to ensure sweep before committing nextTab
第二部分为当前线程分配任务和控制当前线程的任务进度,这部分是transfer()的核心逻辑,描述了如何与其他线程协同工作:
1 // i指向当前bucket,bound表示当前线程所负责的bucket区域的边界 2 for (int i = 0, bound = 0;;) { 3 Node<K,V> f; int fh; 4 // 这个循环使用CAS不断尝试为当前线程分配任务 5 // 直到分配成功或任务队列已经被全部分配完毕 6 // 如果当前线程已经被分配过bucket区域 7 // 那么会通过--i指向下一个待处理bucket然后退出该循环 8 while (advance) { 9 int nextIndex, nextBound; 10 // --i表示将i指向下一个待处理的bucket 11 // 如果--i >= bound,代表当前线程已经分配过bucket区域 12 // 并且还留有未处理的bucket 13 if (--i >= bound || finishing) 14 advance = false; 15 // transferIndex指针 <= 0 表示所有bucket已经被分配完毕 16 else if ((nextIndex = transferIndex) <= 0) { 17 i = -1; 18 advance = false; 19 } 20 // 移动transferIndex指针 21 // 为当前线程设置所负责的bucket区域的范围 22 // i指向该范围的第一个bucket,注意i是逆向遍历的 23 // 这个范围为(bound, i),i是该区域最后一个bucket,遍历顺序是逆向的 24 else if (U.compareAndSwapInt 25 (this, TRANSFERINDEX, nextIndex, 26 nextBound = (nextIndex > stride ? 27 nextIndex - stride : 0))) { 28 bound = nextBound; 29 i = nextIndex - 1; 30 advance = false; 31 } 32 } 33 // 当前线程已经处理完了所负责的所有bucket 34 if (i < 0 || i >= n || i + n >= nextn) { 35 int sc; 36 // 如果任务队列已经全部完成 37 if (finishing) { 38 nextTable = null; 39 table = nextTab; 40 // 设置新的阈值 41 sizeCtl = (n << 1) - (n >>> 1); 42 return; 43 } 44 // 工作中的扩容线程数量减1 45 if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) { 46 // (resizeStamp << RESIZE_STAMP_SHIFT) + 2代表当前有一个扩容线程 47 // 相对的,(sc - 2) != resizeStamp << RESIZE_STAMP_SHIFT 48 // 表示当前还有其他线程正在进行扩容,所以直接返回 49 if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT) 50 return; 51 // 否则,当前线程就是最后一个进行扩容的线程 52 // 设置finishing标识 53 finishing = advance = true; 54 i = n; // recheck before commit 55 } 56 } 57 // 如果待处理bucket是空的 58 // 那么插入ForwardingNode,以通知其他线程 59 else if ((f = tabAt(tab, i)) == null) 60 advance = casTabAt(tab, i, null, fwd); 61 // 如果待处理bucket的头节点是ForwardingNode 62 // 说明此bucket已经被处理过了,跳过该bucket 63 else if ((fh = f.hash) == MOVED) 64 advance = true; // already processed
最后一部分是具体的迁移过程(对当前指向的bucket),这部分的逻辑与HashMap类似,拿旧数组的容量当做一个掩码,然后与节点的hash进行与操作,可以得出该节点的新增有效位,如果新增有效位为0就放入一个链表A,如果为1就放入另一个链表B,链表A在新数组中的位置不变(跟在旧数组的索引一致),链表B在新数组中的位置为原索引加上旧数组容量。这个方法减少了rehash的计算量,而且还能达到均匀分布的目的。
1 else { 2 // 对于节点的操作还是要加上锁的 3 // 不过这个锁的粒度很小,只锁住了bucket的头节点 4 synchronized (f) { 5 if (tabAt(tab, i) == f) { 6 Node<K,V> ln, hn; 7 // hash code不为负,代表这是条链表 8 if (fh >= 0) { 9 // fh & n 获得hash code的新增有效位,用于将链表分离成两类 10 // 要么是0要么是1,关于这个位运算的更多细节 11 // 请看本文中有关HashMap扩容操作的解释 12 int runBit = fh & n; 13 Node<K,V> lastRun = f; 14 // 这个循环用于记录最后一段连续的同一类节点 15 // 这个类别是通过fh & n来区分的 16 // 这段连续的同类节点直接被复用,不会产生额外的复制 17 for (Node<K,V> p = f.next; p != null; p = p.next) { 18 int b = p.hash & n; 19 if (b != runBit) { 20 runBit = b; 21 lastRun = p; 22 } 23 } 24 // 0被放入ln链表,1被放入hn链表 25 // lastRun是连续同类节点的起始节点 26 if (runBit == 0) { 27 ln = lastRun; 28 hn = null; 29 } 30 else { 31 hn = lastRun; 32 ln = null; 33 } 34 // 将最后一段的连续同类节点之前的节点按类别复制到ln或hn 35 // 链表的插入方向是往头部插入的,Node构造函数的第四个参数是next 36 // 所以就算遇到类别与lastRun一致的节点也只会被插入到头部 37 for (Node<K,V> p = f; p != lastRun; p = p.next) { 38 int ph = p.hash; K pk = p.key; V pv = p.val; 39 if ((ph & n) == 0) 40 ln = new Node<K,V>(ph, pk, pv, ln); 41 else 42 hn = new Node<K,V>(ph, pk, pv, hn); 43 } 44 // ln链表被放入到原索引位置,hn放入到原索引 + 旧数组容量 45 // 这一点与HashMap一致,如果看不懂请去参考本文对HashMap扩容的讲解 46 setTabAt(nextTab, i, ln); 47 setTabAt(nextTab, i + n, hn); 48 setTabAt(tab, i, fwd); // 标记该bucket已被处理 49 advance = true; 50 } 51 // 对红黑树的操作,逻辑与链表一样,按新增有效位进行分类 52 else if (f instanceof TreeBin) { 53 TreeBin<K,V> t = (TreeBin<K,V>)f; 54 TreeNode<K,V> lo = null, loTail = null; 55 TreeNode<K,V> hi = null, hiTail = null; 56 int lc = 0, hc = 0; 57 for (Node<K,V> e = t.first; e != null; e = e.next) { 58 int h = e.hash; 59 TreeNode<K,V> p = new TreeNode<K,V> 60 (h, e.key, e.val, null, null); 61 if ((h & n) == 0) { 62 if ((p.prev = loTail) == null) 63 lo = p; 64 else 65 loTail.next = p; 66 loTail = p; 67 ++lc; 68 } 69 else { 70 if ((p.prev = hiTail) == null) 71 hi = p; 72 else 73 hiTail.next = p; 74 hiTail = p; 75 ++hc; 76 } 77 } 78 // 元素数量没有超过UNTREEIFY_THRESHOLD,退化成链表 79 ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) : 80 (hc != 0) ? new TreeBin<K,V>(lo) : t; 81 hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) : 82 (lc != 0) ? new TreeBin<K,V>(hi) : t; 83 setTabAt(nextTab, i, ln); 84 setTabAt(nextTab, i + n, hn); 85 setTabAt(tab, i, fwd); 86 advance = true; 87 }
3.4、计数
在Java 7中ConcurrentHashMap对每个Segment单独计数,想要得到总数就需要获得所有Segment的锁,然后进行统计。由于Java 8抛弃了Segment,显然是不能再这样做了,而且这种方法虽然简单准确但也舍弃了性能。Java 8声明了一个volatile变量baseCount用于记录元素的个数,对这个变量的修改操作是基于CAS的,每当插入元素或删除元素时都会调用addCount()函数进行计数。
1 private transient volatile long baseCount; 2 private final void addCount(long x, int check) { 3 CounterCell[] as; long b, s; 4 // 尝试使用CAS更新baseCount失败 5 // 转用CounterCells进行更新 6 if ((as = counterCells) != null || 7 !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) { 8 CounterCell a; long v; int m; 9 boolean uncontended = true; 10 // 在CounterCells未初始化 11 // 或尝试通过CAS更新当前线程的CounterCell失败时 12 // 调用fullAddCount(),该函数负责初始化CounterCells和更新计数 13 if (as == null || (m = as.length - 1) < 0 || 14 (a = as[ThreadLocalRandom.getProbe() & m]) == null || 15 !(uncontended = 16 U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) { 17 fullAddCount(x, uncontended); 18 return; 19 } 20 if (check <= 1) 21 return; 22 // 统计总数 23 s = sumCount(); 24 } 25 if (check >= 0) { 26 // 判断是否需要扩容,在上文中已经讲过了 27 } 28 }
counterCells是一个元素为CounterCell的数组,该数组的大小与当前机器的CPU数量有关,并且它不会被主动初始化,只有在调用fullAddCount()函数时才会进行初始化。CounterCell是一个简单的内部静态类,每个CounterCell都是一个用于记录数量的单元:
1 /** 2 * Table of counter cells. When non-null, size is a power of 2. 3 */ 4 private transient volatile CounterCell[] counterCells; 5 /** 6 * A padded cell for distributing counts. Adapted from LongAdder 7 * and Striped64. See their internal docs for explanation. 8 */ 9 @sun.misc.Contended static final class CounterCell { 10 volatile long value; 11 CounterCell(long x) { value = x; } 12 }
注解@sun.misc.Contended用于解决伪共享问题。所谓伪共享,即是在同一缓存行(CPU缓存的基本单位)中存储了多个变量,当其中一个变量被修改时,就会影响到同一缓存行内的其他变量,导致它们也要跟着被标记为失效,其他变量的缓存命中率将会受到影响。解决伪共享问题的方法一般是对该变量填充一些无意义的占位数据,从而使它独享一个缓存行。
ConcurrentHashMap的计数设计与LongAdder类似。在一个低并发的情况下,就只是简单地使用CAS操作来对baseCount进行更新,但只要这个CAS操作失败一次,就代表有多个线程正在竞争,那么就转而使用CounterCell数组进行计数,数组内的每个ConuterCell都是一个独立的计数单元。每个线程都会通过ThreadLocalRandom.getProbe() & m寻址找到属于它的CounterCell,然后进行计数。ThreadLocalRandom是一个线程私有的伪随机数生成器,每个线程的probe都是不同的(这点基于ThreadLocalRandom的内部实现,它在内部维护了一个probeGenerator,这是一个类型为AtomicInteger的静态常量,每当初始化一个ThreadLocalRandom时probeGenerator都会先自增一个常量然后返回的整数即为当前线程的probe,probe变量被维护在Thread对象中),可以认为每个线程的probe就是它在CounterCell数组中的hash code。这种方法将竞争数据按照线程的粒度进行分离,相比所有竞争线程对一个共享变量使用CAS不断尝试在性能上要效率好多了,这也是为什么在高并发环境下LongAdder要优于AtomicInteger的原因。
fullAddCount()函数根据当前线程的probe寻找对应的CounterCell进行计数,如果CounterCell数组未被初始化,则初始化CounterCell数组和CounterCell。该函数的实现与Striped64类(LongAdder的父类)的longAccumulate()函数是一样的,把CounterCell数组当成一个散列表,每个线程的probe就是hash code,散列函数也仅仅是简单的(n - 1) & probe。CounterCell数组的大小永远是一个2的n次方,初始容量为2,每次扩容的新容量都是之前容量乘以二,处于性能考虑,它的最大容量上限是机器的CPU数量。所以说CounterCell数组的碰撞冲突是很严重的,因为它的bucket基数太小了。而发生碰撞就代表着一个CounterCell会被多个线程竞争,为了解决这个问题,Doug Lea使用无限循环加上CAS来模拟出一个自旋锁来保证线程安全,自旋锁的实现基于一个被volatile修饰的整数变量,该变量只会有两种状态:0和1,当它被设置为0时表示没有加锁,当它被设置为1时表示已被其他线程加锁。这个自旋锁用于保护初始化CounterCell、初始化CounterCell数组以及对CounterCell数组进行扩容时的安全。CounterCell更新计数是依赖于CAS的,每次循环都会尝试通过CAS进行更新,如果成功就退出无限循环,否则就调用ThreadLocalRandom.advanceProbe()函数为当前线程更新probe,然后重新开始循环,以期望下一次寻址到的CounterCell没有被其他线程竞争。如果连着两次CAS更新都没有成功,那么会对CounterCell数组进行一次扩容,这个扩容操作只会在当前循环中触发一次,而且只能在容量小于上限时触发。
fullAddCount()函数的主要流程如下:
1 首先检查当前线程有没有初始化过ThreadLocalRandom,如果没有则进行初始化。ThreadLocalRandom负责更新线程的probe,而probe又是在数组中进行寻址的关键。 2 检查CounterCell数组是否已经初始化,如果已初始化,那么就根据probe找到对应的CounterCell。 3 如果这个CounterCell等于null,需要先初始化CounterCell,通过把计数增量传入构造函数,所以初始化只要成功就说明更新计数已经完成了。初始化的过程需要获取自旋锁。 4 如果不为null,就按上文所说的逻辑对CounterCell实施更新计数。 5 CounterCell数组未被初始化,尝试获取自旋锁,进行初始化。数组初始化的过程会附带初始化一个CounterCell来记录计数增量,所以只要初始化成功就表示更新计数完成。 6 如果自旋锁被其他线程占用,无法进行数组的初始化,只好通过CAS更新baseCount。
1 private final void fullAddCount(long x, boolean wasUncontended) { 2 int h; 3 // 当前线程的probe等于0,证明该线程的ThreadLocalRandom还未被初始化 4 // 以及当前线程是第一次进入该函数 5 if ((h = ThreadLocalRandom.getProbe()) == 0) { 6 // 初始化ThreadLocalRandom,当前线程会被设置一个probe 7 ThreadLocalRandom.localInit(); // force initialization 8 // probe用于在CounterCell数组中寻址 9 h = ThreadLocalRandom.getProbe(); 10 // 未竞争标志 11 wasUncontended = true; 12 } 13 // 冲突标志 14 boolean collide = false; // True if last slot nonempty 15 for (;;) { 16 CounterCell[] as; CounterCell a; int n; long v; 17 // CounterCell数组已初始化 18 if ((as = counterCells) != null && (n = as.length) > 0) { 19 // 如果寻址到的Cell为空,那么创建一个新的Cell 20 if ((a = as[(n - 1) & h]) == null) { 21 // cellsBusy是一个只有0和1两个状态的volatile整数 22 // 它被当做一个自旋锁,0代表无锁,1代表加锁 23 if (cellsBusy == 0) { // Try to attach new Cell 24 // 将传入的x作为初始值创建一个新的CounterCell 25 CounterCell r = new CounterCell(x); // Optimistic create 26 // 通过CAS尝试对自旋锁加锁 27 if (cellsBusy == 0 && 28 U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { 29 // 加锁成功,声明Cell是否创建成功的标志 30 boolean created = false; 31 try { // Recheck under lock 32 CounterCell[] rs; int m, j; 33 // 再次检查CounterCell数组是否不为空 34 // 并且寻址到的Cell为空 35 if ((rs = counterCells) != null && 36 (m = rs.length) > 0 && 37 rs[j = (m - 1) & h] == null) { 38 // 将之前创建的新Cell放入数组 39 rs[j] = r; 40 created = true; 41 } 42 } finally { 43 // 释放锁 44 cellsBusy = 0; 45 } 46 // 如果已经创建成功,中断循环 47 // 因为新Cell的初始值就是传入的增量,所以计数已经完毕了 48 if (created) 49 break; 50 // 如果未成功 51 // 代表as[(n - 1) & h]这个位置的Cell已经被其他线程设置 52 // 那么就从循环头重新开始 53 continue; // Slot is now non-empty 54 } 55 } 56 collide = false; 57 } 58 // as[(n - 1) & h]非空 59 // 在addCount()函数中通过CAS更新当前线程的Cell进行计数失败 60 // 会传入wasUncontended = false,代表已经有其他线程进行竞争 61 else if (!wasUncontended) // CAS already known to fail 62 // 设置未竞争标志,之后会重新计算probe,然后重新执行循环 63 wasUncontended = true; // Continue after rehash 64 // 尝试进行计数,如果成功,那么就退出循环 65 else if (U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x)) 66 break; 67 // 尝试更新失败,检查counterCell数组是否已经扩容 68 // 或者容量达到最大值(CPU的数量) 69 else if (counterCells != as || n >= NCPU) 70 // 设置冲突标志,防止跳入下面的扩容分支 71 // 之后会重新计算probe 72 collide = false; // At max size or stale 73 // 设置冲突标志,重新执行循环 74 // 如果下次循环执行到该分支,并且冲突标志仍然为true 75 // 那么会跳过该分支,到下一个分支进行扩容 76 else if (!collide) 77 collide = true; 78 // 尝试加锁,然后对counterCells数组进行扩容 79 else if (cellsBusy == 0 && 80 U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { 81 try { 82 // 检查是否已被扩容 83 if (counterCells == as) {// Expand table unless stale 84 // 新数组容量为之前的1倍 85 CounterCell[] rs = new CounterCell[n << 1]; 86 // 迁移数据到新数组 87 for (int i = 0; i < n; ++i) 88 rs[i] = as[i]; 89 counterCells = rs; 90 } 91 } finally { 92 // 释放锁 93 cellsBusy = 0; 94 } 95 collide = false; 96 // 重新执行循环 97 continue; // Retry with expanded table 98 } 99 // 为当前线程重新计算probe 100 h = ThreadLocalRandom.advanceProbe(h); 101 } 102 // CounterCell数组未初始化,尝试获取自旋锁,然后进行初始化 103 else if (cellsBusy == 0 && counterCells == as && 104 U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { 105 boolean init = false; 106 try { // Initialize table 107 if (counterCells == as) { 108 // 初始化CounterCell数组,初始容量为2 109 CounterCell[] rs = new CounterCell[2]; 110 // 初始化CounterCell 111 rs[h & 1] = new CounterCell(x); 112 counterCells = rs; 113 init = true; 114 } 115 } finally { 116 cellsBusy = 0; 117 } 118 // 初始化CounterCell数组成功,退出循环 119 if (init) 120 break; 121 } 122 // 如果自旋锁被占用,则只好尝试更新baseCount 123 else if (U.compareAndSwapLong(this, BASECOUNT, v = baseCount, v + x)) 124 break; // Fall back on using base 125 } 126 }
对于统计总数,只要能够理解CounterCell的思想,就很简单了。仔细想一想,每次计数的更新都会被分摊在baseCount和CounterCell数组中的某一CounterCell,想要获得总数,把它们统计相加就是了。
1 public int size() { 2 long n = sumCount(); 3 return ((n < 0L) ? 0 : 4 (n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE : 5 (int)n); 6 } 7 final long sumCount() { 8 CounterCell[] as = counterCells; CounterCell a; 9 long sum = baseCount; 10 if (as != null) { 11 for (int i = 0; i < as.length; ++i) { 12 if ((a = as[i]) != null) 13 sum += a.value; 14 } 15 } 16 return sum; 17 }
其实size()函数返回的总数可能并不是百分百精确的,试想如果前一个遍历过的CounterCell又进行了更新会怎么样?尽管只是一个估算值,但在大多数场景下都还能接受,而且性能上是要比Java 7好上太多了。
3.5、其他操作
添加元素的主要逻辑与HashMap没什么区别,所以整体来说putVal()函数还是比较简单的,可能唯一需要注意的就是在对节点进行操作的时候需要通过互斥锁保证线程安全,这个互斥锁的粒度很小,只对需要操作的这个bucket加锁。
1 public V put(K key, V value) { 2 return putVal(key, value, false); 3 } 4 /** Implementation for put and putIfAbsent */ 5 final V putVal(K key, V value, boolean onlyIfAbsent) { 6 if (key == null || value == null) throw new NullPointerException(); 7 int hash = spread(key.hashCode()); 8 int binCount = 0; // 节点计数器,用于判断是否需要树化 9 // 无限循环+CAS,无锁的标准套路 10 for (Node<K,V>[] tab = table;;) { 11 Node<K,V> f; int n, i, fh; 12 // 初始化table 13 if (tab == null || (n = tab.length) == 0) 14 tab = initTable(); 15 // bucket为null,通过CAS创建头节点,如果成功就结束循环 16 else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { 17 if (casTabAt(tab, i, null, 18 new Node<K,V>(hash, key, value, null))) 19 break; // no lock when adding to empty bin 20 } 21 // bucket为ForwardingNode 22 // 当前线程前去协助进行扩容 23 else if ((fh = f.hash) == MOVED) 24 tab = helpTransfer(tab, f); 25 else { 26 V oldVal = null; 27 synchronized (f) { 28 if (tabAt(tab, i) == f) { 29 // 节点是链表 30 if (fh >= 0) { 31 binCount = 1; 32 for (Node<K,V> e = f;; ++binCount) { 33 K ek; 34 // 找到目标,设置value 35 if (e.hash == hash && 36 ((ek = e.key) == key || 37 (ek != null && key.equals(ek)))) { 38 oldVal = e.val; 39 if (!onlyIfAbsent) 40 e.val = value; 41 break; 42 } 43 Node<K,V> pred = e; 44 // 未找到节点,插入新节点到链表尾部 45 if ((e = e.next) == null) { 46 pred.next = new Node<K,V>(hash, key, 47 value, null); 48 break; 49 } 50 } 51 } 52 // 节点是红黑树 53 else if (f instanceof TreeBin) { 54 Node<K,V> p; 55 binCount = 2; 56 if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key, 57 value)) != null) { 58 oldVal = p.val; 59 if (!onlyIfAbsent) 60 p.val = value; 61 } 62 } 63 } 64 } 65 // 根据bucket中的节点数决定是否树化 66 if (binCount != 0) { 67 if (binCount >= TREEIFY_THRESHOLD) 68 treeifyBin(tab, i); 69 // oldVal不等于null,说明没有新节点 70 // 所以直接返回,不进行计数 71 if (oldVal != null) 72 return oldVal; 73 break; 74 } 75 } 76 } 77 // 计数 78 addCount(1L, binCount); 79 return null; 80 }
至于删除元素的操作位于函数replaceNode(Object key, V value, Object cv),当table[key].val等于期望值cv时(或cv等于null),更新节点的值为value,如果value等于null,那么删除该节点。
remove()函数通过调用replaceNode(key, null, null)来达成删除目标节点的目的,replaceNode()的具体实现与putVal()没什么差别,只不过对链表的操作有所不同而已。
四、Hashtable介绍
1 和HashMap一样,Hashtable 也是一个散列表,它存储的内容是键值对(key-value)映射。 2 Hashtable 继承于Dictionary,实现了Map、Cloneable、java.io.Serializable接口。 3 Hashtable 的函数都是同步的,这意味着它是线程安全的。它的key、value都不可以为null。 4 此外,Hashtable中的映射不是有序的。
Hashtable的实例有两个参数影响其性能:初始容量和加载因子。容量是哈希表中桶 的数量,初始容量就是哈希表创建时的容量。注意,哈希表的状态为 open:在发生“哈希冲突”的情况下,单个桶会存储多个条目,这些条目必须按顺序搜索。加载因子是对哈希表在其容量自动增加之前可以达到多满的一个尺度。初始容量和加载因子这两个参数只是对该实现的提示。关于何时以及是否调用 rehash 方法的具体细节则依赖于该实现。通常,默认加载因子是 0.75, 这是在时间和空间成本上寻求一种折衷。
1 package java.util; 2 import java.io.*; 3 4 public class Hashtable<K,V> 5 extends Dictionary<K,V> 6 implements Map<K,V>, Cloneable, java.io.Serializable { 7 8 // Hashtable保存key-value的数组。 9 // Hashtable是采用拉链法实现的,每一个Entry本质上是一个单向链表 10 private transient Entry[] table; 11 12 // Hashtable中元素的实际数量 13 private transient int count; 14 15 // 阈值,用于判断是否需要调整Hashtable的容量(threshold = 容量*加载因子) 16 private int threshold; 17 18 // 加载因子 19 private float loadFactor; 20 21 // Hashtable被改变的次数 22 private transient int modCount = 0; 23 24 // 序列版本号 25 private static final long serialVersionUID = 1421746759512286392L; 26 27 // 指定“容量大小”和“加载因子”的构造函数 28 public Hashtable(int initialCapacity, float loadFactor) { 29 if (initialCapacity < 0) 30 throw new IllegalArgumentException("Illegal Capacity: "+ 31 initialCapacity); 32 if (loadFactor <= 0 || Float.isNaN(loadFactor)) 33 throw new IllegalArgumentException("Illegal Load: "+loadFactor); 34 35 if (initialCapacity==0) 36 initialCapacity = 1; 37 this.loadFactor = loadFactor; 38 table = new Entry[initialCapacity]; 39 threshold = (int)(initialCapacity * loadFactor); 40 } 41 42 // 指定“容量大小”的构造函数 43 public Hashtable(int initialCapacity) { 44 this(initialCapacity, 0.75f); 45 } 46 47 // 默认构造函数。 48 public Hashtable() { 49 // 默认构造函数,指定的容量大小是11;加载因子是0.75 50 this(11, 0.75f); 51 } 52 53 // 包含“子Map”的构造函数 54 public Hashtable(Map<? extends K, ? extends V> t) { 55 this(Math.max(2*t.size(), 11), 0.75f); 56 // 将“子Map”的全部元素都添加到Hashtable中 57 putAll(t); 58 } 59 60 public synchronized int size() { 61 return count; 62 } 63 64 public synchronized boolean isEmpty() { 65 return count == 0; 66 } 67 68 // 返回“所有key”的枚举对象 69 public synchronized Enumeration<K> keys() { 70 return this.<K>getEnumeration(KEYS); 71 } 72 73 // 返回“所有value”的枚举对象 74 public synchronized Enumeration<V> elements() { 75 return this.<V>getEnumeration(VALUES); 76 } 77 78 // 判断Hashtable是否包含“值(value)” 79 public synchronized boolean contains(Object value) { 80 // Hashtable中“键值对”的value不能是null, 81 // 若是null的话,抛出异常! 82 if (value == null) { 83 throw new NullPointerException(); 84 } 85 86 // 从后向前遍历table数组中的元素(Entry) 87 // 对于每个Entry(单向链表),逐个遍历,判断节点的值是否等于value 88 Entry tab[] = table; 89 for (int i = tab.length ; i-- > 0 ;) { 90 for (Entry<K,V> e = tab[i] ; e != null ; e = e.next) { 91 if (e.value.equals(value)) { 92 return true; 93 } 94 } 95 } 96 return false; 97 } 98 99 public boolean containsValue(Object value) { 100 return contains(value); 101 } 102 103 // 判断Hashtable是否包含key 104 public synchronized boolean containsKey(Object key) { 105 Entry tab[] = table; 106 int hash = key.hashCode(); 107 // 计算索引值, 108 // % tab.length 的目的是防止数据越界 109 int index = (hash & 0x7FFFFFFF) % tab.length; 110 // 找到“key对应的Entry(链表)”,然后在链表中找出“哈希值”和“键值”与key都相等的元素 111 for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) { 112 if ((e.hash == hash) && e.key.equals(key)) { 113 return true; 114 } 115 } 116 return false; 117 } 118 119 // 返回key对应的value,没有的话返回null 120 public synchronized V get(Object key) { 121 Entry tab[] = table; 122 int hash = key.hashCode(); 123 // 计算索引值, 124 int index = (hash & 0x7FFFFFFF) % tab.length; 125 // 找到“key对应的Entry(链表)”,然后在链表中找出“哈希值”和“键值”与key都相等的元素 126 for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) { 127 if ((e.hash == hash) && e.key.equals(key)) { 128 return e.value; 129 } 130 } 131 return null; 132 } 133 134 // 调整Hashtable的长度,将长度变成原来的(2倍+1) 135 // (01) 将“旧的Entry数组”赋值给一个临时变量。 136 // (02) 创建一个“新的Entry数组”,并赋值给“旧的Entry数组” 137 // (03) 将“Hashtable”中的全部元素依次添加到“新的Entry数组”中 138 protected void rehash() { 139 int oldCapacity = table.length; 140 Entry[] oldMap = table; 141 142 int newCapacity = oldCapacity * 2 + 1; 143 Entry[] newMap = new Entry[newCapacity]; 144 145 modCount++; 146 threshold = (int)(newCapacity * loadFactor); 147 table = newMap; 148 149 for (int i = oldCapacity ; i-- > 0 ;) { 150 for (Entry<K,V> old = oldMap[i] ; old != null ; ) { 151 Entry<K,V> e = old; 152 old = old.next; 153 154 int index = (e.hash & 0x7FFFFFFF) % newCapacity; 155 e.next = newMap[index]; 156 newMap[index] = e; 157 } 158 } 159 } 160 161 // 将“key-value”添加到Hashtable中 162 public synchronized V put(K key, V value) { 163 // Hashtable中不能插入value为null的元素!!! 164 if (value == null) { 165 throw new NullPointerException(); 166 } 167 168 // 若“Hashtable中已存在键为key的键值对”, 169 // 则用“新的value”替换“旧的value” 170 Entry tab[] = table; 171 int hash = key.hashCode(); 172 int index = (hash & 0x7FFFFFFF) % tab.length; 173 for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) { 174 if ((e.hash == hash) && e.key.equals(key)) { 175 V old = e.value; 176 e.value = value; 177 return old; 178 } 179 } 180 181 // 若“Hashtable中不存在键为key的键值对”, 182 // (01) 将“修改统计数”+1 183 modCount++; 184 // (02) 若“Hashtable实际容量” > “阈值”(阈值=总的容量 * 加载因子) 185 // 则调整Hashtable的大小 186 if (count >= threshold) { 187 // Rehash the table if the threshold is exceeded 188 rehash(); 189 190 tab = table; 191 index = (hash & 0x7FFFFFFF) % tab.length; 192 } 193 194 // (03) 将“Hashtable中index”位置的Entry(链表)保存到e中 195 Entry<K,V> e = tab[index]; 196 // (04) 创建“新的Entry节点”,并将“新的Entry”插入“Hashtable的index位置”,并设置e为“新的Entry”的下一个元素(即“新Entry”为链表表头)。 197 tab[index] = new Entry<K,V>(hash, key, value, e); 198 // (05) 将“Hashtable的实际容量”+1 199 count++; 200 return null; 201 } 202 203 // 删除Hashtable中键为key的元素 204 public synchronized V remove(Object key) { 205 Entry tab[] = table; 206 int hash = key.hashCode(); 207 int index = (hash & 0x7FFFFFFF) % tab.length; 208 // 找到“key对应的Entry(链表)” 209 // 然后在链表中找出要删除的节点,并删除该节点。 210 for (Entry<K,V> e = tab[index], prev = null ; e != null ; prev = e, e = e.next) { 211 if ((e.hash == hash) && e.key.equals(key)) { 212 modCount++; 213 if (prev != null) { 214 prev.next = e.next; 215 } else { 216 tab[index] = e.next; 217 } 218 count--; 219 V oldValue = e.value; 220 e.value = null; 221 return oldValue; 222 } 223 } 224 return null; 225 } 226 227 // 将“Map(t)”的中全部元素逐一添加到Hashtable中 228 public synchronized void putAll(Map<? extends K, ? extends V> t) { 229 for (Map.Entry<? extends K, ? extends V> e : t.entrySet()) 230 put(e.getKey(), e.getValue()); 231 } 232 233 // 清空Hashtable 234 // 将Hashtable的table数组的值全部设为null 235 public synchronized void clear() { 236 Entry tab[] = table; 237 modCount++; 238 for (int index = tab.length; --index >= 0; ) 239 tab[index] = null; 240 count = 0; 241 } 242 243 // 克隆一个Hashtable,并以Object的形式返回。 244 public synchronized Object clone() { 245 try { 246 Hashtable<K,V> t = (Hashtable<K,V>) super.clone(); 247 t.table = new Entry[table.length]; 248 for (int i = table.length ; i-- > 0 ; ) { 249 t.table[i] = (table[i] != null) 250 ? (Entry<K,V>) table[i].clone() : null; 251 } 252 t.keySet = null; 253 t.entrySet = null; 254 t.values = null; 255 t.modCount = 0; 256 return t; 257 } catch (CloneNotSupportedException e) { 258 // this shouldn't happen, since we are Cloneable 259 throw new InternalError(); 260 } 261 } 262 263 public synchronized String toString() { 264 int max = size() - 1; 265 if (max == -1) 266 return "{}"; 267 268 StringBuilder sb = new StringBuilder(); 269 Iterator<Map.Entry<K,V>> it = entrySet().iterator(); 270 271 sb.append('{'); 272 for (int i = 0; ; i++) { 273 Map.Entry<K,V> e = it.next(); 274 K key = e.getKey(); 275 V value = e.getValue(); 276 sb.append(key == this ? "(this Map)" : key.toString()); 277 sb.append('='); 278 sb.append(value == this ? "(this Map)" : value.toString()); 279 280 if (i == max) 281 return sb.append('}').toString(); 282 sb.append(", "); 283 } 284 } 285 286 // 获取Hashtable的枚举类对象 287 // 若Hashtable的实际大小为0,则返回“空枚举类”对象; 288 // 否则,返回正常的Enumerator的对象。(Enumerator实现了迭代器和枚举两个接口) 289 private <T> Enumeration<T> getEnumeration(int type) { 290 if (count == 0) { 291 return (Enumeration<T>)emptyEnumerator; 292 } else { 293 return new Enumerator<T>(type, false); 294 } 295 } 296 297 // 获取Hashtable的迭代器 298 // 若Hashtable的实际大小为0,则返回“空迭代器”对象; 299 // 否则,返回正常的Enumerator的对象。(Enumerator实现了迭代器和枚举两个接口) 300 private <T> Iterator<T> getIterator(int type) { 301 if (count == 0) { 302 return (Iterator<T>) emptyIterator; 303 } else { 304 return new Enumerator<T>(type, true); 305 } 306 } 307 308 // Hashtable的“key的集合”。它是一个Set,意味着没有重复元素 309 private transient volatile Set<K> keySet = null; 310 // Hashtable的“key-value的集合”。它是一个Set,意味着没有重复元素 311 private transient volatile Set<Map.Entry<K,V>> entrySet = null; 312 // Hashtable的“key-value的集合”。它是一个Collection,意味着可以有重复元素 313 private transient volatile Collection<V> values = null; 314 315 // 返回一个被synchronizedSet封装后的KeySet对象 316 // synchronizedSet封装的目的是对KeySet的所有方法都添加synchronized,实现多线程同步 317 public Set<K> keySet() { 318 if (keySet == null) 319 keySet = Collections.synchronizedSet(new KeySet(), this); 320 return keySet; 321 } 322 323 // Hashtable的Key的Set集合。 324 // KeySet继承于AbstractSet,所以,KeySet中的元素没有重复的。 325 private class KeySet extends AbstractSet<K> { 326 public Iterator<K> iterator() { 327 return getIterator(KEYS); 328 } 329 public int size() { 330 return count; 331 } 332 public boolean contains(Object o) { 333 return containsKey(o); 334 } 335 public boolean remove(Object o) { 336 return Hashtable.this.remove(o) != null; 337 } 338 public void clear() { 339 Hashtable.this.clear(); 340 } 341 } 342 343 // 返回一个被synchronizedSet封装后的EntrySet对象 344 // synchronizedSet封装的目的是对EntrySet的所有方法都添加synchronized,实现多线程同步 345 public Set<Map.Entry<K,V>> entrySet() { 346 if (entrySet==null) 347 entrySet = Collections.synchronizedSet(new EntrySet(), this); 348 return entrySet; 349 } 350 351 // Hashtable的Entry的Set集合。 352 // EntrySet继承于AbstractSet,所以,EntrySet中的元素没有重复的。 353 private class EntrySet extends AbstractSet<Map.Entry<K,V>> { 354 public Iterator<Map.Entry<K,V>> iterator() { 355 return getIterator(ENTRIES); 356 } 357 358 public boolean add(Map.Entry<K,V> o) { 359 return super.add(o); 360 } 361 362 // 查找EntrySet中是否包含Object(0) 363 // 首先,在table中找到o对应的Entry(Entry是一个单向链表) 364 // 然后,查找Entry链表中是否存在Object 365 public boolean contains(Object o) { 366 if (!(o instanceof Map.Entry)) 367 return false; 368 Map.Entry entry = (Map.Entry)o; 369 Object key = entry.getKey(); 370 Entry[] tab = table; 371 int hash = key.hashCode(); 372 int index = (hash & 0x7FFFFFFF) % tab.length; 373 374 for (Entry e = tab[index]; e != null; e = e.next) 375 if (e.hash==hash && e.equals(entry)) 376 return true; 377 return false; 378 } 379 380 // 删除元素Object(0) 381 // 首先,在table中找到o对应的Entry(Entry是一个单向链表) 382 // 然后,删除链表中的元素Object 383 public boolean remove(Object o) { 384 if (!(o instanceof Map.Entry)) 385 return false; 386 Map.Entry<K,V> entry = (Map.Entry<K,V>) o; 387 K key = entry.getKey(); 388 Entry[] tab = table; 389 int hash = key.hashCode(); 390 int index = (hash & 0x7FFFFFFF) % tab.length; 391 392 for (Entry<K,V> e = tab[index], prev = null; e != null; 393 prev = e, e = e.next) { 394 if (e.hash==hash && e.equals(entry)) { 395 modCount++; 396 if (prev != null) 397 prev.next = e.next; 398 else 399 tab[index] = e.next; 400 401 count--; 402 e.value = null; 403 return true; 404 } 405 } 406 return false; 407 } 408 409 public int size() { 410 return count; 411 } 412 413 public void clear() { 414 Hashtable.this.clear(); 415 } 416 } 417 418 // 返回一个被synchronizedCollection封装后的ValueCollection对象 419 // synchronizedCollection封装的目的是对ValueCollection的所有方法都添加synchronized,实现多线程同步 420 public Collection<V> values() { 421 if (values==null) 422 values = Collections.synchronizedCollection(new ValueCollection(), 423 this); 424 return values; 425 } 426 427 // Hashtable的value的Collection集合。 428 // ValueCollection继承于AbstractCollection,所以,ValueCollection中的元素可以重复的。 429 private class ValueCollection extends AbstractCollection<V> { 430 public Iterator<V> iterator() { 431 return getIterator(VALUES); 432 } 433 public int size() { 434 return count; 435 } 436 public boolean contains(Object o) { 437 return containsValue(o); 438 } 439 public void clear() { 440 Hashtable.this.clear(); 441 } 442 } 443 444 // 重新equals()函数 445 // 若两个Hashtable的所有key-value键值对都相等,则判断它们两个相等 446 public synchronized boolean equals(Object o) { 447 if (o == this) 448 return true; 449 450 if (!(o instanceof Map)) 451 return false; 452 Map<K,V> t = (Map<K,V>) o; 453 if (t.size() != size()) 454 return false; 455 456 try { 457 // 通过迭代器依次取出当前Hashtable的key-value键值对 458 // 并判断该键值对,存在于Hashtable(o)中。 459 // 若不存在,则立即返回false;否则,遍历完“当前Hashtable”并返回true。 460 Iterator<Map.Entry<K,V>> i = entrySet().iterator(); 461 while (i.hasNext()) { 462 Map.Entry<K,V> e = i.next(); 463 K key = e.getKey(); 464 V value = e.getValue(); 465 if (value == null) { 466 if (!(t.get(key)==null && t.containsKey(key))) 467 return false; 468 } else { 469 if (!value.equals(t.get(key))) 470 return false; 471 } 472 } 473 } catch (ClassCastException unused) { 474 return false; 475 } catch (NullPointerException unused) { 476 return false; 477 } 478 479 return true; 480 } 481 482 // 计算Hashtable的哈希值 483 // 若 Hashtable的实际大小为0 或者 加载因子<0,则返回0。 484 // 否则,返回“Hashtable中的每个Entry的key和value的异或值 的总和”。 485 public synchronized int hashCode() { 486 int h = 0; 487 if (count == 0 || loadFactor < 0) 488 return h; // Returns zero 489 490 loadFactor = -loadFactor; // Mark hashCode computation in progress 491 Entry[] tab = table; 492 for (int i = 0; i < tab.length; i++) 493 for (Entry e = tab[i]; e != null; e = e.next) 494 h += e.key.hashCode() ^ e.value.hashCode(); 495 loadFactor = -loadFactor; // Mark hashCode computation complete 496 497 return h; 498 } 499 500 // java.io.Serializable的写入函数 501 // 将Hashtable的“总的容量,实际容量,所有的Entry”都写入到输出流中 502 private synchronized void writeObject(java.io.ObjectOutputStream s) 503 throws IOException 504 { 505 // Write out the length, threshold, loadfactor 506 s.defaultWriteObject(); 507 508 // Write out length, count of elements and then the key/value objects 509 s.writeInt(table.length); 510 s.writeInt(count); 511 for (int index = table.length-1; index >= 0; index--) { 512 Entry entry = table[index]; 513 514 while (entry != null) { 515 s.writeObject(entry.key); 516 s.writeObject(entry.value); 517 entry = entry.next; 518 } 519 } 520 } 521 522 // java.io.Serializable的读取函数:根据写入方式读出 523 // 将Hashtable的“总的容量,实际容量,所有的Entry”依次读出 524 private void readObject(java.io.ObjectInputStream s) 525 throws IOException, ClassNotFoundException 526 { 527 // Read in the length, threshold, and loadfactor 528 s.defaultReadObject(); 529 530 // Read the original length of the array and number of elements 531 int origlength = s.readInt(); 532 int elements = s.readInt(); 533 534 // Compute new size with a bit of room 5% to grow but 535 // no larger than the original size. Make the length 536 // odd if it's large enough, this helps distribute the entries. 537 // Guard against the length ending up zero, that's not valid. 538 int length = (int)(elements * loadFactor) + (elements / 20) + 3; 539 if (length > elements && (length & 1) == 0) 540 length--; 541 if (origlength > 0 && length > origlength) 542 length = origlength; 543 544 Entry[] table = new Entry[length]; 545 count = 0; 546 547 // Read the number of elements and then all the key/value objects 548 for (; elements > 0; elements--) { 549 K key = (K)s.readObject(); 550 V value = (V)s.readObject(); 551 // synch could be eliminated for performance 552 reconstitutionPut(table, key, value); 553 } 554 this.table = table; 555 } 556 557 private void reconstitutionPut(Entry[] tab, K key, V value) 558 throws StreamCorruptedException 559 { 560 if (value == null) { 561 throw new java.io.StreamCorruptedException(); 562 } 563 // Makes sure the key is not already in the hashtable. 564 // This should not happen in deserialized version. 565 int hash = key.hashCode(); 566 int index = (hash & 0x7FFFFFFF) % tab.length; 567 for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) { 568 if ((e.hash == hash) && e.key.equals(key)) { 569 throw new java.io.StreamCorruptedException(); 570 } 571 } 572 // Creates the new entry. 573 Entry<K,V> e = tab[index]; 574 tab[index] = new Entry<K,V>(hash, key, value, e); 575 count++; 576 } 577 578 // Hashtable的Entry节点,它本质上是一个单向链表。 579 // 也因此,我们才能推断出Hashtable是由拉链法实现的散列表 580 private static class Entry<K,V> implements Map.Entry<K,V> { 581 // 哈希值 582 int hash; 583 K key; 584 V value; 585 // 指向的下一个Entry,即链表的下一个节点 586 Entry<K,V> next; 587 588 // 构造函数 589 protected Entry(int hash, K key, V value, Entry<K,V> next) { 590 this.hash = hash; 591 this.key = key; 592 this.value = value; 593 this.next = next; 594 } 595 596 protected Object clone() { 597 return new Entry<K,V>(hash, key, value, 598 (next==null ? null : (Entry<K,V>) next.clone())); 599 } 600 601 public K getKey() { 602 return key; 603 } 604 605 public V getValue() { 606 return value; 607 } 608 609 // 设置value。若value是null,则抛出异常。 610 public V setValue(V value) { 611 if (value == null) 612 throw new NullPointerException(); 613 614 V oldValue = this.value; 615 this.value = value; 616 return oldValue; 617 } 618 619 // 覆盖equals()方法,判断两个Entry是否相等。 620 // 若两个Entry的key和value都相等,则认为它们相等。 621 public boolean equals(Object o) { 622 if (!(o instanceof Map.Entry)) 623 return false; 624 Map.Entry e = (Map.Entry)o; 625 626 return (key==null ? e.getKey()==null : key.equals(e.getKey())) && 627 (value==null ? e.getValue()==null : value.equals(e.getValue())); 628 } 629 630 public int hashCode() { 631 return hash ^ (value==null ? 0 : value.hashCode()); 632 } 633 634 public String toString() { 635 return key.toString()+"="+value.toString(); 636 } 637 } 638 639 private static final int KEYS = 0; 640 private static final int VALUES = 1; 641 private static final int ENTRIES = 2; 642 643 // Enumerator的作用是提供了“通过elements()遍历Hashtable的接口” 和 “通过entrySet()遍历Hashtable的接口”。因为,它同时实现了 “Enumerator接口”和“Iterator接口”。 644 private class Enumerator<T> implements Enumeration<T>, Iterator<T> { 645 // 指向Hashtable的table 646 Entry[] table = Hashtable.this.table; 647 // Hashtable的总的大小 648 int index = table.length; 649 Entry<K,V> entry = null; 650 Entry<K,V> lastReturned = null; 651 int type; 652 653 // Enumerator是 “迭代器(Iterator)” 还是 “枚举类(Enumeration)”的标志 654 // iterator为true,表示它是迭代器;否则,是枚举类。 655 boolean iterator; 656 657 // 在将Enumerator当作迭代器使用时会用到,用来实现fail-fast机制。 658 protected int expectedModCount = modCount; 659 660 Enumerator(int type, boolean iterator) { 661 this.type = type; 662 this.iterator = iterator; 663 } 664 665 // 从遍历table的数组的末尾向前查找,直到找到不为null的Entry。 666 public boolean hasMoreElements() { 667 Entry<K,V> e = entry; 668 int i = index; 669 Entry[] t = table; 670 /* Use locals for faster loop iteration */ 671 while (e == null && i > 0) { 672 e = t[--i]; 673 } 674 entry = e; 675 index = i; 676 return e != null; 677 } 678 679 // 获取下一个元素 680 // 注意:从hasMoreElements() 和nextElement() 可以看出“Hashtable的elements()遍历方式” 681 // 首先,从后向前的遍历table数组。table数组的每个节点都是一个单向链表(Entry)。 682 // 然后,依次向后遍历单向链表Entry。 683 public T nextElement() { 684 Entry<K,V> et = entry; 685 int i = index; 686 Entry[] t = table; 687 /* Use locals for faster loop iteration */ 688 while (et == null && i > 0) { 689 et = t[--i]; 690 } 691 entry = et; 692 index = i; 693 if (et != null) { 694 Entry<K,V> e = lastReturned = entry; 695 entry = e.next; 696 return type == KEYS ? (T)e.key : (type == VALUES ? (T)e.value : (T)e); 697 } 698 throw new NoSuchElementException("Hashtable Enumerator"); 699 } 700 701 // 迭代器Iterator的判断是否存在下一个元素 702 // 实际上,它是调用的hasMoreElements() 703 public boolean hasNext() { 704 return hasMoreElements(); 705 } 706 707 // 迭代器获取下一个元素 708 // 实际上,它是调用的nextElement() 709 public T next() { 710 if (modCount != expectedModCount) 711 throw new ConcurrentModificationException(); 712 return nextElement(); 713 } 714 715 // 迭代器的remove()接口。 716 // 首先,它在table数组中找出要删除元素所在的Entry, 717 // 然后,删除单向链表Entry中的元素。 718 public void remove() { 719 if (!iterator) 720 throw new UnsupportedOperationException(); 721 if (lastReturned == null) 722 throw new IllegalStateException("Hashtable Enumerator"); 723 if (modCount != expectedModCount) 724 throw new ConcurrentModificationException(); 725 726 synchronized(Hashtable.this) { 727 Entry[] tab = Hashtable.this.table; 728 int index = (lastReturned.hash & 0x7FFFFFFF) % tab.length; 729 730 for (Entry<K,V> e = tab[index], prev = null; e != null; 731 prev = e, e = e.next) { 732 if (e == lastReturned) { 733 modCount++; 734 expectedModCount++; 735 if (prev == null) 736 tab[index] = e.next; 737 else 738 prev.next = e.next; 739 count--; 740 lastReturned = null; 741 return; 742 } 743 } 744 throw new ConcurrentModificationException(); 745 } 746 } 747 } 748 749 750 private static Enumeration emptyEnumerator = new EmptyEnumerator(); 751 private static Iterator emptyIterator = new EmptyIterator(); 752 753 // 空枚举类 754 // 当Hashtable的实际大小为0;此时,又要通过Enumeration遍历Hashtable时,返回的是“空枚举类”的对象。 755 private static class EmptyEnumerator implements Enumeration<Object> { 756 757 EmptyEnumerator() { 758 } 759 760 // 空枚举类的hasMoreElements() 始终返回false 761 public boolean hasMoreElements() { 762 return false; 763 } 764 765 // 空枚举类的nextElement() 抛出异常 766 public Object nextElement() { 767 throw new NoSuchElementException("Hashtable Enumerator"); 768 } 769 } 770 771 772 // 空迭代器 773 // 当Hashtable的实际大小为0;此时,又要通过迭代器遍历Hashtable时,返回的是“空迭代器”的对象。 774 private static class EmptyIterator implements Iterator<Object> { 775 776 EmptyIterator() { 777 } 778 779 public boolean hasNext() { 780 return false; 781 } 782 783 public Object next() { 784 throw new NoSuchElementException("Hashtable Iterator"); 785 } 786 787 public void remove() { 788 throw new IllegalStateException("Hashtable Iterator"); 789 } 790 791 } 792 }
1 /* 2 * Copyright (c) 1994, 2013, Oracle and/or its affiliates. All rights reserved. 3 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11 * 12 * 13 * 14 * 15 * 16 * 17 * 18 * 19 * 20 * 21 * 22 * 23 * 24 */ 25 26 package java.util; 27 28 import java.io.*; 29 import java.util.concurrent.ThreadLocalRandom; 30 import java.util.function.BiConsumer; 31 import java.util.function.Function; 32 import java.util.function.BiFunction; 33 34 /** 35 * This class implements a hash table, which maps keys to values. Any 36 * non-<code>null</code> object can be used as a key or as a value. <p> 37 * 38 * To successfully store and retrieve objects from a hashtable, the 39 * objects used as keys must implement the <code>hashCode</code> 40 * method and the <code>equals</code> method. <p> 41 * 42 * An instance of <code>Hashtable</code> has two parameters that affect its 43 * performance: <i>initial capacity</i> and <i>load factor</i>. The 44 * <i>capacity</i> is the number of <i>buckets</i> in the hash table, and the 45 * <i>initial capacity</i> is simply the capacity at the time the hash table 46 * is created. Note that the hash table is <i>open</i>: in the case of a "hash 47 * collision", a single bucket stores multiple entries, which must be searched 48 * sequentially. The <i>load factor</i> is a measure of how full the hash 49 * table is allowed to get before its capacity is automatically increased. 50 * The initial capacity and load factor parameters are merely hints to 51 * the implementation. The exact details as to when and whether the rehash 52 * method is invoked are implementation-dependent.<p> 53 * 54 * Generally, the default load factor (.75) offers a good tradeoff between 55 * time and space costs. Higher values decrease the space overhead but 56 * increase the time cost to look up an entry (which is reflected in most 57 * <tt>Hashtable</tt> operations, including <tt>get</tt> and <tt>put</tt>).<p> 58 * 59 * The initial capacity controls a tradeoff between wasted space and the 60 * need for <code>rehash</code> operations, which are time-consuming. 61 * No <code>rehash</code> operations will <i>ever</i> occur if the initial 62 * capacity is greater than the maximum number of entries the 63 * <tt>Hashtable</tt> will contain divided by its load factor. However, 64 * setting the initial capacity too high can waste space.<p> 65 * 66 * If many entries are to be made into a <code>Hashtable</code>, 67 * creating it with a sufficiently large capacity may allow the 68 * entries to be inserted more efficiently than letting it perform 69 * automatic rehashing as needed to grow the table. <p> 70 * 71 * This example creates a hashtable of numbers. It uses the names of 72 * the numbers as keys: 73 * <pre> {@code 74 * Hashtable<String, Integer> numbers 75 * = new Hashtable<String, Integer>(); 76 * numbers.put("one", 1); 77 * numbers.put("two", 2); 78 * numbers.put("three", 3);}</pre> 79 * 80 * <p>To retrieve a number, use the following code: 81 * <pre> {@code 82 * Integer n = numbers.get("two"); 83 * if (n != null) { 84 * System.out.println("two = " + n); 85 * }}</pre> 86 * 87 * <p>The iterators returned by the <tt>iterator</tt> method of the collections 88 * returned by all of this class's "collection view methods" are 89 * <em>fail-fast</em>: if the Hashtable is structurally modified at any time 90 * after the iterator is created, in any way except through the iterator's own 91 * <tt>remove</tt> method, the iterator will throw a {@link 92 * ConcurrentModificationException}. Thus, in the face of concurrent 93 * modification, the iterator fails quickly and cleanly, rather than risking 94 * arbitrary, non-deterministic behavior at an undetermined time in the future. 95 * The Enumerations returned by Hashtable's keys and elements methods are 96 * <em>not</em> fail-fast. 97 * 98 * <p>Note that the fail-fast behavior of an iterator cannot be guaranteed 99 * as it is, generally speaking, impossible to make any hard guarantees in the 100 * presence of unsynchronized concurrent modification. Fail-fast iterators 101 * throw <tt>ConcurrentModificationException</tt> on a best-effort basis. 102 * Therefore, it would be wrong to write a program that depended on this 103 * exception for its correctness: <i>the fail-fast behavior of iterators 104 * should be used only to detect bugs.</i> 105 * 106 * <p>As of the Java 2 platform v1.2, this class was retrofitted to 107 * implement the {@link Map} interface, making it a member of the 108 * <a href="{@docRoot}/../technotes/guides/collections/index.html"> 109 * 110 * Java Collections Framework</a>. Unlike the new collection 111 * implementations, {@code Hashtable} is synchronized. If a 112 * thread-safe implementation is not needed, it is recommended to use 113 * {@link HashMap} in place of {@code Hashtable}. If a thread-safe 114 * highly-concurrent implementation is desired, then it is recommended 115 * to use {@link java.util.concurrent.ConcurrentHashMap} in place of 116 * {@code Hashtable}. 117 * 118 * @author Arthur van Hoff 119 * @author Josh Bloch 120 * @author Neal Gafter 121 * @see Object#equals(java.lang.Object) 122 * @see Object#hashCode() 123 * @see Hashtable#rehash() 124 * @see Collection 125 * @see Map 126 * @see HashMap 127 * @see TreeMap 128 * @since JDK1.0 129 */ 130 public class Hashtable<K,V> 131 extends Dictionary<K,V> 132 implements Map<K,V>, Cloneable, java.io.Serializable { 133 134 /** 135 * The hash table data. 136 */ 137 private transient Entry<?,?>[] table; 138 139 /** 140 * The total number of entries in the hash table. 141 */ 142 private transient int count; 143 144 /** 145 * The table is rehashed when its size exceeds this threshold. (The 146 * value of this field is (int)(capacity * loadFactor).) 147 * 148 * @serial 149 */ 150 private int threshold; 151 152 /** 153 * The load factor for the hashtable. 154 * 155 * @serial 156 */ 157 private float loadFactor; 158 159 /** 160 * The number of times this Hashtable has been structurally modified 161 * Structural modifications are those that change the number of entries in 162 * the Hashtable or otherwise modify its internal structure (e.g., 163 * rehash). This field is used to make iterators on Collection-views of 164 * the Hashtable fail-fast. (See ConcurrentModificationException). 165 */ 166 private transient int modCount = 0; 167 168 /** use serialVersionUID from JDK 1.0.2 for interoperability */ 169 private static final long serialVersionUID = 1421746759512286392L; 170 171 /** 172 * Constructs a new, empty hashtable with the specified initial 173 * capacity and the specified load factor. 174 * 175 * @param initialCapacity the initial capacity of the hashtable. 176 * @param loadFactor the load factor of the hashtable. 177 * @exception IllegalArgumentException if the initial capacity is less 178 * than zero, or if the load factor is nonpositive. 179 */ 180 public Hashtable(int initialCapacity, float loadFactor) { 181 if (initialCapacity < 0) 182 throw new IllegalArgumentException("Illegal Capacity: "+ 183 initialCapacity); 184 if (loadFactor <= 0 || Float.isNaN(loadFactor)) 185 throw new IllegalArgumentException("Illegal Load: "+loadFactor); 186 187 if (initialCapacity==0) 188 initialCapacity = 1; 189 this.loadFactor = loadFactor; 190 table = new Entry<?,?>[initialCapacity]; 191 threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1); 192 } 193 194 /** 195 * Constructs a new, empty hashtable with the specified initial capacity 196 * and default load factor (0.75). 197 * 198 * @param initialCapacity the initial capacity of the hashtable. 199 * @exception IllegalArgumentException if the initial capacity is less 200 * than zero. 201 */ 202 public Hashtable(int initialCapacity) { 203 this(initialCapacity, 0.75f); 204 } 205 206 /** 207 * Constructs a new, empty hashtable with a default initial capacity (11) 208 * and load factor (0.75). 209 */ 210 public Hashtable() { 211 this(11, 0.75f); 212 } 213 214 /** 215 * Constructs a new hashtable with the same mappings as the given 216 * Map. The hashtable is created with an initial capacity sufficient to 217 * hold the mappings in the given Map and a default load factor (0.75). 218 * 219 * @param t the map whose mappings are to be placed in this map. 220 * @throws NullPointerException if the specified map is null. 221 * @since 1.2 222 */ 223 public Hashtable(Map<? extends K, ? extends V> t) { 224 this(Math.max(2*t.size(), 11), 0.75f); 225 putAll(t); 226 } 227 228 /** 229 * Returns the number of keys in this hashtable. 230 * 231 * @return the number of keys in this hashtable. 232 */ 233 public synchronized int size() { 234 return count; 235 } 236 237 /** 238 * Tests if this hashtable maps no keys to values. 239 * 240 * @return <code>true</code> if this hashtable maps no keys to values; 241 * <code>false</code> otherwise. 242 */ 243 public synchronized boolean isEmpty() { 244 return count == 0; 245 } 246 247 /** 248 * Returns an enumeration of the keys in this hashtable. 249 * 250 * @return an enumeration of the keys in this hashtable. 251 * @see Enumeration 252 * @see #elements() 253 * @see #keySet() 254 * @see Map 255 */ 256 public synchronized Enumeration<K> keys() { 257 return this.<K>getEnumeration(KEYS); 258 } 259 260 /** 261 * Returns an enumeration of the values in this hashtable. 262 * Use the Enumeration methods on the returned object to fetch the elements 263 * sequentially. 264 * 265 * @return an enumeration of the values in this hashtable. 266 * @see java.util.Enumeration 267 * @see #keys() 268 * @see #values() 269 * @see Map 270 */ 271 public synchronized Enumeration<V> elements() { 272 return this.<V>getEnumeration(VALUES); 273 } 274 275 /** 276 * Tests if some key maps into the specified value in this hashtable. 277 * This operation is more expensive than the {@link #containsKey 278 * containsKey} method. 279 * 280 * <p>Note that this method is identical in functionality to 281 * {@link #containsValue containsValue}, (which is part of the 282 * {@link Map} interface in the collections framework). 283 * 284 * @param value a value to search for 285 * @return <code>true</code> if and only if some key maps to the 286 * <code>value</code> argument in this hashtable as 287 * determined by the <tt>equals</tt> method; 288 * <code>false</code> otherwise. 289 * @exception NullPointerException if the value is <code>null</code> 290 */ 291 public synchronized boolean contains(Object value) { 292 if (value == null) { 293 throw new NullPointerException(); 294 } 295 296 Entry<?,?> tab[] = table; 297 for (int i = tab.length ; i-- > 0 ;) { 298 for (Entry<?,?> e = tab[i] ; e != null ; e = e.next) { 299 if (e.value.equals(value)) { 300 return true; 301 } 302 } 303 } 304 return false; 305 } 306 307 /** 308 * Returns true if this hashtable maps one or more keys to this value. 309 * 310 * <p>Note that this method is identical in functionality to {@link 311 * #contains contains} (which predates the {@link Map} interface). 312 * 313 * @param value value whose presence in this hashtable is to be tested 314 * @return <tt>true</tt> if this map maps one or more keys to the 315 * specified value 316 * @throws NullPointerException if the value is <code>null</code> 317 * @since 1.2 318 */ 319 public boolean containsValue(Object value) { 320 return contains(value); 321 } 322 323 /** 324 * Tests if the specified object is a key in this hashtable. 325 * 326 * @param key possible key 327 * @return <code>true</code> if and only if the specified object 328 * is a key in this hashtable, as determined by the 329 * <tt>equals</tt> method; <code>false</code> otherwise. 330 * @throws NullPointerException if the key is <code>null</code> 331 * @see #contains(Object) 332 */ 333 public synchronized boolean containsKey(Object key) { 334 Entry<?,?> tab[] = table; 335 int hash = key.hashCode(); 336 int index = (hash & 0x7FFFFFFF) % tab.length; 337 for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) { 338 if ((e.hash == hash) && e.key.equals(key)) { 339 return true; 340 } 341 } 342 return false; 343 } 344 345 /** 346 * Returns the value to which the specified key is mapped, 347 * or {@code null} if this map contains no mapping for the key. 348 * 349 * <p>More formally, if this map contains a mapping from a key 350 * {@code k} to a value {@code v} such that {@code (key.equals(k))}, 351 * then this method returns {@code v}; otherwise it returns 352 * {@code null}. (There can be at most one such mapping.) 353 * 354 * @param key the key whose associated value is to be returned 355 * @return the value to which the specified key is mapped, or 356 * {@code null} if this map contains no mapping for the key 357 * @throws NullPointerException if the specified key is null 358 * @see #put(Object, Object) 359 */ 360 @SuppressWarnings("unchecked") 361 public synchronized V get(Object key) { 362 Entry<?,?> tab[] = table; 363 int hash = key.hashCode(); 364 int index = (hash & 0x7FFFFFFF) % tab.length; 365 for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) { 366 if ((e.hash == hash) && e.key.equals(key)) { 367 return (V)e.value; 368 } 369 } 370 return null; 371 } 372 373 /** 374 * The maximum size of array to allocate. 375 * Some VMs reserve some header words in an array. 376 * Attempts to allocate larger arrays may result in 377 * OutOfMemoryError: Requested array size exceeds VM limit 378 */ 379 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; 380 381 /** 382 * Increases the capacity of and internally reorganizes this 383 * hashtable, in order to accommodate and access its entries more 384 * efficiently. This method is called automatically when the 385 * number of keys in the hashtable exceeds this hashtable's capacity 386 * and load factor. 387 */ 388 @SuppressWarnings("unchecked") 389 protected void rehash() { 390 int oldCapacity = table.length; 391 Entry<?,?>[] oldMap = table; 392 393 // overflow-conscious code 394 int newCapacity = (oldCapacity << 1) + 1; 395 if (newCapacity - MAX_ARRAY_SIZE > 0) { 396 if (oldCapacity == MAX_ARRAY_SIZE) 397 // Keep running with MAX_ARRAY_SIZE buckets 398 return; 399 newCapacity = MAX_ARRAY_SIZE; 400 } 401 Entry<?,?>[] newMap = new Entry<?,?>[newCapacity]; 402 403 modCount++; 404 threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1); 405 table = newMap; 406 407 for (int i = oldCapacity ; i-- > 0 ;) { 408 for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) { 409 Entry<K,V> e = old; 410 old = old.next; 411 412 int index = (e.hash & 0x7FFFFFFF) % newCapacity; 413 e.next = (Entry<K,V>)newMap[index]; 414 newMap[index] = e; 415 } 416 } 417 } 418 419 private void addEntry(int hash, K key, V value, int index) { 420 modCount++; 421 422 Entry<?,?> tab[] = table; 423 if (count >= threshold) { 424 // Rehash the table if the threshold is exceeded 425 rehash(); 426 427 tab = table; 428 hash = key.hashCode(); 429 index = (hash & 0x7FFFFFFF) % tab.length; 430 } 431 432 // Creates the new entry. 433 @SuppressWarnings("unchecked") 434 Entry<K,V> e = (Entry<K,V>) tab[index]; 435 tab[index] = new Entry<>(hash, key, value, e); 436 count++; 437 } 438 439 /** 440 * Maps the specified <code>key</code> to the specified 441 * <code>value</code> in this hashtable. Neither the key nor the 442 * value can be <code>null</code>. <p> 443 * 444 * The value can be retrieved by calling the <code>get</code> method 445 * with a key that is equal to the original key. 446 * 447 * @param key the hashtable key 448 * @param value the value 449 * @return the previous value of the specified key in this hashtable, 450 * or <code>null</code> if it did not have one 451 * @exception NullPointerException if the key or value is 452 * <code>null</code> 453 * @see Object#equals(Object) 454 * @see #get(Object) 455 */ 456 public synchronized V put(K key, V value) { 457 // Make sure the value is not null 458 if (value == null) { 459 throw new NullPointerException(); 460 } 461 462 // Makes sure the key is not already in the hashtable. 463 Entry<?,?> tab[] = table; 464 int hash = key.hashCode(); 465 int index = (hash & 0x7FFFFFFF) % tab.length; 466 @SuppressWarnings("unchecked") 467 Entry<K,V> entry = (Entry<K,V>)tab[index]; 468 for(; entry != null ; entry = entry.next) { 469 if ((entry.hash == hash) && entry.key.equals(key)) { 470 V old = entry.value; 471 entry.value = value; 472 return old; 473 } 474 } 475 476 addEntry(hash, key, value, index); 477 return null; 478 } 479 480 /** 481 * Removes the key (and its corresponding value) from this 482 * hashtable. This method does nothing if the key is not in the hashtable. 483 * 484 * @param key the key that needs to be removed 485 * @return the value to which the key had been mapped in this hashtable, 486 * or <code>null</code> if the key did not have a mapping 487 * @throws NullPointerException if the key is <code>null</code> 488 */ 489 public synchronized V remove(Object key) { 490 Entry<?,?> tab[] = table; 491 int hash = key.hashCode(); 492 int index = (hash & 0x7FFFFFFF) % tab.length; 493 @SuppressWarnings("unchecked") 494 Entry<K,V> e = (Entry<K,V>)tab[index]; 495 for(Entry<K,V> prev = null ; e != null ; prev = e, e = e.next) { 496 if ((e.hash == hash) && e.key.equals(key)) { 497 modCount++; 498 if (prev != null) { 499 prev.next = e.next; 500 } else { 501 tab[index] = e.next; 502 } 503 count--; 504 V oldValue = e.value; 505 e.value = null; 506 return oldValue; 507 } 508 } 509 return null; 510 } 511 512 /** 513 * Copies all of the mappings from the specified map to this hashtable. 514 * These mappings will replace any mappings that this hashtable had for any 515 * of the keys currently in the specified map. 516 * 517 * @param t mappings to be stored in this map 518 * @throws NullPointerException if the specified map is null 519 * @since 1.2 520 */ 521 public synchronized void putAll(Map<? extends K, ? extends V> t) { 522 for (Map.Entry<? extends K, ? extends V> e : t.entrySet()) 523 put(e.getKey(), e.getValue()); 524 } 525 526 /** 527 * Clears this hashtable so that it contains no keys. 528 */ 529 public synchronized void clear() { 530 Entry<?,?> tab[] = table; 531 modCount++; 532 for (int index = tab.length; --index >= 0; ) 533 tab[index] = null; 534 count = 0; 535 } 536 537 /** 538 * Creates a shallow copy of this hashtable. All the structure of the 539 * hashtable itself is copied, but the keys and values are not cloned. 540 * This is a relatively expensive operation. 541 * 542 * @return a clone of the hashtable 543 */ 544 public synchronized Object clone() { 545 try { 546 Hashtable<?,?> t = (Hashtable<?,?>)super.clone(); 547 t.table = new Entry<?,?>[table.length]; 548 for (int i = table.length ; i-- > 0 ; ) { 549 t.table[i] = (table[i] != null) 550 ? (Entry<?,?>) table[i].clone() : null; 551 } 552 t.keySet = null; 553 t.entrySet = null; 554 t.values = null; 555 t.modCount = 0; 556 return t; 557 } catch (CloneNotSupportedException e) { 558 // this shouldn't happen, since we are Cloneable 559 throw new InternalError(e); 560 } 561 } 562 563 /** 564 * Returns a string representation of this <tt>Hashtable</tt> object 565 * in the form of a set of entries, enclosed in braces and separated 566 * by the ASCII characters "<tt>, </tt>" (comma and space). Each 567 * entry is rendered as the key, an equals sign <tt>=</tt>, and the 568 * associated element, where the <tt>toString</tt> method is used to 569 * convert the key and element to strings. 570 * 571 * @return a string representation of this hashtable 572 */ 573 public synchronized String toString() { 574 int max = size() - 1; 575 if (max == -1) 576 return "{}"; 577 578 StringBuilder sb = new StringBuilder(); 579 Iterator<Map.Entry<K,V>> it = entrySet().iterator(); 580 581 sb.append('{'); 582 for (int i = 0; ; i++) { 583 Map.Entry<K,V> e = it.next(); 584 K key = e.getKey(); 585 V value = e.getValue(); 586 sb.append(key == this ? "(this Map)" : key.toString()); 587 sb.append('='); 588 sb.append(value == this ? "(this Map)" : value.toString()); 589 590 if (i == max) 591 return sb.append('}').toString(); 592 sb.append(", "); 593 } 594 } 595 596 597 private <T> Enumeration<T> getEnumeration(int type) { 598 if (count == 0) { 599 return Collections.emptyEnumeration(); 600 } else { 601 return new Enumerator<>(type, false); 602 } 603 } 604 605 private <T> Iterator<T> getIterator(int type) { 606 if (count == 0) { 607 return Collections.emptyIterator(); 608 } else { 609 return new Enumerator<>(type, true); 610 } 611 } 612 613 // Views 614 615 /** 616 * Each of these fields are initialized to contain an instance of the 617 * appropriate view the first time this view is requested. The views are 618 * stateless, so there's no reason to create more than one of each. 619 */ 620 private transient volatile Set<K> keySet; 621 private transient volatile Set<Map.Entry<K,V>> entrySet; 622 private transient volatile Collection<V> values; 623 624 /** 625 * Returns a {@link Set} view of the keys contained in this map. 626 * The set is backed by the map, so changes to the map are 627 * reflected in the set, and vice-versa. If the map is modified 628 * while an iteration over the set is in progress (except through 629 * the iterator's own <tt>remove</tt> operation), the results of 630 * the iteration are undefined. The set supports element removal, 631 * which removes the corresponding mapping from the map, via the 632 * <tt>Iterator.remove</tt>, <tt>Set.remove</tt>, 633 * <tt>removeAll</tt>, <tt>retainAll</tt>, and <tt>clear</tt> 634 * operations. It does not support the <tt>add</tt> or <tt>addAll</tt> 635 * operations. 636 * 637 * @since 1.2 638 */ 639 public Set<K> keySet() { 640 if (keySet == null) 641 keySet = Collections.synchronizedSet(new KeySet(), this); 642 return keySet; 643 } 644 645 private class KeySet extends AbstractSet<K> { 646 public Iterator<K> iterator() { 647 return getIterator(KEYS); 648 } 649 public int size() { 650 return count; 651 } 652 public boolean contains(Object o) { 653 return containsKey(o); 654 } 655 public boolean remove(Object o) { 656 return Hashtable.this.remove(o) != null; 657 } 658 public void clear() { 659 Hashtable.this.clear(); 660 } 661 } 662 663 /** 664 * Returns a {@link Set} view of the mappings contained in this map. 665 * The set is backed by the map, so changes to the map are 666 * reflected in the set, and vice-versa. If the map is modified 667 * while an iteration over the set is in progress (except through 668 * the iterator's own <tt>remove</tt> operation, or through the 669 * <tt>setValue</tt> operation on a map entry returned by the 670 * iterator) the results of the iteration are undefined. The set 671 * supports element removal, which removes the corresponding 672 * mapping from the map, via the <tt>Iterator.remove</tt>, 673 * <tt>Set.remove</tt>, <tt>removeAll</tt>, <tt>retainAll</tt> and 674 * <tt>clear</tt> operations. It does not support the 675 * <tt>add</tt> or <tt>addAll</tt> operations. 676 * 677 * @since 1.2 678 */ 679 public Set<Map.Entry<K,V>> entrySet() { 680 if (entrySet==null) 681 entrySet = Collections.synchronizedSet(new EntrySet(), this); 682 return entrySet; 683 } 684 685 private class EntrySet extends AbstractSet<Map.Entry<K,V>> { 686 public Iterator<Map.Entry<K,V>> iterator() { 687 return getIterator(ENTRIES); 688 } 689 690 public boolean add(Map.Entry<K,V> o) { 691 return super.add(o); 692 } 693 694 public boolean contains(Object o) { 695 if (!(o instanceof Map.Entry)) 696 return false; 697 Map.Entry<?,?> entry = (Map.Entry<?,?>)o; 698 Object key = entry.getKey(); 699 Entry<?,?>[] tab = table; 700 int hash = key.hashCode(); 701 int index = (hash & 0x7FFFFFFF) % tab.length; 702 703 for (Entry<?,?> e = tab[index]; e != null; e = e.next) 704 if (e.hash==hash && e.equals(entry)) 705 return true; 706 return false; 707 } 708 709 public boolean remove(Object o) { 710 if (!(o instanceof Map.Entry)) 711 return false; 712 Map.Entry<?,?> entry = (Map.Entry<?,?>) o; 713 Object key = entry.getKey(); 714 Entry<?,?>[] tab = table; 715 int hash = key.hashCode(); 716 int index = (hash & 0x7FFFFFFF) % tab.length; 717 718 @SuppressWarnings("unchecked") 719 Entry<K,V> e = (Entry<K,V>)tab[index]; 720 for(Entry<K,V> prev = null; e != null; prev = e, e = e.next) { 721 if (e.hash==hash && e.equals(entry)) { 722 modCount++; 723 if (prev != null) 724 prev.next = e.next; 725 else 726 tab[index] = e.next; 727 728 count--; 729 e.value = null; 730 return true; 731 } 732 } 733 return false; 734 } 735 736 public int size() { 737 return count; 738 } 739 740 public void clear() { 741 Hashtable.this.clear(); 742 } 743 } 744 745 /** 746 * Returns a {@link Collection} view of the values contained in this map. 747 * The collection is backed by the map, so changes to the map are 748 * reflected in the collection, and vice-versa. If the map is 749 * modified while an iteration over the collection is in progress 750 * (except through the iterator's own <tt>remove</tt> operation), 751 * the results of the iteration are undefined. The collection 752 * supports element removal, which removes the corresponding 753 * mapping from the map, via the <tt>Iterator.remove</tt>, 754 * <tt>Collection.remove</tt>, <tt>removeAll</tt>, 755 * <tt>retainAll</tt> and <tt>clear</tt> operations. It does not 756 * support the <tt>add</tt> or <tt>addAll</tt> operations. 757 * 758 * @since 1.2 759 */ 760 public Collection<V> values() { 761 if (values==null) 762 values = Collections.synchronizedCollection(new ValueCollection(), 763 this); 764 return values; 765 } 766 767 private class ValueCollection extends AbstractCollection<V> { 768 public Iterator<V> iterator() { 769 return getIterator(VALUES); 770 } 771 public int size() { 772 return count; 773 } 774 public boolean contains(Object o) { 775 return containsValue(o); 776 } 777 public void clear() { 778 Hashtable.this.clear(); 779 } 780 } 781 782 // Comparison and hashing 783 784 /** 785 * Compares the specified Object with this Map for equality, 786 * as per the definition in the Map interface. 787 * 788 * @param o object to be compared for equality with this hashtable 789 * @return true if the specified Object is equal to this Map 790 * @see Map#equals(Object) 791 * @since 1.2 792 */ 793 public synchronized boolean equals(Object o) { 794 if (o == this) 795 return true; 796 797 if (!(o instanceof Map)) 798 return false; 799 Map<?,?> t = (Map<?,?>) o; 800 if (t.size() != size()) 801 return false; 802 803 try { 804 Iterator<Map.Entry<K,V>> i = entrySet().iterator(); 805 while (i.hasNext()) { 806 Map.Entry<K,V> e = i.next(); 807 K key = e.getKey(); 808 V value = e.getValue(); 809 if (value == null) { 810 if (!(t.get(key)==null && t.containsKey(key))) 811 return false; 812 } else { 813 if (!value.equals(t.get(key))) 814 return false; 815 } 816 } 817 } catch (ClassCastException unused) { 818 return false; 819 } catch (NullPointerException unused) { 820 return false; 821 } 822 823 return true; 824 } 825 826 /** 827 * Returns the hash code value for this Map as per the definition in the 828 * Map interface. 829 * 830 * @see Map#hashCode() 831 * @since 1.2 832 */ 833 public synchronized int hashCode() { 834 /* 835 * This code detects the recursion caused by computing the hash code 836 * of a self-referential hash table and prevents the stack overflow 837 * that would otherwise result. This allows certain 1.1-era 838 * applets with self-referential hash tables to work. This code 839 * abuses the loadFactor field to do double-duty as a hashCode 840 * in progress flag, so as not to worsen the space performance. 841 * A negative load factor indicates that hash code computation is 842 * in progress. 843 */ 844 int h = 0; 845 if (count == 0 || loadFactor < 0) 846 return h; // Returns zero 847 848 loadFactor = -loadFactor; // Mark hashCode computation in progress 849 Entry<?,?>[] tab = table; 850 for (Entry<?,?> entry : tab) { 851 while (entry != null) { 852 h += entry.hashCode(); 853 entry = entry.next; 854 } 855 } 856 857 loadFactor = -loadFactor; // Mark hashCode computation complete 858 859 return h; 860 } 861 862 @Override 863 public synchronized V getOrDefault(Object key, V defaultValue) { 864 V result = get(key); 865 return (null == result) ? defaultValue : result; 866 } 867 868 @SuppressWarnings("unchecked") 869 @Override 870 public synchronized void forEach(BiConsumer<? super K, ? super V> action) { 871 Objects.requireNonNull(action); // explicit check required in case 872 // table is empty. 873 final int expectedModCount = modCount; 874 875 Entry<?, ?>[] tab = table; 876 for (Entry<?, ?> entry : tab) { 877 while (entry != null) { 878 action.accept((K)entry.key, (V)entry.value); 879 entry = entry.next; 880 881 if (expectedModCount != modCount) { 882 throw new ConcurrentModificationException(); 883 } 884 } 885 } 886 } 887 888 @SuppressWarnings("unchecked") 889 @Override 890 public synchronized void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) { 891 Objects.requireNonNull(function); // explicit check required in case 892 // table is empty. 893 final int expectedModCount = modCount; 894 895 Entry<K, V>[] tab = (Entry<K, V>[])table; 896 for (Entry<K, V> entry : tab) { 897 while (entry != null) { 898 entry.value = Objects.requireNonNull( 899 function.apply(entry.key, entry.value)); 900 entry = entry.next; 901 902 if (expectedModCount != modCount) { 903 throw new ConcurrentModificationException(); 904 } 905 } 906 } 907 } 908 909 @Override 910 public synchronized V putIfAbsent(K key, V value) { 911 Objects.requireNonNull(value); 912 913 // Makes sure the key is not already in the hashtable. 914 Entry<?,?> tab[] = table; 915 int hash = key.hashCode(); 916 int index = (hash & 0x7FFFFFFF) % tab.length; 917 @SuppressWarnings("unchecked") 918 Entry<K,V> entry = (Entry<K,V>)tab[index]; 919 for (; entry != null; entry = entry.next) { 920 if ((entry.hash == hash) && entry.key.equals(key)) { 921 V old = entry.value; 922 if (old == null) { 923 entry.value = value; 924 } 925 return old; 926 } 927 } 928 929 addEntry(hash, key, value, index); 930 return null; 931 } 932 933 @Override 934 public synchronized boolean remove(Object key, Object value) { 935 Objects.requireNonNull(value); 936 937 Entry<?,?> tab[] = table; 938 int hash = key.hashCode(); 939 int index = (hash & 0x7FFFFFFF) % tab.length; 940 @SuppressWarnings("unchecked") 941 Entry<K,V> e = (Entry<K,V>)tab[index]; 942 for (Entry<K,V> prev = null; e != null; prev = e, e = e.next) { 943 if ((e.hash == hash) && e.key.equals(key) && e.value.equals(value)) { 944 modCount++; 945 if (prev != null) { 946 prev.next = e.next; 947 } else { 948 tab[index] = e.next; 949 } 950 count--; 951 e.value = null; 952 return true; 953 } 954 } 955 return false; 956 } 957 958 @Override 959 public synchronized boolean replace(K key, V oldValue, V newValue) { 960 Objects.requireNonNull(oldValue); 961 Objects.requireNonNull(newValue); 962 Entry<?,?> tab[] = table; 963 int hash = key.hashCode(); 964 int index = (hash & 0x7FFFFFFF) % tab.length; 965 @SuppressWarnings("unchecked") 966 Entry<K,V> e = (Entry<K,V>)tab[index]; 967 for (; e != null; e = e.next) { 968 if ((e.hash == hash) && e.key.equals(key)) { 969 if (e.value.equals(oldValue)) { 970 e.value = newValue; 971 return true; 972 } else { 973 return false; 974 } 975 } 976 } 977 return false; 978 } 979 980 @Override 981 public synchronized V replace(K key, V value) { 982 Objects.requireNonNull(value); 983 Entry<?,?> tab[] = table; 984 int hash = key.hashCode(); 985 int index = (hash & 0x7FFFFFFF) % tab.length; 986 @SuppressWarnings("unchecked") 987 Entry<K,V> e = (Entry<K,V>)tab[index]; 988 for (; e != null; e = e.next) { 989 if ((e.hash == hash) && e.key.equals(key)) { 990 V oldValue = e.value; 991 e.value = value; 992 return oldValue; 993 } 994 } 995 return null; 996 } 997 998 @Override 999 public synchronized V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) { 1000 Objects.requireNonNull(mappingFunction); 1001 1002 Entry<?,?> tab[] = table; 1003 int hash = key.hashCode(); 1004 int index = (hash & 0x7FFFFFFF) % tab.length; 1005 @SuppressWarnings("unchecked") 1006 Entry<K,V> e = (Entry<K,V>)tab[index]; 1007 for (; e != null; e = e.next) { 1008 if (e.hash == hash && e.key.equals(key)) { 1009 // Hashtable not accept null value 1010 return e.value; 1011 } 1012 } 1013 1014 V newValue = mappingFunction.apply(key); 1015 if (newValue != null) { 1016 addEntry(hash, key, newValue, index); 1017 } 1018 1019 return newValue; 1020 } 1021 1022 @Override 1023 public synchronized V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) { 1024 Objects.requireNonNull(remappingFunction); 1025 1026 Entry<?,?> tab[] = table; 1027 int hash = key.hashCode(); 1028 int index = (hash & 0x7FFFFFFF) % tab.length; 1029 @SuppressWarnings("unchecked") 1030 Entry<K,V> e = (Entry<K,V>)tab[index]; 1031 for (Entry<K,V> prev = null; e != null; prev = e, e = e.next) { 1032 if (e.hash == hash && e.key.equals(key)) { 1033 V newValue = remappingFunction.apply(key, e.value); 1034 if (newValue == null) { 1035 modCount++; 1036 if (prev != null) { 1037 prev.next = e.next; 1038 } else { 1039 tab[index] = e.next; 1040 } 1041 count--; 1042 } else { 1043 e.value = newValue; 1044 } 1045 return newValue; 1046 } 1047 } 1048 return null; 1049 } 1050 1051 @Override 1052 public synchronized V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) { 1053 Objects.requireNonNull(remappingFunction); 1054 1055 Entry<?,?> tab[] = table; 1056 int hash = key.hashCode(); 1057 int index = (hash & 0x7FFFFFFF) % tab.length; 1058 @SuppressWarnings("unchecked") 1059 Entry<K,V> e = (Entry<K,V>)tab[index]; 1060 for (Entry<K,V> prev = null; e != null; prev = e, e = e.next) { 1061 if (e.hash == hash && Objects.equals(e.key, key)) { 1062 V newValue = remappingFunction.apply(key, e.value); 1063 if (newValue == null) { 1064 modCount++; 1065 if (prev != null) { 1066 prev.next = e.next; 1067 } else { 1068 tab[index] = e.next; 1069 } 1070 count--; 1071 } else { 1072 e.value = newValue; 1073 } 1074 return newValue; 1075 } 1076 } 1077 1078 V newValue = remappingFunction.apply(key, null); 1079 if (newValue != null) { 1080 addEntry(hash, key, newValue, index); 1081 } 1082 1083 return newValue; 1084 } 1085 1086 @Override 1087 public synchronized V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) { 1088 Objects.requireNonNull(remappingFunction); 1089 1090 Entry<?,?> tab[] = table; 1091 int hash = key.hashCode(); 1092 int index = (hash & 0x7FFFFFFF) % tab.length; 1093 @SuppressWarnings("unchecked") 1094 Entry<K,V> e = (Entry<K,V>)tab[index]; 1095 for (Entry<K,V> prev = null; e != null; prev = e, e = e.next) { 1096 if (e.hash == hash && e.key.equals(key)) { 1097 V newValue = remappingFunction.apply(e.value, value); 1098 if (newValue == null) { 1099 modCount++; 1100 if (prev != null) { 1101 prev.next = e.next; 1102 } else { 1103 tab[index] = e.next; 1104 } 1105 count--; 1106 } else { 1107 e.value = newValue; 1108 } 1109 return newValue; 1110 } 1111 } 1112 1113 if (value != null) { 1114 addEntry(hash, key, value, index); 1115 } 1116 1117 return value; 1118 } 1119 1120 /** 1121 * Save the state of the Hashtable to a stream (i.e., serialize it). 1122 * 1123 * @serialData The <i>capacity</i> of the Hashtable (the length of the 1124 * bucket array) is emitted (int), followed by the 1125 * <i>size</i> of the Hashtable (the number of key-value 1126 * mappings), followed by the key (Object) and value (Object) 1127 * for each key-value mapping represented by the Hashtable 1128 * The key-value mappings are emitted in no particular order. 1129 */ 1130 private void writeObject(java.io.ObjectOutputStream s) 1131 throws IOException { 1132 Entry<Object, Object> entryStack = null; 1133 1134 synchronized (this) { 1135 // Write out the length, threshold, loadfactor 1136 s.defaultWriteObject(); 1137 1138 // Write out length, count of elements 1139 s.writeInt(table.length); 1140 s.writeInt(count); 1141 1142 // Stack copies of the entries in the table 1143 for (int index = 0; index < table.length; index++) { 1144 Entry<?,?> entry = table[index]; 1145 1146 while (entry != null) { 1147 entryStack = 1148 new Entry<>(0, entry.key, entry.value, entryStack); 1149 entry = entry.next; 1150 } 1151 } 1152 } 1153 1154 // Write out the key/value objects from the stacked entries 1155 while (entryStack != null) { 1156 s.writeObject(entryStack.key); 1157 s.writeObject(entryStack.value); 1158 entryStack = entryStack.next; 1159 } 1160 } 1161 1162 /** 1163 * Reconstitute the Hashtable from a stream (i.e., deserialize it). 1164 */ 1165 private void readObject(java.io.ObjectInputStream s) 1166 throws IOException, ClassNotFoundException 1167 { 1168 // Read in the length, threshold, and loadfactor 1169 s.defaultReadObject(); 1170 1171 // Read the original length of the array and number of elements 1172 int origlength = s.readInt(); 1173 int elements = s.readInt(); 1174 1175 // Compute new size with a bit of room 5% to grow but 1176 // no larger than the original size. Make the length 1177 // odd if it's large enough, this helps distribute the entries. 1178 // Guard against the length ending up zero, that's not valid. 1179 int length = (int)(elements * loadFactor) + (elements / 20) + 3; 1180 if (length > elements && (length & 1) == 0) 1181 length--; 1182 if (origlength > 0 && length > origlength) 1183 length = origlength; 1184 table = new Entry<?,?>[length]; 1185 threshold = (int)Math.min(length * loadFactor, MAX_ARRAY_SIZE + 1); 1186 count = 0; 1187 1188 // Read the number of elements and then all the key/value objects 1189 for (; elements > 0; elements--) { 1190 @SuppressWarnings("unchecked") 1191 K key = (K)s.readObject(); 1192 @SuppressWarnings("unchecked") 1193 V value = (V)s.readObject(); 1194 // synch could be eliminated for performance 1195 reconstitutionPut(table, key, value); 1196 } 1197 } 1198 1199 /** 1200 * The put method used by readObject. This is provided because put 1201 * is overridable and should not be called in readObject since the 1202 * subclass will not yet be initialized. 1203 * 1204 * <p>This differs from the regular put method in several ways. No 1205 * checking for rehashing is necessary since the number of elements 1206 * initially in the table is known. The modCount is not incremented 1207 * because we are creating a new instance. Also, no return value 1208 * is needed. 1209 */ 1210 private void reconstitutionPut(Entry<?,?>[] tab, K key, V value) 1211 throws StreamCorruptedException 1212 { 1213 if (value == null) { 1214 throw new java.io.StreamCorruptedException(); 1215 } 1216 // Makes sure the key is not already in the hashtable. 1217 // This should not happen in deserialized version. 1218 int hash = key.hashCode(); 1219 int index = (hash & 0x7FFFFFFF) % tab.length; 1220 for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) { 1221 if ((e.hash == hash) && e.key.equals(key)) { 1222 throw new java.io.StreamCorruptedException(); 1223 } 1224 } 1225 // Creates the new entry. 1226 @SuppressWarnings("unchecked") 1227 Entry<K,V> e = (Entry<K,V>)tab[index]; 1228 tab[index] = new Entry<>(hash, key, value, e); 1229 count++; 1230 } 1231 1232 /** 1233 * Hashtable bucket collision list entry 1234 */ 1235 private static class Entry<K,V> implements Map.Entry<K,V> { 1236 final int hash; 1237 final K key; 1238 V value; 1239 Entry<K,V> next; 1240 1241 protected Entry(int hash, K key, V value, Entry<K,V> next) { 1242 this.hash = hash; 1243 this.key = key; 1244 this.value = value; 1245 this.next = next; 1246 } 1247 1248 @SuppressWarnings("unchecked") 1249 protected Object clone() { 1250 return new Entry<>(hash, key, value, 1251 (next==null ? null : (Entry<K,V>) next.clone())); 1252 } 1253 1254 // Map.Entry Ops 1255 1256 public K getKey() { 1257 return key; 1258 } 1259 1260 public V getValue() { 1261 return value; 1262 } 1263 1264 public V setValue(V value) { 1265 if (value == null) 1266 throw new NullPointerException(); 1267 1268 V oldValue = this.value; 1269 this.value = value; 1270 return oldValue; 1271 } 1272 1273 public boolean equals(Object o) { 1274 if (!(o instanceof Map.Entry)) 1275 return false; 1276 Map.Entry<?,?> e = (Map.Entry<?,?>)o; 1277 1278 return (key==null ? e.getKey()==null : key.equals(e.getKey())) && 1279 (value==null ? e.getValue()==null : value.equals(e.getValue())); 1280 } 1281 1282 public int hashCode() { 1283 return hash ^ Objects.hashCode(value); 1284 } 1285 1286 public String toString() { 1287 return key.toString()+"="+value.toString(); 1288 } 1289 } 1290 1291 // Types of Enumerations/Iterations 1292 private static final int KEYS = 0; 1293 private static final int VALUES = 1; 1294 private static final int ENTRIES = 2; 1295 1296 /** 1297 * A hashtable enumerator class. This class implements both the 1298 * Enumeration and Iterator interfaces, but individual instances 1299 * can be created with the Iterator methods disabled. This is necessary 1300 * to avoid unintentionally increasing the capabilities granted a user 1301 * by passing an Enumeration. 1302 */ 1303 private class Enumerator<T> implements Enumeration<T>, Iterator<T> { 1304 Entry<?,?>[] table = Hashtable.this.table; 1305 int index = table.length; 1306 Entry<?,?> entry; 1307 Entry<?,?> lastReturned; 1308 int type; 1309 1310 /** 1311 * Indicates whether this Enumerator is serving as an Iterator 1312 * or an Enumeration. (true -> Iterator). 1313 */ 1314 boolean iterator; 1315 1316 /** 1317 * The modCount value that the iterator believes that the backing 1318 * Hashtable should have. If this expectation is violated, the iterator 1319 * has detected concurrent modification. 1320 */ 1321 protected int expectedModCount = modCount; 1322 1323 Enumerator(int type, boolean iterator) { 1324 this.type = type; 1325 this.iterator = iterator; 1326 } 1327 1328 public boolean hasMoreElements() { 1329 Entry<?,?> e = entry; 1330 int i = index; 1331 Entry<?,?>[] t = table; 1332 /* Use locals for faster loop iteration */ 1333 while (e == null && i > 0) { 1334 e = t[--i]; 1335 } 1336 entry = e; 1337 index = i; 1338 return e != null; 1339 } 1340 1341 @SuppressWarnings("unchecked") 1342 public T nextElement() { 1343 Entry<?,?> et = entry; 1344 int i = index; 1345 Entry<?,?>[] t = table; 1346 /* Use locals for faster loop iteration */ 1347 while (et == null && i > 0) { 1348 et = t[--i]; 1349 } 1350 entry = et; 1351 index = i; 1352 if (et != null) { 1353 Entry<?,?> e = lastReturned = entry; 1354 entry = e.next; 1355 return type == KEYS ? (T)e.key : (type == VALUES ? (T)e.value : (T)e); 1356 } 1357 throw new NoSuchElementException("Hashtable Enumerator"); 1358 } 1359 1360 // Iterator methods 1361 public boolean hasNext() { 1362 return hasMoreElements(); 1363 } 1364 1365 public T next() { 1366 if (modCount != expectedModCount) 1367 throw new ConcurrentModificationException(); 1368 return nextElement(); 1369 } 1370 1371 public void remove() { 1372 if (!iterator) 1373 throw new UnsupportedOperationException(); 1374 if (lastReturned == null) 1375 throw new IllegalStateException("Hashtable Enumerator"); 1376 if (modCount != expectedModCount) 1377 throw new ConcurrentModificationException(); 1378 1379 synchronized(Hashtable.this) { 1380 Entry<?,?>[] tab = Hashtable.this.table; 1381 int index = (lastReturned.hash & 0x7FFFFFFF) % tab.length; 1382 1383 @SuppressWarnings("unchecked") 1384 Entry<K,V> e = (Entry<K,V>)tab[index]; 1385 for(Entry<K,V> prev = null; e != null; prev = e, e = e.next) { 1386 if (e == lastReturned) { 1387 modCount++; 1388 expectedModCount++; 1389 if (prev == null) 1390 tab[index] = e.next; 1391 else 1392 prev.next = e.next; 1393 count--; 1394 lastReturned = null; 1395 return; 1396 } 1397 } 1398 throw new ConcurrentModificationException(); 1399 } 1400 } 1401 } 1402 }
测试程序:
1 package com.hash.hashmaptest; 2 import java.util.*; 3 4 public class HashtableTest { 5 6 public static void main(String[] args) { 7 testHashtableAPIs(); 8 } 9 10 private static void testHashtableAPIs() { 11 // 初始化随机种子 12 Random r = new Random(); 13 // 新建Hashtable 14 Hashtable table = new Hashtable(); 15 // 添加操作 16 table.put("one", new Integer(r.nextInt(10))); 17 table.put("two", new Integer(r.nextInt(10))); 18 table.put("three", new Integer(r.nextInt(10))); 19 20 // 打印出table 21 System.out.println("table:"+table ); 22 23 // 通过Iterator遍历key-value 24 Iterator iter = table.entrySet().iterator(); 25 while(iter.hasNext()) { 26 Map.Entry entry = (Map.Entry)iter.next(); 27 System.out.println("next : "+ entry.getKey() +" - "+entry.getValue()); 28 } 29 30 // Hashtable的键值对个数 31 System.out.println("size:"+table.size()); 32 33 // containsKey(Object key) :是否包含键key 34 System.out.println("contains key two : "+table.containsKey("two")); 35 System.out.println("contains key five : "+table.containsKey("five")); 36 37 // containsValue(Object value) :是否包含值value 38 System.out.println("contains value 0 : "+table.containsValue(new Integer(0))); 39 40 // remove(Object key) : 删除键key对应的键值对 41 table.remove("three"); 42 43 System.out.println("table:"+table ); 44 45 // clear() : 清空Hashtable 46 table.clear(); 47 48 // isEmpty() : Hashtable是否为空 49 System.out.println((table.isEmpty()?"table is empty":"table is not empty") ); 50 } 51 }
五、HashMap和Hashtable的对比
5.1、产生的时间和作者
HashTable产生于JDK 1.1,而HashMap产生于JDK 1.2。从时间的维度上来看,HashMap要比HashTable出现得晚一些。
5.2、从方法层面分析
两个类的继承体系有些不同,虽然都实现了Map、Cloneable、Serializable三个接口。但是HashMap继承自抽象类AbstractMap,而HashTable继承自抽象类Dictionary。其中Dictionary类是一个已经被废弃的类。
同时Hashtable比HashMap多了两个公开方法。一个是elements,这来自于抽象类Dictionary,鉴于该类已经废弃,所以这个方法也就没什么用处了。另一个多出来的方法是contains,这个多出来的方法也没什么用,因为它跟containsValue方法功能是一样的。
此外HashMap是支持null键和null值的,而Hashtable在遇到null时,会抛出NullPointerException异常。这并不是因为HashTable有什么特殊的实现层面的原因导致不能支持null键和null值,这仅仅是因为HashMap在实现时对null做了特殊处理,将null的hashCode值定为了0,从而将其存放在哈希表的第0个bucket中。
5.3、从算法层面上分析
初始容量大小和每次扩充容量大小的不同。
1 以下代码及注释来自java.util.HashTable 2 // 哈希表默认初始大小为11 3 public Hashtable() { 4 this(11, 0.75f); 5 } 6 protected void rehash() { 7 int oldCapacity = table.length; 8 Entry<K,V>[] oldMap = table; 9 10 // 每次扩容为原来的2n+1 11 int newCapacity = (oldCapacity << 1) + 1; 12 // ... 13 } 14 以下代码及注释来自java.util.HashMap 15 // 哈希表默认初始大小为2^4=16 16 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 17 void addEntry(int hash, K key, V value, int bucketIndex) { 18 // 每次扩充为原来的2n 19 if ((size >= threshold) && (null != table[bucketIndex])) { 20 resize(2 * table.length); 21 }
可以看到Hashtable默认的初始大小为11,之后每次扩充为原来的2n+1。HashMap默认的初始化大小为16,之后每次扩充为原来的2倍。如果在创建时给定了初始化大小,那么Hashtable会直接使用你给定的大小,而HashMap会将其扩充为2的幂次方大小。也就是说Hashtable会尽量使用素数、奇数。而HashMap则总是使用2的幂作为哈希表的大小。我们知道当哈希表的大小为素数时,简单的取模哈希的结果会更加均匀,所以单从这一点上看,Hashtable的哈希表大小选择,似乎更高明些。但另一方面我们又知道,在取模计算时,如果模数是2的幂,那么我们可以直接使用位运算来得到结果,效率要大大高于做除法。所以从hash计算的效率上,又是HashMap更胜一筹。所以,事实就是HashMap为了加快hash的速度,将哈希表的大小固定为了2的幂。当然这引入了哈希分布不均匀的问题,所以HashMap为解决这问题,又对hash算法做了一些改动。具体我们来看看,在获取了key对象的hashCode之后,Hashtable和HashMap分别是怎样将它们hash到确定的哈希桶(Entry数组位置)中的。
HashMap由于使用了2的幂次方,所以在取模运算时不需要做除法,只需要位的与运算就可以了。但是由于引入的hash冲突加剧问题,HashMap在调用了对象的hashCode方法之后,又做了一些位运算在打散数据。
1 以下代码及注释来自java.util.Hashtable 2 3 // hash 不能超过Integer.MAX_VALUE 所以要取其最小的31个bit 4 int hash = hash(key); 5 int index = (hash & 0x7FFFFFFF) % tab.length; 6 7 // 直接计算key.hashCode() 8 private int hash(Object k) { 9 // hashSeed will be zero if alternative hashing is disabled. 10 return hashSeed ^ k.hashCode(); 11 } 12 以下代码及注释来自java.util.HashMap 13 int hash = hash(key); 14 int i = indexFor(hash, table.length); 15 16 // 在计算了key.hashCode()之后,做了一些位运算来减少哈希冲突 17 final int hash(Object k) { 18 int h = hashSeed; 19 if (0 != h && k instanceof String) { 20 return sun.misc.Hashing.stringHash32((String) k); 21 } 22 23 h ^= k.hashCode(); 24 25 // This function ensures that hashCodes that differ only by 26 // constant multiples at each bit position have a bounded 27 // number of collisions (approximately 8 at default load factor). 28 h ^= (h >>> 20) ^ (h >>> 12); 29 return h ^ (h >>> 7) ^ (h >>> 4); 30 } 31 32 // 取模不再需要做除法 33 static int indexFor(int h, int length) { 34 // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2"; 35 return h & (length-1); 36 }
HashMap和HashTable在计算hash时都用到了一个叫hashSeed的变量。这是因为映射到同一个hash桶内的Entry对象,是以链表的形式存在的,而链表的查询效率比较低,所以HashMap/Hashtable的效率对哈希冲突非常敏感,所以可以额外开启一个可选hash(hashSeed),从而减少哈希冲突。事实上,这个优化在JDK 1.8中已经去掉了,因为JDK 1.8中,映射到同一个哈希桶(数组位置)的Entry对象,使用了红黑树来存储,从而大大加速了其查找效率。
5.4、线程安全
HashTable是同步的,HashMap不是,也就是说HashTable在多线程使用的情况下,不需要做额外的同步,而HashMap则不行。但是使用了synchronized描述符降低了效率。
5.5、代码风格
HashMap的代码要比Hashtable整洁很多。
5.6、使用情况
Hashtable已经被淘汰了,不要在代码中再使用它。简单来说就是,如果不需要线程安全,那么使用HashMap,如果需要线程安全,那么使用ConcurrentHashMap。
5.7、持续优化
虽然HashMap和Hashtable的公开接口应该不会改变,或者说改变不频繁。但每一版本的JDK,都会对HashMap和Hashtable的内部实现做优化,比如JDK 1.8的红黑树优化。所以,尽可能的使用新版本的JDK,除了那些炫酷的新功能,普通的API也会有性能上有提升。为什么HashTable已经淘汰了,还要优化它?因为有老的代码还在使用它,所以优化了它之后,这些老的代码也能获得性能提升。
六、总结
本文中我们深入探讨了三种Map结构,对于其中的实现原理,初始化,增删改查,扩容,提升查找效率等等方面进行了分析和探讨,对我们以后的使用非常有帮助。
参考文献: https://www.cnblogs.com/skywang12345/p/3310835.html
http://www.importnew.com/20386.html