写点什么

HashMap 及 HashTable 源码解析

发布于: 2021 年 11 月 07 日

[java] view plain copy




  1. static?final?Entry<?,?>[]?EMPTY_TABLE?=?{};??

  2. ??/**?

  3. ???*?The?table,?resized?as?necessary.?Length?MUST?Always?be?a?power?of?two.?

  4. ???*/??

  5. ??transient?Entry<K,V>[]?table?=?(Entry<K,V>[])?EMPTY_TABLE;??

  6. ??/**?

  7. ???*?The?number?of?key-value?mappings?contained?in?this?map.?

  8. ???*/??

  9. ??transient?int?size;??

  10. ??/**?

  11. ???*?The?next?size?value?at?which?to?resize?(capacity?*?load?factor).?

  12. ???*?@serial?

  13. ???*/??

  14. ??//?If?table?==?EMPTY_TABLE?then?this?is?the?initial?capacity?at?which?the??

  15. ??//?table?will?be?created?when?inflated.??

  16. ??int?threshold;??


下面讲解几个比较重要的方法


1)当我们进行 put 操作的时候


先贴出源码


[java] view plain copy




  1. public?V?put(K?key,?V?value)?{??

  2. ????????if?(key?==?null)??

  3. ????????????return?putForNullKey(value);?//处理 null 值??

  4. ????????int?hash?=?hash(key.hashCode());//计算 hash??

  5. ????????int?i?=?indexFor(hash,?table.length);//计算在数组中的存储位置??

  6. ????//遍历 table[i]位置的链表,查找相同的 key,若找到则使用新的 value 替换掉原来的 oldValue 并返回 oldValue??

  7. ????????for?(Entry<K,V>?e?=?table[i];?e?!=?null;?e?=?e.next)?{??

  8. ????????????Object?k;??

  9. ????????????if?(e.hash?==?hash?&&?((k?=?e.key)?==?key?||?key.equals(k)))?{??

  10. ????????????????V?oldValue?=?e.value;??

  11. ????????????????e.value?=?value;??

  12. ????????????????e.recordAccess(this);??

  13. ????????????????return?oldValue;??

  14. ????????????}??

  15. ????????}??

  16. ????//若没有在 table[i]位置找到相同的 key,则添加 key 到 table[i]位置,新的元素总是在 table[i]位置的第一个元素,原来的元素后移??

  17. ????????modCount++;??

  18. ????????addEntry(hash,?key,?value,?i);??

  19. ????????return?null;??

  20. ????}??


a HashMap 会对 null 值 key 进行特殊处理,总是放到 table[0]位置


b 计算 has 值


c 找到在 table 数组中的索引


1)遍历 table[i


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


]位置的链表,查找相同的 key,若找到则使用新的 value 替换掉原来的 oldValue 并返回 oldValue


2)若没有在 table[i]位置找到相同的 key,则添加 key 到 table[i]位置,新的元素总是在 table[i]位置的第一个元素,原来的元素后调用 addEntry 方法


[java] view plain copy




  1. void?addEntry(int?hash,?K?key,?V?value,?int?bucketIndex)?{??

  2. ????????if?((size?>=?threshold)?&&?(null?!=?table[bucketIndex]))?{??

  3. ????????????resize(2?*?table.length);??

  4. ????????????hash?=?(null?!=?key)???hash(key)?:?0;??

  5. ????????????bucketIndex?=?indexFor(hash,?table.length);??

  6. ????????}??

  7. ????????createEntry(hash,?key,?value,?bucketIndex);??

  8. ????}??


[java] view plain copy




  1. void?createEntry(int?hash,?K?key,?V?value,?int?bucketIndex)?{??

  2. ????Entry<K,V>?e?=?table[bucketIndex];??

  3. ????table[bucketIndex]?=?new?Entry<>(hash,?key,?value,?e);??

  4. ????size++;??

  5. }??


所做的工作就是:


1)判断当元素数量达到临界值(capactiy*factor)时,则进行扩容,是 table 数组长度变为 table.length*2


2)当 table[index]已存在其它元素时,会在 table[index]位置形成一个链表,将新添加的元素放在 table[index],原来的元素通过 Entry 的 next 进行链接,这样以链表形式解决 hash 冲突问题,


**2)get 方法


**同样当 key 为 null 时会进行特殊处理,在 table[0]的链表上查找 key 为 null 的元素


[java] view plain copy




  1. public?V?get(Object?key)?{??

  2. ???????if?(key?==?null)??

  3. ???????????return?getForNullKey();??

  4. ???????Entry<K,V>?entry?=?getEntry(key);??

  5. ???????return?null?==?entry???null?:?entry.getValue();??

  6. ???}??


