1、Hashtable-源码分析-底层数据结构

  1. 1Hashtable-源码分析-底层数据结构:与HashMap底层数据结构一样、
  2. 2Hashtable-源码中属性字段:
  3. /**
  4. * 键值对/Entry数组,每个Entry本质上是一个单向链表的表头
  5. */
  6. private transient Entry<?, ?>[] table;
  7. /**
  8. * 当前表中的Entry数量,如果超过了阈值,就会扩容,即调用rehash方法
  9. */
  10. private transient int count;
  11. /**
  12. * rehash阈值
  13. *
  14. * @serial
  15. */
  16. private int threshold;
  17. /**
  18. * 负载因子
  19. *
  20. * @serial
  21. */
  22. private float loadFactor;
  23. /**
  24. * 用来实现"fail-fast"机制的(也就是快速失败)。所谓快速失败就是在并发集合中,其进行
  25. * 迭代操作时,若有其他线程对其进行结构性的修改,这时迭代器会立马感知到,并且立即抛出
  26. * ConcurrentModificationException异常,而不是等到迭代完成之后才告诉你(你已经出错了)。
  27. */
  28. private transient int modCount = 0;

2、Hashtable-源码分析-构造方法

  1. 1Hashtable-源码分析-构造方法:有4个构造方法
  2. 2、源代码:
  3. //默认构造函数,指定的容量大小是11;加载因子是0.75
  4. public Hashtable() {
  5. this(11, 0.75f);
  6. }
  7. //指定容量大小的构造函数,加载因子是0.75
  8. public Hashtable(int initialCapacity) {
  9. this(initialCapacity, 0.75f);
  10. }
  11. //指定容量大小和加载因子的构造函数
  12. public Hashtable(int initialCapacity, float loadFactor) {
  13. //验证初始容量,初始容量不能小于0
  14. if (initialCapacity < 0)
  15. throw new IllegalArgumentException("Illegal Capacity: "+
  16. initialCapacity);
  17. if (loadFactor <= 0 || Float.isNaN(loadFactor))
  18. throw new IllegalArgumentException("Illegal Load: "+loadFactor);
  19. if (initialCapacity==0)
  20. initialCapacity = 1;
  21. this.loadFactor = loadFactor;
  22. //初始化table,获得大小为initialCapacity的table数组
  23. table = new Entry<?,?>[initialCapacity];
  24. //计算阀值
  25. threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
  26. }
  27. //构造一个与给定的 Map 具有相同映射关系的新哈希表,容量是原来容量的两倍和11的最大值。
  28. public Hashtable(Map<? extends K, ? extends V> t) {
  29. //设置table容器大小,其值==t.size * 2 + 1 Vs 11的最大值
  30. this(Math.max(2*t.size(), 11), 0.75f);
  31. putAll(t);
  32. }

3、Hashtable-源码分析-Entry类

  1. 1Entry类,与其他Map实现类一样,HashtableEntry类实现了Map.Entry<K,V>。
  2. 2、源代码:
  3. /**
  4. * Hashtable bucket collision list entry
  5. * 哈希表bucket冲突列表项
  6. * Hashtable的Entry节点,它本质上是一个单向链表。
  7. */
  8. private static class Entry<K,V> implements Map.Entry<K,V> {
  9. final int hash;
  10. final K key;
  11. V value;
  12. Entry<K,V> next;
  13. protected Entry(int hash, K key, V value, Entry<K,V> next) {
  14. this.hash = hash;
  15. this.key = key;
  16. this.value = value;
  17. this.next = next;
  18. }
  19. @SuppressWarnings("unchecked")
  20. protected Object clone() {
  21. return new Entry<>(hash, key, value,
  22. (next==null ? null : (Entry<K,V>) next.clone()));
  23. }
  24. // Map.Entry Ops
  25. public K getKey() {
  26. return key;
  27. }
  28. public V getValue() {
  29. return value;
  30. }
  31. // 进行判断value是否为空,即不允许value为空,其实key也不能为空
  32. public V setValue(V value) {
  33. if (value == null)
  34. throw new NullPointerException();
  35. V oldValue = this.value;
  36. this.value = value;
  37. return oldValue;
  38. }
  39. public boolean equals(Object o) {
  40. if (!(o instanceof Map.Entry))
  41. return false;
  42. Map.Entry<?,?> e = (Map.Entry<?,?>)o;
  43. return (key==null ? e.getKey()==null : key.equals(e.getKey())) &&
  44. (value==null ? e.getValue()==null : value.equals(e.getValue()));
  45. }
  46. public int hashCode() {
  47. // 直接用hash进行异或,与HashMap不同
  48. return hash ^ Objects.hashCode(value);
  49. }
  50. public String toString() {
  51. return key.toString()+"="+value.toString();
  52. }
  53. }

