HashTable源码学习
一.介绍
1.HashMap和HashTable的区别
1.相同点
- 二者都实现了Map接口。
- 底层都是哈西表
2.不同点
-
Hashtable继承自Dictionary类,而HashMap继承自AbstractMap类。
-
Hashtable 第一次创建对象的时候就会给底层数组开辟空间 Entry[] 11
HashMap 创建对象时 没有给底层数组进行空间开辟
-
HashMap把Hashtable的contains方法去掉了。改成containsValue和containsKey
Hashtable则保留了contains,containsValue和containsKey三个方法,其中contains和containsValue功能相同。
-
Hashtable中,key和value都不允许出现null值
HashMap中,null可以作为键,这样的键只有一个
-
哈希值的使用不同,HashTable直接使用对象的hashCode
HashMap重新计算hash值。
-
HashTable在不指定容量的情况下的默认容量为11
HashMap为16
-
Hashtable不要求底层数组的容量一定要为2的整数次幂
HashMap则要求一定为2的整数次幂。
-
Hashtable扩容时,将容量变为原来的2倍加1
HashMap扩容时,将容量变为原来的2倍。
-
Hashtable 中的方法是Synchronize的。
HashMap线程不安全
-
计算index的方法不同:
- HashTable: index = (hash & 0x7FFFFFFF) % tab.length
- HashMap:index = hash & (tab.length – 1)
2.HashTable
像HashMap一样,Hashtable在哈希表中存储键/值对。当使用一个哈希表,要指定用作键的对象,以及要链接到该键的值。
然后,该键经过哈希处理,所得到的散列码被用作存储在该表中值的索引
二.源码部分
1.基本属性
继承Dictionary 类
是一个抽象类,用来存储键/值对,作用和Map类相似。
给出键和值,你就可以将值存储在Dictionary对象中。一旦该值被存储,就可以通过它的键来获取它。所以和Map一样, Dictionary 也可以作为一个键/值对列表。
Cloneable是标记型的接口,它们内部都没有方法和属性,实现 Cloneable来表示该对象能被克隆
java.io.Serializable接口:
可以启用其序列化功能。未实现次接口的类无法使其任何状态序列化或反序列化。可序列化类的所有子类型本身都是可序列化的。
public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable {
/**
* The hash table data.哈希表数据
*/
private transient Entry<?,?>[] table;
/**
* The total number of entries in the hash table.哈希表中enyty的总数
*/
private transient int count;
/**
* 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;
/** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID = 1421746759512286392L;
/**
* The maximum size of array to allocate.
* 要分配的数组的最大大小。
* Some VMs reserve some header words in an array.
* Attempts to allocate larger arrays may result in
* OutOfMemoryError: Requested array size exceeds VM limit
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
2.构造函数
/**
* 无参构造,默认容量为11,加载因子为0.75
* Constructs a new, empty hashtable with a default initial capacity (11)
* and load factor (0.75).
*/
public Hashtable() {
this(11, 0.75f);
}
/**
* Constructs a new, empty hashtable with the specified initial capacity
* and default load factor (0.75).
* 创建指定大小的哈希表
* 初始容量用户自定义,加载因子为默认的0.75
* @param initialCapacity the initial capacity of the hashtable.
* @exception IllegalArgumentException if the initial capacity is less
* than zero.
*/
public Hashtable(int initialCapacity) {
this(initialCapacity, 0.75f);
}
/**
* Constructs a new, empty hashtable with the specified initial
* capacity and the specified load factor.
* 创建了一个指定大小的哈希表,并且通过fillRatio指定填充比例
* @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);
//当初始化容量为0的时候,将初始容量设置为1
if (initialCapacity==0)
initialCapacity = 1;
//加载因子赋值
this.loadFactor = loadFactor;
//创建一个新的大小为初始容量的enytry数组
table = new Entry<?,?>[initialCapacity];
//阈值为initialCapacity * loadFactor或最大值
threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
}
/**
* Constructs a new hashtable with the same mappings as the given
* Map. The hashtable is created with an initial capacity sufficient to
* hold the mappings in the given Map and a default load factor (0.75).
* 第四个构造方法创建了一个以M中元素为初始化元素的哈希表。
* 哈希表的容量被设置为M的两倍,或是11
* @param t the map whose mappings are to be placed in this map.
* @throws NullPointerException if the specified map is null.
* @since 1.2
*/
public Hashtable(Map<? extends K, ? extends V> t) {
this(Math.max(2*t.size(), 11), 0.75f);
putAll(t);
}
3.contains()
/**
* Tests if some key maps into the specified value in this hashtable.
* This operation is more expensive than the {@link #containsKey
* containsKey} method.
* 测试此映射表中是否存在与指定值关联的键。
* <p>Note that this method is identical in functionality to
* {@link #containsValue containsValue}, (which is part of the
* {@link Map} interface in the collections framework).
*
* @param value a value to search for
* @return <code>true</code> if and only if some key maps to the
* <code>value</code> argument in this hashtable as
* determined by the <tt>equals</tt> method;
* <code>false</code> otherwise.
* @exception NullPointerException if the value is <code>null</code>
*/
public synchronized boolean contains(Object value) {
//如果传入的value值为空,则抛出异常
if (value == null) {
throw new NullPointerException();
}
//2层循环遍历数组及其链表
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;
}
4.put()
/**
* 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.
* 将指定 key 映射到此哈希表中的指定 value。
* @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 = key.hashCode();
//得到数组下标
int index = (hash & 0x7FFFFFFF) % tab.length;
//忽略警告
@SuppressWarnings("unchecked")
//将对应的数组元素保留起来
Entry<K,V> entry = (Entry<K,V>)tab[index];
//当该位置已有元素,则发生哈希冲突,遍历链表找通过equal方法找到key
for(; entry != null ; entry = entry.next) {
if ((entry.hash == hash) && entry.key.equals(key)) {
//覆盖原来的值
V old = entry.value;
entry.value = value;
//返回原来的值
return old;
}
}
5.addEntry()
private void addEntry(int hash, K key, V value, int index) {
modCount++;
Entry<?,?> tab[] = table;
//如果数组有效值等于阈值,将进行扩容操作,然后在计算新的index
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.创建新条目。count++
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>) tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
}
6.扩容机制
/**
* Increases the capacity of and internally reorganizes this
* hashtable,增加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.
* 当哈希表中的键数超过该哈希表的容量和负载因子时,将自动调用此方法。
*/
@SuppressWarnings("unchecked")
protected void rehash() {
//先将数组原来的容量保存起来
int oldCapacity = table.length;
Entry<?,?>[] oldMap = table;
// overflow-conscious code
//新的容量为原来的一半 + 1
int newCapacity = (oldCapacity << 1) + 1;
//如果新的容量超出了最大值,则将新容量变为最大值
if (newCapacity - MAX_ARRAY_SIZE > 0) {
//如果原来的容量就是最大值则将不扩容,return
if (oldCapacity == MAX_ARRAY_SIZE)
// Keep running with MAX_ARRAY_SIZE buckets
return;
newCapacity = MAX_ARRAY_SIZE;
}
//新建一个新容量大小的数组
Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];
modCount++;
//阈值也重新赋值,如若已经超出最大值,则为MAX_ARRAY_SIZE + 1
threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
//将table指向新数组
table = newMap;
//遍历原来的hashtable,将其拷贝过来
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;
//数组下标值不同于hashmap,都重新计算
int index = (e.hash & 0x7FFFFFFF) % newCapacity;
e.next = (Entry<K,V>)newMap[index];
newMap[index] = e;
}
}
}
三.总结
- Hash:是一种信息摘要算法,它还叫做哈希,或者散列。我们平时使用的MD5,SHA1都属于Hash算法,通过输入key进行Hash计算,就可以获取key的HashCode(),比如我们通过校验MD5来验证文件的完整性。
- Hashtable 实现并发安全的原理是通过
synchronized 关键字
- 当线程数量增加的时候,Hashtable 的性能会急剧下降,因为每一次修改都需要锁住整个对象,而其他线程在此期间是不能操作的。不仅如此,还会带来额外的上下文切换等开销,所以此时它的吞吐量甚至还不如单线程的情况。