get 的过程是先计算 hash 然后通过 hash 与 table.length 取摸计算 index 值,然后遍历 table[index]上的链表,直到找到 key,


然后返回,找不到返回 null


[java] view plain copy




  1. final?Entry<K,V>?getEntry(Object?key)?{??

  2. ????????if?(size?==?0)?{??

  3. ????????????return?null;??

  4. ????????}??

  5. ????????int?hash?=?(key?==?null)???0?:?hash(key);??

  6. ????????for?(Entry<K,V>?e?=?table[indexFor(hash,?table.length)];??

  7. ?????????????e?!=?null;??

  8. ?????????????e?=?e.next)?{??

  9. ????????????Object?k;??

  10. ????????????if?(e.hash?==?hash?&&??

  11. ????????????????((k?=?e.key)?==?key?||?(key?!=?null?&&?key.equals(k))))??

  12. ????????????????return?e;??

  13. ????????}??

  14. ????????return?null;??

  15. ????}??


3)remove 方法


remove 方法和 put get 类似,计算 hash,计算 index,然后遍历查找,将找到的元素从 table[index]链表移除


[java] view plain copy




  1. public?V?remove(Object?key)?{??

  2. ????????Entry<K,V>?e?=?removeEntryForKey(key);??

  3. ????????return?(e?==?null???null?:?e.value);??

  4. ????}??


[java] view plain copy




  1. final?Entry<K,V>?removeEntryForKey(Object?key)?{??

  2. ????????if?(size?==?0)?{??

  3. ????????????return?null;??

  4. ????????}??

  5. ????????int?hash?=?(key?==?null)???0?:?hash(key);??

  6. ????????int?i?=?indexFor(hash,?table.length);??

  7. ????????Entry<K,V>?prev?=?table[i];??

  8. ????????Entry<K,V>?e?=?prev;??

  9. ????????while?(e?!=?null)?{??

  10. ????????????Entry<K,V>?next?=?e.next;??

  11. ????????????Object?k;??

  12. ????????????if?(e.hash?==?hash?&&??

  13. ????????????????((k?=?e.key)?==?key?||?(key?!=?null?&&?key.equals(k))))?{??

  14. ????????????????modCount++;??

  15. ????????????????size--;??

  16. ????????????????if?(prev?==?e)??

  17. ????????????????????table[i]?=?next;??

  18. ????????????????else??

  19. ????????????????????prev.next?=?next;??

  20. ????????????????e.recordRemoval(this);??

  21. ????????????????return?e;??

  22. ????????????}??

  23. ????????????prev?=?e;??

  24. ????????????e?=?next;??

  25. ????????}??

  26. ????????return?e;??

  27. ????}??


4)clear()方法


clear 方法非常简单,就是遍历 table 然后把每个位置置为 null,同时修改元素个数为 0


需要注意的是 clear 方法只会清楚里面的元素,并不会重置 capactiy


[java] view plain copy




  1. public?void?clear()?{??

  2. ????????modCount++;??

  3. ????????Arrays.fill(table,?null);??

  4. ????????size?=?0;??

  5. ????}??


5)containsKey 和 containsValue


containsKey 方法是先计算 hash 然后使用 hash 和 table.length 取摸得到 index 值,遍历 table[index]元素查找是否包含 key 相同的值


[java] view plain copy




  1. public?boolean?containsKey(Object?key)?{??

  2. ???????return?getEntry(key)?!=?null;??

  3. ???}??


[java] view plain copy




  1. final?Entry<K,V>?getEntry(Object?key)?{??

  2. ????????if?(size?==?0)?{??

  3. ????????????return?null;??

  4. ????????}??

  5. ????????int?hash?=?(key?==?null)???0?:?hash(key);??

  6. ????????for?(Entry<K,V>?e?=?table[indexFor(hash,?table.length)];??

  7. ?????????????e?!=?null;??

  8. ?????????????e?=?e.next)?{??

  9. ????????????Object?k;??

  10. ????????????if?(e.hash?==?hash?&&??

  11. ????????????????((k?=?e.key)?==?key?||?(key?!=?null?&&?key.equals(k))))??

  12. ????????????????return?e;??

  13. ????????}??

  14. ????????return?null;??

  15. ????}??


6)containsValue 方法就比较粗暴了,就是直接遍历所有元素直到找到 value,由此可见 HashMap 的 containsValue 方法本质上和普通数组和 list 的 contains 方法没什么区别,你别指望它会像 containsKey 那么高效


