HashTable
概述
本文主要参考了Java Collection Framework 源码剖析这位博主的专栏,写的很好,感兴趣的可以去看一下!
HashTable底层实现都是一个链表数组,具有寻址容易、插入和删除也容易的特性;
事实上,HashMap几乎可以等价于Hashtable,除了HashMap是非线程安全的并且可以接受null键和null值。
HashTable在JDK中的定义
Hashtable实现了Map接口,并继承Dictionary抽象类 :
Hashtable也包括五个成员变量,分别是table数组、Hashtable中Entry个数count、Hashtable的阈值threshold、Hashtable的负载因子loadFactor 和 Hashtable结构性修改次数modCount
public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable {
private transient Entry[] table; // 由Entry对象组成的链表数组
private transient int count; // Hashtable中Entry对象的个数
private int threshold; // Hashtable进行扩容的阈值
private float loadFactor; // 在其容量自动增加之前可以达到多满的一种尺度,默认为0.75
private transient int modCount = 0; // 记录Hashtable生命周期中结构性修改的次数
}
HashTable数据结构
HashTable本质上是一个链表数组
从上图中,我们可以形象地看出Hashtable底层实现还是数组,只是数组中存放的元素是Entry对象,而Entry对象是一种典型链状结构,定义如下:
static class Entry<K,V> implements Map.Entry<K,V> {
K key; // 键值对的键
V value; // 键值对的值
Entry<K,V> next; // 指向下一个节点的指针
int hash; // key 的哈希值(与HashMap中key的哈希值计算方式不同)
/**
* Creates new entry.
*/
protected Entry(int h, K k, V v, Entry<K,V> n) { // Entry 的构造函数
value = v;
next = n;
key = k;
hash = h;
}
......
}
HashTable的快速存取
1、put(key,value)
HashTable保存数据和HashMap类似,计算key的hash值并且确定K/V对应要插入的桶位;其次查找该桶位内是否存在具有相同的key的K/V对,若存在直接覆盖对应的value值,否则将该节点保存在桶的链表的头位置(最先保存的元素放在尾部),如果容器达到阈值就会扩容;
public synchronized V put(K key, V value) { // 加锁同步,保证Hashtable的线程安全性
// Make sure the value is not null
if (value == null) { // 不同于HashMap,Hashtable不允许空的value
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable.
Entry tab[] = table;
int hash = key.hashCode(); // key 的哈希值,同时也暗示Hashtable不同于HashMap,其不允许空的key
int index = (hash & 0x7FFFFFFF) % tab.length; // 取余计算节点存放桶位,0x7FFFFFFF 是最大的int型数的二进制表示
// 先查找Hashtable上述桶位中是否包含具有相同Key的K/V对
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;
}
}
// 向Hashtable中插入目标K/V对
modCount++; // 发生结构性改变,modCount加1
if (count >= threshold) { //在插入目标K/V对前,先检查是否需要扩容(不同于HashMap的插入后检查是否需要扩容)
// Rehash the table if the threshold is exceeded
rehash();
tab = table;
index = (hash & 0x7FFFFFFF) % tab.length; // 扩容后,重新计算K/V对插入的桶位
}
// Creates the new entry.
Entry<K, V> e = tab[index];
tab[index] = new Entry<K, V>(hash, key, value, e); // 将K/V对链入对应桶中链表,并成为头结点
count++; // Hashtable中Entry数目加1
return null;
}
- HashTable不允许key为null,也不允许value为null;
- HashTable中用于定位桶位的Key的Hash计算过程比HashMap要简单,直接;
- HashTable则是先检查是否需要扩容后插入,与HashMap相反;
- HashTable的put操作是线程安全的;
2、get(Object key)
Hashtable只需通过key的hash值定位到table数组的某个特定的桶,然后查找并返回该key对应的value即可;
public synchronized V get(Object key) { // 不同于HashMap,Hashtable的读取操作是同步的
Entry tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length; // 定位K/V对的桶位
for (Entry<K, V> e = tab[index]; e != null; e = e.next) { // 在特定桶中依次查找指定Key的K/V对
if ((e.hash == hash) && e.key.equals(key)) {
return e.value;
}
}
return null; // 查找失败
}
- HashTable的读取操作是同步的;
- HashTable中若读取到的Value值是NULL,只有一种可能就是HashTable中不含有该key的Entry;HashTable中不允许空的key,也不允许空的value;
HashMap、HashTable与ConcurrentMap的联系和区别
1、Hashtable与HashMap的联系和区别
- HashMap和Hashtable的实现模板不同:虽然二者都实现了Map接口,但HashTable继承于Dictionary类,而HashMap是继承于AbstractMap;Dictionary是是任何可将键映射到相应值的类的抽象父类,而AbstractMap是基于Map接口的骨干实现,它以最大限度地减少实现此接口所需的工作;
- HashMap和Hashtable对键值的限制不同:HashMap可以允许存在一个为null的key和任意个为null的value,但是HashTable中的key和value都不允许为null;
- HashMap和Hashtable的线程安全性不同:Hashtable的方法是同步的,实现线程安全的Map;而HashMap的方法不是同步的,是Map的非线程安全实现;
- HashMap和Hashtable的地位不同:在并发环境下,Hashtable虽然是线程安全的,但是我们一般不推荐使用它,因为有比它更高效、更好的选择ConcurrentHashMap;而单线程环境下,HashMap拥有比Hashtable更高的效率(Hashtable的操作都是同步的,导致效率低下)
2、Hashtable与ConcurrentHashMap的联系和区别
Hashtable和ConcurrentHashMap都可以用于并发环境,但是HashTable的性能不如ConcurrentHashMap;ConcurrentHashMap引入了分段锁机制,HashTable则会锁定整个map;
HashTable(同一把锁):采用synchronized来保证线程安全,效率低下。当多线程访问同步方法,会进入阻塞或者轮询状态,例如一个线程使用put操作,另一个线程不能使用put也不能使用get,导致效率低下;
ConcurrentHashMap(分段锁):每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。Segment 是一种可重入锁ReentrantLock,扮演锁的角色。HashEntry 用于存储键值对数据。
一个ConcurrentHashMap 里包含一个Segment 数组。Segment 的结构和Hashmap类似,是一种数组和链表结构,一个Segment 包含一个HashEntry 数组,每个HashEntry是一个链表结构的元素,每个Segment 守护着一个HashEntry 数组里的元素,当对HashEntry数组的数据进行修改时,必须首先获得对应的Segment。