散列(Hash)是一种用于以常数时间进行查找,删除,插入的技术
Hashtable源码分析:
根据JDK源码: 此类实现了一个哈希表,该表将键映射到相应的值,任何非null对象都可以用作键和值,(HashMap可以以null作为值和键,并且非同步,除了这两点 他们基本相同),正是因为这种键和值一一对应的关系才保证了哈希表的查找时间花费常数时间,即主要花费在将键通过hash()确定散列到表的位置。
影响Hashtable的性能有初始容量和加载因子。加载因子 是对哈希表在其容量自动增加之前可以达到多满的一个尺度(默认0.75)。初始容量和加载因子这两个参数只是对该实现的提示。关于何时以及是否调用 rehash 方法的具体细节则依赖于该实现。
初始容量主要控制空间消耗与执行 rehash
操作所需要的时间损耗之间的平衡。如果初始容量大于 Hashtable 所包含的最大条目数除以加载因子,则永远 不会发生rehash
操作。但是,将初始容量设置太高可能会浪费空间。
如果很多条目要存储在一个 Hashtable
中,那么与根据需要执行自动 rehashing 操作来增大表的容量的做法相比,使用足够大的初始容量创建哈希表或许可以更有效地插入条目。
分析源码:
上面的图片就是class Hashtable的具体抽象。每个哈希表里都有一个Entry数组,Entry是一个私有的内部静态类
private static class Entry<K,V> implements Map.Entry<K,V> {
int hash;
final K key;
V value;
Entry<K,V> next;
protected Entry(int hash, K key, V value, Entry<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
}
每一个Entry都有一个指向下一个Entry的指针,主要作为碰撞链,当加载因子大于1时就意味着必然存在碰撞,即不同的键匹配到了相同的位置。
接着看构造函数
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);
}
我们可以看到Hashtable的默认容量是11 加载因子是0.75.
在看几个重要的方法:
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 = 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();
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;
}
put()方法首先
对key的hash做了一个与操作,保证其是一个正整数,通过hash(key)得到哈希值,然后得到该映射在哈希表的位置,首先看看key是否存在,存在则更新并将其返回,不存在则插入链表前,因为这样不仅方便而且还因为常常发生这样的事实:新近插入的元素最有可能不久又被访问。之后进行一次判定,在挡哈希表中的总个数超过哈希表表当前容量*加载因子(就是theeshold)的时候调用rehash()方法,并进行扩建和重新确定下标。
protected void rehash() {
int oldCapacity = table.length;
Entry[] oldMap = table;
int newCapacity = oldCapacity * 2 + 1;
Entry[] newMap = new Entry[newCapacity];
modCount++;
threshold = (int)(newCapacity * loadFactor);
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;
int index = (e.hash & 0x7FFFFFFF) % newCapacity;
e.next = newMap[index];
newMap[index] = e;
}
}
}
容量*2了,并且进行内部的重组。
modCount和expectedModCount则和其他容器里的一样,用于抛出 ConcurrentModificationException,防止并发发生不确定行为。