[java] view plain copy




  1. public?boolean?containsValue(Object?value)?{??

  2. ????????//?Same?idea?as?size()??

  3. ????????if?(value?==?null)??

  4. ????????????throw?new?NullPointerException();??

  5. ????????final?Segment<K,V>[]?segments?=?this.segments;??

  6. ????????boolean?found?=?false;??

  7. ????????long?last?=?0;??

  8. ????????int?retries?=?-1;??

  9. ????????try?{??

  10. ????????????outer:?for?(;;)?{??

  11. ????????????????if?(retries++?==?RETRIES_BEFORE_LOCK)?{??

  12. ????????????????????for?(int?j?=?0;?j?<?segments.length;?++j)??

  13. ????????????????????????ensureSegment(j).lock();?//?force?creation??

  14. ????????????????}??

  15. ????????????????long?hashSum?=?0L;??

  16. ????????????????int?sum?=?0;??

  17. ????????????????for?(int?j?=?0;?j?<?segments.length;?++j)?{??

  18. ????????????????????HashEntry<K,V>[]?tab;??

  19. ????????????????????Segment<K,V>?seg?=?segmentAt(segments,?j);??

  20. ????????????????????if?(seg?!=?null?&&?(tab?=?seg.table)?!=?null)?{??

  21. ????????????????????????for?(int?i?=?0?;?i?<?tab.length;?i++)?{??

  22. ????????????????????????????HashEntry<K,V>?e;??

  23. ????????????????????????????for?(e?=?entryAt(tab,?i);?e?!=?null;?e?=?e.next)?{??

  24. ????????????????????????????????V?v?=?e.value;??

  25. ????????????????????????????????if?(v?!=?null?&&?value.equals(v))?{??

  26. ????????????????????????????????????found?=?true;??

  27. ????????????????????????????????????break?outer;??

  28. ????????????????????????????????}??

  29. ????????????????????????????}??

  30. ????????????????????????}??

  31. ????????????????????????sum?+=?seg.modCount;??

  32. ????????????????????}??

  33. ????????????????}??

  34. ????????????????if?(retries?>?0?&&?sum?==?last)??

  35. ????????????????????break;??

  36. ????????????????last?=?sum;??

  37. ????????????}??

  38. ????????}?finally?{??

  39. ????????????if?(retries?>?RETRIES_BEFORE_LOCK)?{??

  40. ????????????????for?(int?j?=?0;?j?<?segments.length;?++j)??

  41. ????????????????????segmentAt(segments,?j).unlock();??

  42. ????????????}??

  43. ????????}??

  44. ????????return?found;??

  45. ????}??


2 HashTable 源码分析


1)构造方法有三个 ,带零个、一个、两个参数的,最终都会调用两个参数的构造方法


其思想也比较简单,检查参数的合法性


public Hashtable(int initialCapacity, float loadFactor) {


if (initialCapacity < 0)


throw new IllegalArgumentException("Illegal Capacity: "+


initialCapacity);


if (loadFactor <= 0 || Float.isNaN(loadFactor))


throw new IllegalArgumentException("Illegal Load: "+loadFactor);


if (initialCapacity==0)


initialCapacity = 1;


this.loadFactor = loadFactor;


table = new Entry[initialCapacity];


threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);


initHashSeedAsNeeded(initialCapacity);


}


2)put 操作,直接在方法的前面加上 synchronized 关键字,简单粗暴,在并发激烈的情况下,效率较低,所以才会有


ConcurrentHashMap 的诞生。


public synchronized V put(K key, V value) {


// Make sure the value is not null


if (value == null) {


throw new NullPointerException();


}


// Makes sure the key is not already in the hashtable.


Entry tab[] = table;


int hash = hash(key);


int index = (hash & 0x7FFFFFFF) % tab.length;


for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {


if ((e.hash == hash) && e.key.equals(key)) {


V old = e.value;


e.value = value;


return old;


}


}


modCount++;


if (count >= threshold) {


// Rehash the table if the threshold is exceeded


rehash();


tab = table;


hash = hash(key);


index = (hash & 0x7FFFFFFF) % tab.length;


}


// Creates the new entry.


Entry<K,V> e = tab[index];


tab[index] = new Entry<>(hash, key, value, e);


count++;


return null;


}


3)get 操作也一样,直接在访问该方法的时候上锁,确保同一个时刻只有一个线程能访问


public synchronized V get(Object key) {


Entry tab[] = table;


int hash = hash(key);


int index = (hash & 0x7FFFFFFF) % tab.length;


for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {


if ((e.hash == hash) && e.key.equals(key)) {


return e.value;


}


}


return null;


}


4)其他方法就不说了,基本更 HashMap 里面的方法一样,只是加上同步机制而已

评论

发布
暂无评论
HashMap及HashTable源码解析