Java中的Hashtable源码详细解析
作者:西瓜游侠
1、简介
和HashMap一样,Hashtable 也是一个散列表,它存储的内容是键值对(key-value)映射。
Hashtable 继承于Dictionary,实现了Map、Cloneable、java.io.Serializable接口。 Hashtable 的函数都是同步的,这意味着它是线程安全的。它的key、value都不可以为null。此外,Hashtable中的映射不是有序的。
Hashtable 的实例有两个参数影响其性能:初始容量 和 加载因子。
容量:是哈希表中桶 的数量,初始容量 就是哈希表创建时的容量。注意,哈希表的状态为 open:在发生“哈希冲突”的情况下,单个桶会存储多个条目,这些条目必须按顺序搜索。
加载因子:是对哈希表在其容量自动增加之前可以达到多满的一个尺度(阈值)。初始容量和加载因子这两个参数只是对该实现的提示。关于何时以及是否调用 rehash 方法的具体细节则依赖于该实现。
通常,默认加载因子是 0.75, 这是在时间和空间成本上寻求一种折衷。加载因子过高虽然减少了空间开销,但同时也增加了查找某个条目的时间(在大多数 Hashtable 操作中,包括 get 和 put 操作,都反映了这一点)。
2、关键点
- Hashtable继承于Dictionary类,实现了Map接口。Map是”key-value键值对”接口,Dictionary是声明了操作”键值对”函数接口的抽象类。
- Hashtable是通过”拉链法“实现的哈希表。它包括几个重要的成员变量:table, count, threshold, loadFactor, modCount。
- table是一个Entry[]数组类型,而Entry实际上就是一个单向链表。哈希表的”key-value键值对”都是存储在Entry数组中的。
- count是Hashtable的大小,它是Hashtable保存的键值对的数量。
- threshold是Hashtable的阈值,用于判断是否需要调整Hashtable的容量。threshold的值=”容量*加载因子”。
- loadFactor就是加载因子。
- modCount是用来实现fail-fast机制的。
3、Hashtable数据存储数组
private transient Entry<?,?>[] table;
Hashtable中的key-value都是存储在table数组中的。
4、数据节点Entry<K,V>的数据结构
private static class Entry<K,V> implements Map.Entry<K,V> { final int hash; //hash值 final K key; //键 V value; //值 Entry<K,V> next; //链表中下一个Entry的引用 //Entry的构造函数 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; } //设置value。若value是null,则抛出异常。 public V setValue(V value) { if (value == null) throw new NullPointerException(); V oldValue = this.value; this.value = value; return oldValue; } // 覆盖equals()方法,判断两个Entry是否相等。 // 若两个Entry的key和value都相等,则认为它们相等。 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())); } //Entry对象的hash值 public int hashCode() { return hash ^ Objects.hashCode(value); } public String toString() { return key.toString()+"="+value.toString(); } }
5、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; 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); } // 包含“子Map”的构造函数 public Hashtable(Map<? extends K, ? extends V> t) { this(Math.max(2*t.size(), 11), 0.75f); // 将“子Map”的全部元素都添加到Hashtable中 putAll(t); }
6、Hashtable的主要对外接口
6.1 clear()
clear() 的作用是清空Hashtable。它是将Hashtable的table数组的值全部设为null。
public synchronized void clear() { Entry<?,?> tab[] = table; modCount++; for (int index = tab.length; --index >= 0; ) tab[index] = null; //将数组中的引用全部置为null count = 0; }
6.2 contains() 和 containsValue()
contains() 和 containsValue() 的作用都是判断Hashtable是否包含“值(value)”。
public synchronized boolean contains(Object value) { // Hashtable中“键值对”的value不能是null, // 若是null的话,抛出异常! if (value == null) { throw new NullPointerException(); } Entry<?,?> tab[] = table; // 从后向前遍历table数组中的元素(Entry) // 对于每个Entry(单向链表),逐个遍历,判断节点的值是否等于value 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; }
public boolean containsValue(Object value) { return contains(value); }
6.3 containsKey()
containsKey() 的作用是判断Hashtable是否包含key。
public synchronized boolean containsKey(Object key) { Entry<?,?> tab[] = table; int hash = key.hashCode(); // 计算索引值, // % tab.length 的目的是防止数据越界 int index = (hash & 0x7FFFFFFF) % tab.length; // 找到“key对应的Entry(链表)”,然后在链表中找出“哈希值”和“键值”与key都相等的元素 for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { return true; } } return false; }
6.4 elements()
elements() 的作用是返回“所有value”的枚举对象。
public synchronized Enumeration<V> elements() { return this.<V>getEnumeration(VALUES); }
// 获取Hashtable的枚举类对象 private <T> Enumeration<T> getEnumeration(int type) { if (count == 0) { return Collections.emptyEnumeration(); } else { return new Enumerator<>(type, false); } }
7、put()和get()操作
7.1 put()操作
put() 的作用是对外提供接口,让Hashtable对象可以通过put()将“key-value”添加到Hashtable中。
public synchronized V put(K key, V value) { // Hashtable中不能插入value为null的元素!!! if (value == null) { throw new NullPointerException(); } // 若“Hashtable中已存在键为key的键值对”, // 则用“新的value”替换“旧的value” Entry<?,?> tab[] = table; int hash = key.hashCode(); //计算key的索引 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; } } //添加Entry addEntry(hash, key, value, index); return null; }
private void addEntry(int hash, K key, V value, int index) { //将“修改统计数”+1 modCount++; Entry<?,?> tab[] = table; //若“Hashtable实际容量” > “阈值”(阈值=总的容量 * 加载因子) //则调整Hashtable的大小 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. //将“Hashtable中index”位置的Entry(链表)保存到e中 @SuppressWarnings("unchecked") Entry<K,V> e = (Entry<K,V>) tab[index]; //采用 “头插法” //创建“新的Entry节点”,并将“新的Entry”插入“Hashtable的index位置”, //并设置e为“新的Entry”的下一个元素(即“新Entry”为链表表头)。 tab[index] = new Entry<>(hash, key, value, e); //将“Hashtable的实际容量”+1 count++; }
7.2 get()操作
get() 的作用就是获取key对应的value,没有的话返回null。
public synchronized V get(Object key) { Entry<?,?> tab[] = table; //key的hash值 int hash = key.hashCode(); //计算索引 int index = (hash & 0x7FFFFFFF) % tab.length; //找到“key对应的Entry(链表)”,然后在链表中找出“哈希值”和“键值”与key都相等的元素 for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { return (V)e.value; } } return null; }
8、rehash()
rehash()函数负责hashtable的扩容。
protected void rehash() { int oldCapacity = table.length; //扩容前的map Entry<?,?>[] oldMap = table; // overflow-conscious code ////新容量 = 旧容量 * 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; } //创建新的数组 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; } } }
9、remove()
remove() 的作用就是删除Hashtable中键为key的元素。
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]; // 找到“key对应的Entry(链表)” // 然后在链表中找出要删除的节点,并删除该节点。 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; //将value引用置为null,回收value对象 e.value = null; return oldValue; } } return null; }
到此这篇关于Java中的Hashtable源码详细解析的文章就介绍到这了,更多相关Hashtable源码内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!