Hashtable
源码探究
- 存在包:
package java.util。 - 继承关系:
public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable
继承了Dictionary(JDK较早而提供的实现类),实现了Map接口,可以克隆,可以序列化;
- 底层数据结构:
哈希表(数组+链表)。 - 基本属性:
private transient Entry<K,V>[] table;//哈希表 数组大小默认值11
private transient int count;//哈希表中的实体个数
private int threshold;//扩容的阈值
private float loadFactor;//加载因子
private transient int modCount = 0;
private static final long serialVersionUID = 1421746759512286392L;//序列号
static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;
- 构造函数:
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]; //数组初始化 与hashmap不同
threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
//扩容阈值初始化
initHashSeedAsNeeded(initialCapacity);
}
/**
* Constructs a new, empty hashtable with the specified initial capacity
* and default load factor (0.75).
*/
public Hashtable(int initialCapacity) {
this(initialCapacity, 0.75f);
}
/**
* Constructs a new, empty hashtable with a default initial capacity (11)
* and load factor (0.75). 数组默认大小11,加载因子默认0.75
*/
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);
}
-
增长方式:
以存储的数组二倍长度加一的方式增长,2 * table.length + 1。 -
CRUD方法研究(增删改查):
1.添加元素:put()
Hashtable 多线程下安全;HashMap 线程不安全
synchronized:调用synchronized修饰的方法,对对象进行加锁(互斥:同一时刻同一资源只有一个线程访问)
- 思路:
1.判断value是否为null;若为null抛出异常 ====》hashtable中value不能为null
2.通过key进行hash获取到key该存储的索引位置
3.该索引位置的链表进行遍历,获取key是否存在(key存在条件:hash是否相等,key使用equals())
4.在存在该key的情况下,将value值进行更新并且直接返回
5.key不存在则进行新节点插入
5.1 扩容考虑:(count >= threshold )entry结点个数大于扩容阈值;
5.2 新容量大小为:2 * table.length
5.3 将原哈希表中的数据全部进行重新hash到新的哈希表中
5.4 更新插入的key的新的位置
5.5 找到新节点的位置,创建entry实体通过头结点将元素进行插入
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();//特点:value 不能为null
}
// Makes sure the key is not already in the hashtable.
//key不能为null,key为null也会抛出空指针异常
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)) {//未使用双等key判断
V old = e.value;
e.value = value;
return old;
}
}
modCount++;
if (count >= threshold) {//count 实体个数 扩容条件
// Rehash the table if the threshold is exceeded 重hash
rehash();
tab = table;
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;
}
//hash方式与数组长度相关
protected void rehash() {
int oldCapacity = table.length;
Entry<K,V>[] oldMap = table;
// overflow-conscious code
int newCapacity = (oldCapacity << 1) + 1;//增长方式2倍加一
if (newCapacity - MAX_ARRAY_SIZE > 0) {
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;
}
}
}
private int hash(Object k) {
// hashSeed will be zero if alternative hashing is disabled.
return hashSeed ^ k.hashCode();
}
public synchronized int hashCode() {
int h = 0;
if (count == 0 || loadFactor < 0)
return h; // Returns zero
loadFactor = -loadFactor; // Mark hashCode computation in progress
Entry[] tab = table;
for (Entry<K,V> entry : tab)
while (entry != null) {
h += entry.hashCode();
entry = entry.next;
}
loadFactor = -loadFactor; // Mark hashCode computation complete
return h;
}
2.删除元素:remove():通过key进行查找
* 删除操作本身是线程安全的
* 思路:
1.通过key获取到当前存储的索引位置
2.对该索引位置下的链表进行遍历,找到key所对应的entry实体,并进行删除;
public synchronized V remove(Object key) {
Entry tab[] = table;
//通过key哈希到指定索引位置
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;
}
3.查找元素:get():通过key进行查找
-
思路:
1.通过key的哈希获取到存储的索引位置 —》通过key为null进行get操作也会抛出异常
2.遍历当前索引位置下的结点,判断是否相等(hash、equals),找到则直接返回value值,未找到返回null
-
特点: get()本身具有线程安全性
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;
}
Hashtable方法总结:
1.int size():存储键值对个数。
2.boolean isEmpty(): 测试此哈希表是否没有键映射到值。
3.boolean contains(Object value): 测试此映射表中是否存在与指定值关联的键。
4.boolean containsKey(Object key):测试指定对象是否为此哈希表中的键。
5.Object get(Object key): 返回指定键所映射到的值,如果此映射不包含此键的映射,则返回 null. 更确切地讲,如果此映射包含满足 (key.equals(k)) 的从键 k 到值 v 的映射,则此方法返回 v;否则,返回 null。
6.void rehash(): 增加此哈希表的容量并在内部对其进行重组,以便更有效地容纳和访问其元素。
7.Object put(Object key, Object value): 将指定 key 映射到此哈希表中的指定 value。
8.Object remove(Object key):从哈希表中移除该键及其相应的值。
9.void clear(): 将此哈希表清空,使其不包含任何键。
10.String toString():返回此 Hashtable 对象的字符串表示形式,其形式为 ASCII 字符 ", " (逗号加空格)分隔开的、括在括号中的一组条目。
Hashtable特点总结:
-
null值:键值对不能为null;
-
底层数据结构:数组+链表
-
key重复性:key不能重复;
-
无序性:不能保证插入有序