目录
HashTable
初始容量是11,加载因子是0.75,是线程安全的HashMap,底层采用数组加链表(没有红黑树),每个方法都加了synchronized保证线程安全
HashTable构造函数
在构造时候就开辟了一个长度为11的数组空间,加载因子设置为0.75,预警节点个数设置为11*0.75
public Hashtable() {
this(11, 0.75f);
}
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);
}
HashTable Put分析
将key和数组长度相&,求得下标,如果数组下标里面没有元素,则直接将新节点存放,如果数组下标里面已经存放了元素,此时
从下标里的元素开始寻找以它为头结点的整个链表,比较key,有相等的就覆盖,没有相等的则将新添加的节点放入刚才计算的下标位置里面,后面链上以该下标位置之前存放的元素为头结点的链表。
下标确定
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;//下标确定是,key的hash值对数组长度求余
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];
for(; entry != null ; entry = entry.next) {//如果确定的下标里面已经存放了元素,则通过equals比较key,相等就覆盖,
//不相等继续与通过next得到的元素比较key,直到找到一个相等的key,覆盖里面的value退出,或者整个链中找不到相等的,就链接到最后
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
return old;
}
}
addEntry(hash, key, value, index);
return null;
}
private void addEntry(int hash, K key, V value, int index) {
modCount++;
Entry<?,?> tab[] = table;
if (count >= threshold) { //HashTabel数组中元素个数达到预警节点个数,就扩容
rehash();
tab = table;
hash = key.hashCode();
index = (hash & 0x7FFFFFFF) % tab.length;
}
// Creates the new entry.
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>) tab[index];
tab[index] = new Entry<>(hash, key, value, e);//每次将新添加的元素放在一个链表的头部,
//如果e为null,代表table[index]里面没有存放元素
//如果e不为null,代表table[index]里面存放了元素,并且要添加的key与e为开头的链表里面每一个节点的key都不相等
//此时把新添加的的节点作为头节点后面链接上以e为头结点的链表。
count++;
}
HashTable扩容分析
按原数组长度2倍+1扩容,预警节点个数:新长度*加载因子,扩容时旧数组元素的移动:
按下标循环旧数组,通过hash值和新数组长度求模求得新下标,将旧元素直接放到新下标里面,再循环以此旧元素为头结点的链表,拿到一个就放到刚才头节点放入的新下标里面,后面链接上以上次放到新下表标里面元素为头节点的链表。
每次拿到的节点都作为最新头结点,后面链接上上次头节点为头的链表
protected void rehash() {
int oldCapacity = table.length;
Entry<?,?>[] oldMap = table;
// overflow-conscious code
int newCapacity = (oldCapacity << 1) + 1;//按2倍加1扩容
if (newCapacity - MAX_ARRAY_SIZE > 0) {
if (oldCapacity == MAX_ARRAY_SIZE)
// Keep running with MAX_ARRAY_SIZE buckets
return;
newCapacity = MAX_ARRAY_SIZE;
}
Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];
modCount++;
threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);//预警节点=扩容后的长度*加载因子(0.75)
table = newMap;
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;
int index = (e.hash & 0x7FFFFFFF) % newCapacity;//计算新下标
e.next = (Entry<K,V>)newMap[index];
newMap[index] = e;//每次拿到的节点都作为最新头结点,后面链接上上次头节点为头的链表
}
}
}
HashTable Get分析
通过key计算下标,循环以计算出的下标位置为头结点的链表,依次比较key,相等则返回此节点
public synchronized V get(Object key) {
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
return (V)e.value;
}
}
return null;
}
HashTable Remove分析
public synchronized V remove(Object key) {
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>)tab[index];
for(Entry<K,V> 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;
}