4、Hashtable-源码分析-添加元素

  1. 1Hashtable-源码分析-添加元素:.put()方法
  2. 2、源代码:
  3. //添加元素,要求key和value都不能为null
  4. public synchronized V put(K key, V value) {
  5. // Make sure the value is not null
  6. if (value == null) {
  7. throw new NullPointerException();
  8. }
  9. /*
  10. * 确保key在table[]是不重复的
  11. * 处理过程:
  12. * 1、计算key的hash值,确认在table[]中的索引位置
  13. * 2、迭代index索引位置,如果该位置处的链表中存在一个一样的key,则替换其value,返回旧值。
  14. */
  15. Entry<?,?> tab[] = table;
  16. //计算key的hash值
  17. int hash = key.hashCode();
  18. //确认该key的索引位置
  19. //&0x7FFFFFFF的目的是为了将负的hash值转化为正值,因为hash值有可能为负数,而 hash & 0x7FFFFFFF 后,只有符号外改变,而后面的位都不变。
  20. int index = (hash & 0x7FFFFFFF) % tab.length;
  21. @SuppressWarnings("unchecked")
  22. Entry<K,V> entry = (Entry<K,V>)tab[index];
  23. //迭代,寻找该key,替换
  24. for(; entry != null ; entry = entry.next) {
  25. if ((entry.hash == hash) && entry.key.equals(key)) {
  26. V old = entry.value;
  27. entry.value = value;
  28. return old;
  29. }
  30. }
  31. //执行到这里,说明key没有重复,则进行插入操作
  32. addEntry(hash, key, value, index);
  33. //如果是插入全新的Entry的话,就返回空
  34. return null;
  35. }
  36. /**
  37. 进行插入操作
  38. */
  39. private void addEntry(int hash, K key, V value, int index) {
  40. modCount++;
  41. Entry<?,?> tab[] = table;
  42. //如果容器中的元素数量已经达到阀值,则进行扩容操作
  43. if (count >= threshold) {
  44. // Rehash the table if the threshold is exceeded
  45. //扩容方法,见后续章节简介
  46. rehash();
  47. tab = table;
  48. //计算key的索引
  49. hash = key.hashCode();
  50. index = (hash & 0x7FFFFFFF) % tab.length;
  51. }
  52. // Creates the new entry.
  53. @SuppressWarnings("unchecked")
  54. // 在索引位置处插入一个新的节点
  55. Entry<K,V> e = (Entry<K,V>) tab[index];
  56. tab[index] = new Entry<>(hash, key, value, e);
  57. //容器中元素+1
  58. count++;
  59. }
  60. 3、.put()方法,执行过程:
  61. 3-1、计算keyhash值,根据hash值获得keytable数组中的索引位置,然后迭代该key处的Entry链表(我们暂且理解为链表),若该链表中存在一个这个的key对象,那么就直接替换其value值即可,否则在将改key-value节点插入该index索引位置处。
  62. 4、.put()方法,特殊点:
  63. 4-1、在put方法中,如果需要向table[]中添加Entry元素,会首先进行容量校验,如果容量已经达到了阀值,HashTable就会进行扩容处理rehash()。

