HashTable类源码解读
HashTable这个类实现了哈希表从key映射到value的数据结构形式。任何非null的对象都可以作为key或者value。
要在hashtable中存储和检索对象,作为key的对象必须实现hashCode、equals方法。
一般来说,默认的加载因子(0.75)提供了一种对于空间、时间消耗比较好的权衡策略。太高的值(指加载因子loadFactor)虽然减少了空间开销但是增加了检索时间,这反应在对hashtable的很多操作中,比如get、put方法。
初始容量的控制也是在空间消耗和rehash操作耗时(该操作耗时较大)二者之间的权衡。 如果初始容量大于哈希表的当前最大的条目数除以加载因子,则不会发生rehash。但是,将初始容量设置过高会浪费空间。
如果有大量的数据需要放进hashtable,则选择设置较大的初始容量比它自动rehash更优。
在Java平台v1.2中,这个类被重新安装以实现Map接口,使它成为Java集合框架的成员。与新的集合实现不同,Hashtable是同步的。如果不需要线程安全的实现,建议使用HashMap代替Hashtable。如果想要一个线程安全的高并发实现,那么建议使用java.util.concurrent.ConcurrentHashMap取代了Hashtable。
HashTable 的属性
private transient Entry<?,?>[] table; // table 是存储数据的数组。
private transient int count; // 在哈希表中已经存储的数据个数
private int threshold; // 哈希表将会在存储数据个数达到这个值时进行rehash,该值 = (int)容量 * 加载因子。
private float loadFactor; // 加载因子,默认为 0.75。
private transient int modCount = 0; // 哈希表发生结构性改变的次数,这个字段用于在创建迭代器时发生快速失败(fail-fast)发生ConcurrentModificationException。
HashTable 构造器
HashTable 提供了常见的4个构造器,常见的有三个:
指定初始容量、加载因子的构造器
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); }
指定初始容量的构造器
指定初始容量的构造器,其默认加载因子为0.75
public Hashtable(int initialCapacity) { this(initialCapacity, 0.75f); }
空构造器
空构造器,内部使用了默认初始容量为11,加载因子为0.75.
public Hashtable() { this(11, 0.75f); }
HashTable 的哈希函数
HashTable 的哈希函数 是将key的hashCode跟最大整数进行按位与,最后对哈希表的存储数组的长度进行取模,以便得到 该 key 在 存储数组中的索引值index。
int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length;
哈希桶内部静态类 Entry<K,V>
HashTable 内部数据使用一个静态内部类对象存储,Entry<K,V>,该实体类包含四个属性:
- final int hash; // 哈希值
- final K key; // 键
- V value; // 值
- Entry<K,V> next; //指向其下一个元素
private static class Entry<K,V> implements Map.Entry<K,V> { final int hash; final K key; V value; Entry<K,V> next; //...... }
put(K key, V value) 存储数据到哈希表
HashTable的put(key, value) 方法,可以将非空数据key-value 键值对放到哈希表中。
put 方法获取 key 的 hashCode值,再将其与Integer的最大值进行按位与,这样是保留整型的最大值的高位 + 哈希值的低位 组合作为 进行 求模的操作数。 使用该值(Integer的最大值进行按位与) 是为了减少哈希碰撞的概率。
index 作为开始在存储数组中的索引值进行匹配,如果index处没有存储数据(即没有进入到for循环中),则直接在此位置上添加该key-value键值对(调用 addEntry(hash, key, value, index); 方法)。
如果在index中已经存储过数据了,又分两种情况:
- (1) 已经存在的数据的 hash 值 和当前要存储的数据的 hash 值相等, 且 key 也相等。 就表示是数据对象相等,则将旧的数据对象的value设置为此次需要存储的数据对象的value, 并返回旧的数据对象的value。
- (2)如果不相等且找到后面为null的位置,在此位置插入数据----进入 addEntry(hash, key, value, index); 方法。
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 = key.hashCode(); //hash值 int index = (hash & 0x7FFFFFFF) % tab.length; // 高位按位与操作后进行求模减少哈希碰撞的概率 @SuppressWarnings("unchecked") 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; } } //找到index位置数据为null的位置也结束for循环 //在此空挡处添加数据项 addEntry(hash, key, value, index); return null; }
addEntry(int hash, K key, V value, int index)
该方法用于存储数据key-value对象。
首先会将 modCount 自增, 表示该哈希表会发生结构性改变。
如果哈希表数据个数 count 还没有达到 门槛值 threshold,则直接添加该key-value到index此位置,同时count自增。
如果 count >= threshold 了,就 执行rehash操作(详见rehash方法描述),哈希表扩大,
private void addEntry(int hash, K key, V value, int index) { modCount++;//结构性改变 Entry<?,?> tab[] = table; 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<K,V> e = (Entry<K,V>) tab[index]; tab[index] = new Entry<>(hash, key, value, e); count++; }
rehash()
rehash 操作是哈希表的重要且耗时的操作。
rehash 操作会扩大、重新组织哈希表,新的容量 = 旧容量 * 2 + 1。
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
如果新容量大于存储数组的最大容量:如果旧容量已经等于存储数组的最大容量,则不做任何动作直接返回; 否则,新容量 置为 存储数组的最大容量(已满)。
步骤:
创建一个新的存储数组,容量为新容量newCapacity。
modCount++,结构性变更。
threshold 下一次的rehash门槛更新。
双层for循环,将旧的存储数组元素放到新的存储数组中去。
protected void rehash() { int oldCapacity = table.length; Entry<?,?>[] oldMap = table; // overflow-conscious code int newCapacity = (oldCapacity << 1) + 1; // 新的容量 = 旧容量 * 2 + 1 if (newCapacity - MAX_ARRAY_SIZE > 0) { //如果新容量大于存储数组的最大容量 if (oldCapacity == MAX_ARRAY_SIZE) // Keep running with MAX_ARRAY_SIZE buckets return; newCapacity = MAX_ARRAY_SIZE; } Entry<?,?>[] newMap = new Entry<?,?>[newCapacity]; modCount++; threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1); table = newMap; 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; // 下一次内层for循环则处理当前元素的下一个元素 //旧的元素重新根据新容量计算hash int index = (e.hash & 0x7FFFFFFF) % newCapacity; e.next = (Entry<K,V>)newMap[index]; //将旧数据放到新表中的新index位置上 newMap[index] = e;//放置旧元素 } } //for循环完成旧表数据堆放到新表中去, 就是一次rehash }
get(Object key)
get(Object key) 方法根据指定的key获取其value。该方法通过key的hash值,再进行高位按位与,获取到索引index,然后遍历存储数组,直到找到 hash、key均相等的元素,返回其value。
public synchronized V get(Object key) { Entry<?,?> tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { return (V)e.value; } } return null; }
contains(Object value)
contains(Object value) 判断哈希表的存储数组中是否包含指定的calue。会遍历数组,去匹配value,找到一个就返回true。
public synchronized boolean contains(Object value) { if (value == null) { throw new NullPointerException(); } Entry<?,?> tab[] = table; for (int i = tab.length ; i-- > 0 ;) { for (Entry<?,?> e = tab[i] ; e != null ; e = e.next) { if (e.value.equals(value)) { return true; } } } return false; }
containsKey(Object key)
containsKey(Object key) 方法用于检查哈希表中是否包含指定的key。
public synchronized boolean containsKey(Object key) { Entry<?,?> tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { return true; } } return false; }
remove(Object key)
remove 操作移除指定的key。会改变 modCount 、count 的值。
public synchronized V remove(Object key) { Entry<?,?> tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; @SuppressWarnings("unchecked") Entry<K,V> e = (Entry<K,V>)tab[index]; for(Entry<K,V> prev = null ; e != null ; prev = e, e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { modCount++; if (prev != null) { prev.next = e.next; } else { tab[index] = e.next; } count--; V oldValue = e.value; e.value = null; return oldValue; } } return null; }
clear()
clear() 方法会改变 modCount、count 的值。将存储数组中元素置为 null。
public synchronized void clear() { Entry<?,?> tab[] = table; modCount++; for (int index = tab.length; --index >= 0; ) tab[index] = null; count = 0; }
hashCode()
hashTable 的hashCode(0 方法也是有点意思的。loadFactor = -loadFactor;
这行代码用于防止递归调用hashCode导致堆栈溢出。
public synchronized int hashCode() { /* * This code detects the recursion caused by computing the hash code * of a self-referential hash table and prevents the stack overflow * that would otherwise result. This allows certain 1.1-era * applets with self-referential hash tables to work. This code * abuses the loadFactor field to do double-duty as a hashCode * in progress flag, so as not to worsen the space performance. * A negative load factor indicates that hash code computation is * in progress. */ int h = 0; if (count == 0 || loadFactor < 0) return h; // Returns zero loadFactor = -loadFactor; // Mark hashCode computation in progress Entry<?,?>[] tab = table; for (Entry<?,?> entry : tab) { while (entry != null) { h += entry.hashCode(); entry = entry.next; } } loadFactor = -loadFactor; // Mark hashCode computation complete return h; }
上一篇: php使用for语句输出三角形的方法
下一篇: 脚臭盐是什么 脚臭盐对身体的危害有哪些