HashTable原理与源码分析
本文版权归 远方的风lyh和博客园共有,欢迎转载,但须保留此段声明,并给出原文链接,谢谢合作,如有错误之处忘不吝批评指正!
HashTable内部存储结构
HashTable内部存储结构为数组+单向链表的形式存储数据,即定义的 Entry<?,?>[] table 变量
源码分析
变量定义
//使用Entry数组存储数据 (Entry 单向链表) private transient Entry<?,?>[] table; //已经存储在table 的 Entry 个数 private transient int count; /**** * Entry数组扩容阈值(count) count>=threshold 时扩容 Entry数组会进行扩容 * 建议不要设置超过1 更要不设置太大,导致链表长度过长 会导致查询很慢 * 比如 HashTble initialCapacity =5 loadFactor=0.75 则计算 threshold =3 * when count >=3 时 table开始扩容(具体如何扩容的看扩容代码) * ***/ private int threshold; //负载因子 用于计算 threshold private float loadFactor; //记录修改次数 private transient int modCount = 0; //table数组的最大长度 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
Entry点向链表结构
/** * Hashtable bucket collision list entry *单向链表 */ private static class Entry<K,V> implements Map.Entry<K,V> { //hash值 final int hash; //key final K key; //value V value; //后继 Entry<K,V> next; protected Entry(int hash, K key, V value, Entry<K,V> next) { this.hash = hash; this.key = key; this.value = value; this.next = next; } @SuppressWarnings("unchecked") protected Object clone() { return new Entry<>(hash, key, value, (next==null ? null : (Entry<K,V>) next.clone())); } // Map.Entry Ops public K getKey() { return key; } public V getValue() { return value; } public V setValue(V value) { if (value == null) throw new NullPointerException(); V oldValue = this.value; this.value = value; return oldValue; } public boolean equals(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry<?,?> e = (Map.Entry<?,?>)o; return (key==null ? e.getKey()==null : key.equals(e.getKey())) && (value==null ? e.getValue()==null : value.equals(e.getValue())); } public int hashCode() { return hash ^ Objects.hashCode(value); } public String toString() { return key.toString()+"="+value.toString(); } }
构造函数
/*** * 初始化HashTable 指定初始化容量(initialCapacity),负载因子(loadFactor) * HashTable初始化核心代码 * **/ 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; //初始化Entry数组 table = new Entry<?,?>[initialCapacity]; //计算扩容阈值 threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1); } /*** * 初始化HashTable 指定初始化容量(initialCapacity) * 默认负载因子大小为0.75 * ***/ public Hashtable(int initialCapacity) { this(initialCapacity, 0.75f); } /*** * 默认初始化 Entry数组 大小为 11 * loadFactor =0.75 ***/ public Hashtable() { this(11, 0.75f); } /*** * * ***/ public Hashtable(Map<? extends K, ? extends V> t) { this(Math.max(2*t.size(), 11), 0.75f); putAll(t); }
put方法
/*** **添加元素 key value ** 注意:put方法是加锁的 这就是 hashTable 为啥是线程安全的原因 阻塞的
** key、value 均不能为空 ****/ public synchronized V put(K key, V value) { // Make sure the value is not null value不能为空 if (value == null) { throw new NullPointerException(); } // Makes sure the key is not already in the hashtable. Entry<?,?> tab[] = table; //hashCode取的是key的HashCode key不能为空 int hash = key.hashCode(); //根据hashCode & long最大值散列 再对 数组长度取模获取到所要插入的数组下标 int index = (hash & 0x7FFFFFFF) % tab.length; @SuppressWarnings("unchecked") //查看数组该下标下是否存在元素 如存在便利 value(新)覆盖key相同的值 Entry<K,V> entry = (Entry<K,V>)tab[index]; for(; entry != null ; entry = entry.next) { if ((entry.hash == hash) && entry.key.equals(key)) { V old = entry.value; entry.value = value; return old; } } //保存 addEntry(hash, key, value, index); return null; } /*** *保存 ***/ private void addEntry(int hash, K key, V value, int index) { //记录put次数 +1 modCount++; Entry<?,?> tab[] = table; //已存储的Entry个数 >= 阈值 扩容 if (count >= threshold) { // Rehash the table if the threshold is exceeded rehash(); tab = table; hash = key.hashCode(); index = (hash & 0x7FFFFFFF) % tab.length; } // Creates the new entry. @SuppressWarnings("unchecked") //查询数组原有的Entry链表 Entry<K,V> e = (Entry<K,V>) tab[index]; //将Entry<K,V> 保存 并将Entry<K,V>.next 指向原来的Entry链表 tab[index] = new Entry<>(hash, key, value, e); //数组长度+1 count++; }
扩容机制
/**** **扩容 ** 触发 count(已存储的Entry个数) >= threshold(阈值) ****/ protected void rehash() { //扩容前 数组(table)长度 int oldCapacity = table.length; //扩容前的 table数组 Entry<?,?>[] oldMap = table; // overflow-conscious code //扩容后的 数组长度为 oldCapacity*2+1 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; } // 新建一个数组长度为 oldCapacity*2+1 数组 Entry<?,?>[] newMap = new Entry<?,?>[newCapacity]; modCount++; //计算阈值 threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1); table = newMap; //将旧数组中的Entry 重新计算转移到新数组中 for (int i = oldCapacity ; i-- > 0 ;) { for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) { Entry<K,V> e = old; old = old.next; int index = (e.hash & 0x7FFFFFFF) % newCapacity; e.next = (Entry<K,V>)newMap[index]; newMap[index] = e; } } }
特点:
- key、value 都不允许为空 key不允许为空 因为hashcode取的是key 该对象的HashCode()
- HashTable 默认构造方法 容量初始化为 11 负载因子为 0.75 若使用带负载因子的构造方法创建HashTable 请不要讲负载因子设置过大 比如 初始化容量设为 1 负载因子设为1000 这样会导致查询很慢
- HashTable是数组+单项链表Entry<k,v>的结构来存储数据
- 数组table最大长度为 Integer.Max - 8;
- 扩容条件 count(HashTbale 存储链表的个数) >= threshold(阈值 计算方法 table.length * loadFactor)
- 线程安全