5、Hashtable-源码分析-扩容方法

  1. 1Hashtable-源码分析-扩容方法:.rehash();
  2. 2、源代码:
  3. /**
  4. 在put方法中,如果需要向table[]中添加Entry元素,会首先进行容量校验,如果容量已经达到了阀值,HashTable就会进行扩容处理rehash()。
  5. */
  6. @SuppressWarnings("unchecked")
  7. protected void rehash() {
  8. //旧容量=链表的长度
  9. int oldCapacity = table.length;
  10. //获取旧链表,将老数组赋值给oldMap
  11. Entry<?,?>[] oldMap = table;
  12. // overflow-conscious code
  13. //新容量=旧容量 * 2 + 1
  14. int newCapacity = (oldCapacity << 1) + 1;
  15. if (newCapacity - MAX_ARRAY_SIZE > 0) {
  16. if (oldCapacity == MAX_ARRAY_SIZE)
  17. // Keep running with MAX_ARRAY_SIZE buckets
  18. return;
  19. newCapacity = MAX_ARRAY_SIZE;
  20. }
  21. //新建一个size = newCapacity 的HashTable
  22. Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];
  23. modCount++;
  24. //重新计算阀值:在新容量*加载因子和最大值 + 1之间取最小值
  25. threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
  26. table = newMap;
  27. //将原来的元素拷贝到新的HashTable中
  28. //双重遍历老数组,第一重遍历是遍历桶上的各个Entry,将老数组的节点移到新数组
  29. for (int i = oldCapacity ; i-- > 0 ;) {
  30. //第二重遍历是遍历桶上Entry后面接的链表
  31. for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {
  32. //将old赋值给e,e代表遍历到的节点
  33. Entry<K,V> e = old;
  34. //old指向他的后继节点,下次遍历就遍历到后继节点了
  35. old = old.next;
  36. int index = (e.hash & 0x7FFFFFFF) % newCapacity;
  37. //把新桶上已存在链表节点作为当前节点的后继节点
  38. e.next = (Entry<K,V>)newMap[index];
  39. //遍历到的节点放到新数组的桶上,[!!!]是链表的头结点
  40. newMap[index] = e;
  41. }
  42. }
  43. }

6、Hashtable-源码分析-获取元素

  1. 1Hashtable-源码分析-获取元素:.get()方法
  2. 2、源代码:
  3. @SuppressWarnings("unchecked")
  4. public synchronized V get(Object key) {
  5. Entry<?,?> tab[] = table;
  6. int hash = key.hashCode();
  7. //桶的索引是用key的hash值和0x7FFFFFFF做与运算,最后对数组长度取余
  8. int index = (hash & 0x7FFFFFFF) % tab.length;
  9. for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
  10. if ((e.hash == hash) && e.key.equals(key)) {
  11. return (V)e.value;
  12. }
  13. }
  14. //如果是插入全新的Entry的话,就返回空
  15. return null;
  16. }
  17. 3、.get()方法-执行过程:
  18. 3-1、计算keyhash值,判断在table数组中的索引位置,然后迭代链表,匹配直到找到相对应keyvalue,若没有找到返回null

7、Hashtable-源码分析-删除元素

  1. 1Hashtable-源码分析-删除元素:.remove()方法
  2. 2、源代码:
  3. public synchronized V remove(Object key) {
  4. Entry<?,?> tab[] = table;
  5. //计算key的hash
  6. int hash = key.hashCode();
  7. //根据hash计算桶的索引index
  8. int index = (hash & 0x7FFFFFFF) % tab.length;
  9. @SuppressWarnings("unchecked")
  10. Entry<K,V> e = (Entry<K,V>)tab[index];
  11. //拿到桶的首节点后,往死里遍历首节点后面的链表
  12. for(Entry<K,V> prev = null ; e != null ; prev = e, e = e.next) {
  13. //如果key的hash相等,而且key本身也相等,说明就是要删掉这个节点
  14. if ((e.hash == hash) && e.key.equals(key)) {
  15. //自增
  16. modCount++;
  17. //如果prev不为空,说明要删除的不是头结点
  18. if (prev != null) {
  19. //那么将目标节点的头结点指向目标节点的后继节点
  20. prev.next = e.next;
  21. } else {
  22. //如果删除的就是头结点,那么将原来头结点的后继节点指定为头结点
  23. tab[index] = e.next;
  24. }
  25. //元素总数 - 1
  26. count--;
  27. //取出要删除的节点的value
  28. V oldValue = e.value;
  29. //将e的value置空(需要吗?)
  30. e.value = null;
  31. //返回删除节点的value
  32. return oldValue;
  33. }
  34. }
  35. return null;
  36. }

