一、HashTable
1. 继承关系
public class Hashtable<K,V>extends Dictionary<K,V>implements Map<K,V>,
Cloneable, java.io.Serializable
继承Dictionary类(JDK较早提供的一个实现类),实现Map、Cloneable、 java.io.Serializable 接口,可以克隆、序列化
2. 基本属性
private transient Entry<K,V>[] table;
private transient int count; //结点个数
private int threshold; //阈值
private float loadFactor; //加载因子
int hash;
final K key;
V value;
ntry<K,V> next;
3.特点
-
底层数据结构:数组+链表
-
键不能重复、值可以重复
-
键、值都不能为null
-
键无序
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); //计算阈值
initHashSeedAsNeeded(initialCapacity);
}
public Hashtable(int initialCapacity) {
this(initialCapacity, 0.75f);
}
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);
}
5. 增长方式
从源码中可以看出,HashTable中数组扩容后的新容量为2*table.length+1
;
6. 增删方式
put()方法添加元素(线程安全)
-
判断value,若为null则抛出异常,HashTable中key,value不能为null,若key为null也会抛出空指针异常
-
通过key进行哈希获取到key该存储的索引位置
-
遍历该索引位置下的链表,判断key是否存在(存在条件:hash值相等,且key.equals)
-
在存在该key的情况下,替换value且直接返回 旧的value
-
key不存在则进行新结点插入逻辑
- 扩容条件:count > threshold
- 新容量为 2 * table.length + 1
- 将原哈希表中的数据进行重新哈希到新的hash表
- 更新插入的key的新位置
- 找到结点的新位置,创建Entry实体通过头插法将元素插入
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) { // 键、值都不能为null
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable.
Entry tab[] = table;
//通过key的哈希找到存储位置
int hash = hash(key);
int index = (hash & 0x7FFFFFFF) % tab.length;
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;
}
}
modCount++;
if (count >= threshold) {
// Rehash the table if the threshold is exceeded
rehash();
hash = hash(key);
index = (hash & 0x7FFFFFFF) % tab.length;
}
// Creates the new entry.
Entry<K,V> e = tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
return null;
}
protected void rehash() {
int oldCapacity = table.length;
Entry<K,V>[] oldMap = table;
// overflow-conscious code
int newCapacity = (oldCapacity << 1) + 1;
if (newCapacity - MAX_ARRAY_SIZE > 0) { //newCapacity >
if (oldCapacity == MAX_ARRAY_SIZE)
// Keep running with MAX_ARRAY_SIZE buckets
return;
newCapacity = MAX_ARRAY_SIZE;
}
Entry<K,V>[] newMap = new Entry[newCapacity];
modCount++;
threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
boolean rehash = initHashSeedAsNeeded(newCapacity);
table = newMap;
for (int i = oldCapacity ; i-- > 0 ;) {
for (Entry<K,V> old = oldMap[i] ; old != null ; ) {
Entry<K,V> e = old;
old = old.next;
if (rehash) {
e.hash = hash(e.key);
}
int index = (e.hash & 0x7FFFFFFF) % newCapacity;
e.next = newMap[index];
newMap[index] = e;
}
}
}
remove()方法删除元素 (线程安全)
- 通过key来哈希获取到存储索引位置;
- 对该索引位置的链表进行遍历,找到key所对应的entry实体并进行删除;
public synchronized V remove(Object key) {
Entry tab[] = table;
int hash = hash(key);
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<K,V> e = tab[index], 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;
}
get()方法获取元素
- get()方法本身也具有线程安全性
- 通过key来哈希获取到存储索引位置 通过key为null进行get操作也会有异常抛出
- 遍历当前索引位置结点,判断是否相等(hash key),找到直接返回value,未找到返回null
public synchronized V get(Object key) {
Entry tab[] = table;
int hash = hash(key);
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
return e.value;
}
}
return null;
}
二、HashMap和HashTable的异同点
-
相同点:
- 底层数据结构:数组+链表
- key都不能重复
- 插入无序
- 哈希过程:通过key进行hash
-
不同点:
继承父类 | 安全性 | 扩容方式 | key、value是否可以为null | 数组默认容量 | 单线程下效率 | 哈希算法不同 | |
---|---|---|---|---|---|---|---|
HashMap | AbstractMap | 非线程安全 | 2*table.length | 可以 | 16,且必须指定为2的指数级关系 | 高 | |
HashTable | Dictionary | 线程安全 | 2*table.length+1 | 不可以 | 11 | 低 |
- 单线程下HashTable效率低的原因
简单来说就是用户态和内核态的切换是有耗时的
三、HashTable能保证线程安全的原理
HashTable对相应方法添加Synchronized关键字,该关键字是一种互斥锁;
互斥锁的目的是保证同一时刻只能有一个线程对资源的访问
在HashMap对get()、set()等一般方法添加Synchronized关键字,修饰的是类对象
,该对象调用put操作即为该HashTable对象添加了一个互斥锁,那么在同一时刻只能一个线程访问该HashTable,从而保证添加元素不会出现异常
四、HashMap不具有线程安全性,如何让其在多线程下具有线程安全性?
使用集合工具类 Collections 使HashMap具有线程安全性
Collections.synchronizedMap(hashMap);