HashTable代码解析
HashTable继承关系如下:
HashTable是一个线程安全的【键-值对】存储结构。其存储结构和HashMap相同,参考这里。
1. HashTable定义了一个类型为Entry<K,V>的数组table用来存储数据。
1 2 3 4 | /** * The hash table data. */ private transient Entry<K,V>[] table; |
类型Entry<K,V>的定义如下:
1 2 3 4 5 6 7 8 9 | /** * Hashtable bucket collision list entry */ private static class Entry<K,V> implements Map.Entry<K,V> { int hash; final K key; V value; Entry<K,V> next; } |
由Entry<K,V>的定义可知,上图每个节点中其实存了4个变量:
key表示键,即存入map的键值
value表示值,即存入map的值
next表示下一个Entry节点
hash表示key的哈希值。
那么,table的图示为:
2. HashTable定义了count值来表示HashTable中元素的个数
1 2 3 4 | /** * The total number of entries in the hash table. */ private transient int count; |
由于所有对count值进行操作的方法都是线程安全的,所以count可以精确表示HashTable中元素的个数。(在HashMap中,size()方法是不精确的)
有了精确的count值,求size() / isEmpty() 就比较简单了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | /** * Returns the number of keys in this hashtable. * * @return the number of keys in this hashtable. */ public synchronized int size() { return count; } /** * Tests if this hashtable maps no keys to values. * * @return <code>true</code> if this hashtable maps no keys to values; * <code>false</code> otherwise. */ public synchronized boolean isEmpty() { return count == 0 ; } |
注意: 这些方法都带有synchronized关键字。
3. HashTable同样定义了
threshold: hashtable的重新扩容的阈值,一般值为(int)(capacity * loadFactor)。二般情况下是什么值呢?就是当HashTable的table数组的大小已经超过Integer最大值-8时,rehash的时候不在扩大table数组的大小,而是将threshold值放到最大。
loadFactor: 负载因子,默认是0.75f
modCount: 修改次数
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | /** * The table is rehashed when its size exceeds this threshold. (The * value of this field is (int)(capacity * loadFactor).) * * @serial */ private int threshold; /** * The load factor for the hashtable. * * @serial */ private float loadFactor; /** * The number of times this Hashtable has been structurally modified * Structural modifications are those that change the number of entries in * the Hashtable or otherwise modify its internal structure (e.g., * rehash). This field is used to make iterators on Collection-views of * the Hashtable fail-fast. (See ConcurrentModificationException). */ private transient int modCount = 0 ; |
3. HashTable默认构造函数为
1 2 3 4 5 6 7 | /** * Constructs a new, empty hashtable with a default initial capacity (11) * and load factor (0.75). */ public Hashtable() { this ( 11 , 0 .75f); } |
为什么初始容量为11 ??
其中, this()调用了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | /** * Constructs a new, empty hashtable with the specified initial * capacity and the specified load factor. * * @param initialCapacity the initial capacity of the hashtable. * @param loadFactor the load factor of the hashtable. * @exception IllegalArgumentException if the initial capacity is less * than zero, or if the load factor is nonpositive. */ 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数组变量 table = new Entry[initialCapacity]; // 求threshold的值 threshold = ( int )Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1 ); initHashSeedAsNeeded(initialCapacity); } |
4. hash()方法
1 2 3 4 | private int hash(Object k) { // hashSeed will be zero if alternative hashing is disabled. return hashSeed ^ k.hashCode(); } |
5. put()方法,使用了synchronized方法修饰
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | /** * Maps the specified <code>key</code> to the specified * <code>value</code> in this hashtable. Neither the key nor the * value can be <code>null</code>. <p> * * The value can be retrieved by calling the <code>get</code> method * with a key that is equal to the original key. * * @param key the hashtable key * @param value the value * @return the previous value of the specified key in this hashtable, * or <code>null</code> if it did not have one * @exception NullPointerException if the key or value is * <code>null</code> * @see Object#equals(Object) * @see #get(Object) */ 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); // 在HashMap中,求一个key的索引位置是只用的hash & (tab.length-1) int index = (hash & 0x7FFFFFFF ) % tab.length; // 如果已经包含了该key,更新value,并返回旧的value 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++; // 如果HashTable中元素的个数已经超过了阈值threshold,需要对HashTable扩容,从新hash 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); // 元素个数加1 count++; return null ; } |
6. rehash(),HashTable是如何进行rehash的呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | /** * Increases the capacity of and internally reorganizes this * hashtable, in order to accommodate and access its entries more * efficiently. This method is called automatically when the * number of keys in the hashtable exceeds this hashtable's capacity * and load factor. */ protected void rehash() { int oldCapacity = table.length; Entry<K,V>[] oldMap = table; // overflow-conscious code // oldCapacity左移1位,扩大2倍,可能会溢出 int newCapacity = (oldCapacity << 1 ) + 1 ; if (newCapacity - MAX_ARRAY_SIZE > 0 ) { if (oldCapacity == MAX_ARRAY_SIZE) // Keep running with MAX_ARRAY_SIZE buckets return ; newCapacity = MAX_ARRAY_SIZE; } // 创建新的table,大小为newCapacity Entry<K,V>[] newMap = new Entry[newCapacity]; modCount++; threshold = ( int )Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1 ); boolean rehash = initHashSeedAsNeeded(newCapacity); // 更新table数组,为新的newMap table = newMap; // 遍历oldMap,迁移到新的newMap中 // oldMap数组的长度为oldCapacity for ( int i = oldCapacity ; i-- > 0 ;) { for (Entry<K,V> old = oldMap[i] ; old != null ; ) { Entry<K,V> e = old; old = old.next; if (rehash) { e.hash = hash(e.key); } int index = (e.hash & 0x7FFFFFFF ) % newCapacity; e.next = newMap[index]; newMap[index] = e; } } } |
7. get()方法比较简单
(a). 根据key,求出hash值
(b). 根据hash值求出key所在的table的索引index,可以定位到链表的第一个元素
(c). 遍历链表娶老婆(元素), 娶不到返回null
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | /** * Returns the value to which the specified key is mapped, * or {@code null} if this map contains no mapping for the key. * * <p>More formally, if this map contains a mapping from a key * {@code k} to a value {@code v} such that {@code (key.equals(k))}, * then this method returns {@code v}; otherwise it returns * {@code null}. (There can be at most one such mapping.) * * @param key the key whose associated value is to be returned * @return the value to which the specified key is mapped, or * {@code null} if this map contains no mapping for the key * @throws NullPointerException if the specified key is null * @see #put(Object, Object) */ 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 ; } |
8. remove(),最后再来学习下remove()方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | /** * Removes the key (and its corresponding value) from this * hashtable. This method does nothing if the key is not in the hashtable. * * @param key the key that needs to be removed * @return the value to which the key had been mapped in this hashtable, * or <code>null</code> if the key did not have a mapping * @throws NullPointerException if the key is <code>null</code> */ public synchronized V remove(Object key) { Entry tab[] = table; int hash = hash(key); int index = (hash & 0x7FFFFFFF ) % tab.length; // 和get方法类似,同样是先获取到key对应的链表tab[index] // 然后遍历链表,移除元素 for (Entry<K,V> e = tab[index], prev = null ; e != null ; prev = e, e = e.next) { // prev -> e ,prev是e的下一个元素,e就是要删除的元素 if ((e.hash == hash) && e.key.equals(key)) { modCount++; if (prev != null ) { prev.next = e.next; } else { tab[index] = e.next; } // count个数减1 count--; V oldValue = e.value; e.value = null ; return oldValue; } } return null ; } |
HashTable的线程安全是使用synchronized关键字实现的,因此效率不高,所以在多线程环境下,推荐使用ConcurrentHashMap。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 字符编码:从基础到乱码解决
2015-04-12 Git学习笔记五--分支管理
2015-04-12 Git库搭建好之后,当要提交一个新的文件,需要做的是3个步骤