8、Hashtable-源码分析-遍历元素

  1. 1Hashtable-源码分析-遍历元素:方法有很多种,此处只介绍Enumeration方式
  2. 2Enumeration类-源代码:
  3. /**
  4. * Enumerator的作用是提供了通过elements()遍历Hashtable的接口和通过entrySet()遍历Hashtable的接口。因为,它同时实现了 Enumerator接口和Iterator接口。
  5. */
  6. private class Enumerator<T> implements Enumeration<T>, Iterator<T> {
  7. // 指向Hashtable的table
  8. Entry<?, ?>[] table = Hashtable.this.table;
  9. // Hashtable的总的大小
  10. int index = table.length;
  11. Entry<?, ?> entry;
  12. Entry<?, ?> lastReturned;
  13. int type;
  14. /**
  15. * Enumerator是 迭代器(Iterator) 还是 枚举类(Enumeration)的标志
  16. * iterator为true,表示它是迭代器;否则,是枚举类。
  17. */
  18. boolean iterator;
  19. /**
  20. * 在将Enumerator当作迭代器使用时会用到,用来实现fail-fast机制。
  21. */
  22. protected int expectedModCount = modCount;
  23. Enumerator(int type, boolean iterator) {
  24. this.type = type;
  25. this.iterator = iterator;
  26. }
  27. /**
  28. * 从遍历table的数组的末尾向前查找,直到找到不为null的Entry。
  29. */
  30. public boolean hasMoreElements() {
  31. Entry<?, ?> e = entry;
  32. int i = index;
  33. Entry<?, ?>[] t = table;
  34. /* Use locals for faster loop iteration */
  35. while (e == null && i > 0) {
  36. e = t[--i];
  37. }
  38. entry = e;
  39. index = i;
  40. return e != null;
  41. }
  42. /**
  43. * 获取下一个元素
  44. * 注意:从hasMoreElements() 和nextElement() 可以看出Hashtable的elements()遍历方式
  45. * 首先,从后向前的遍历table数组。table数组的每个节点都是一个单向链表(Entry)。
  46. * 然后,依次向后遍历单向链表Entry。
  47. */
  48. @SuppressWarnings("unchecked")
  49. public T nextElement() {
  50. Entry<?, ?> et = entry;
  51. int i = index;
  52. Entry<?, ?>[] t = table;
  53. /* Use locals for faster loop iteration */
  54. while (et == null && i > 0) {
  55. et = t[--i];
  56. }
  57. entry = et;
  58. index = i;
  59. if (et != null) {
  60. Entry<?, ?> e = lastReturned = entry;
  61. entry = e.next;
  62. return type == KEYS ? (T) e.key : (type == VALUES ? (T) e.value : (T) e);
  63. }
  64. throw new NoSuchElementException("Hashtable Enumerator");
  65. }
  66. // 迭代器Iterator的判断是否存在下一个元素
  67. // 实际上,它是调用的hasMoreElements()
  68. public boolean hasNext() {
  69. return hasMoreElements();
  70. }
  71. // 迭代器获取下一个元素
  72. // 实际上,它是调用的nextElement()
  73. public T next() {
  74. if (modCount != expectedModCount)
  75. throw new ConcurrentModificationException();
  76. return nextElement();
  77. }
  78. // 迭代器的remove()接口。
  79. // 首先,它在table数组中找出要删除元素所在的Entry,
  80. // 然后,删除单向链表Entry中的元素。
  81. public void remove() {
  82. if (!iterator)
  83. throw new UnsupportedOperationException();
  84. if (lastReturned == null)
  85. throw new IllegalStateException("Hashtable Enumerator");
  86. if (modCount != expectedModCount)
  87. throw new ConcurrentModificationException();
  88. synchronized (Hashtable.this) {
  89. Entry<?, ?>[] tab = Hashtable.this.table;
  90. int index = (lastReturned.hash & 0x7FFFFFFF) % tab.length;
  91. //获取该槽位第一个元素
  92. @SuppressWarnings("unchecked")
  93. Entry<K, V> e = (Entry<K, V>) tab[index];
  94. //从单链表的一端向后遍历
  95. for (Entry<K, V> prev = null; e != null; prev = e, e = e.next) {
  96. //当前元素即为上一个返回元素
  97. if (e == lastReturned) {
  98. modCount++;
  99. expectedModCount++;
  100. //删除上一个元素
  101. if (prev == null)
  102. tab[index] = e.next;
  103. else
  104. prev.next = e.next;
  105. count--;
  106. lastReturned = null;
  107. return;
  108. }
  109. }
  110. throw new ConcurrentModificationException();
  111. }
  112. }
  113. }
  114. 3Demo
  115. //通过Enumeration来遍历Hashtable
  116. Enumeration<String> enu = table.keys();
  117. while(enu.hasMoreElements()) {
  118. System.out.println("Enumeration:"+table.keys()+" "+enu.nextElement());
  119. }