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);
}
这个构造方法是指定初始容量和加载因子;当初始容量小于0时,抛出异常;当加载因子小于或等于0时,抛出异常;当指定容量为0时,则设置容量为1;
public Hashtable(int initialCapacity) {
this(initialCapacity, 0.75f);
}
这个构造方法时指定初始容量和默认加载因子为0.75
public Hashtable() {
this(11, 0.75f);
}
这个构造方法时默认初始容量为11,加载因子为0.75
public Hashtable(Map<? extends K, ? extends V> t) {
this(Math.max(2*t.size(), 11), 0.75f);
putAll(t);
}
这个构造方法时当放入一个map为参数时,map的size的2倍和11比大小,谁大,取容量为谁
2 put方法
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;
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];
for(; entry != null ; entry = entry.next) {
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
return old;
}
}
addEntry(hash, key, value, index);
return null;
}
当存入的值value为空时,就会报空值针异常;
然后获取取hashtable中的 entry 数组,
计算key的hashcode;
通过hash值与 与运算 然后与数组长度求余,获取下标长度,对该元素数组下标定位
再获取该数组下标中的数组所有的元素(也就是一个链表)
遍历该链表;判断链表中是否有hash和key相等的对象,如果有,旧值被新值覆盖,返回旧值;也就是把原来的值给覆盖掉,然后把被覆盖的值返回
private void addEntry(int hash, K key, V value, int index) {
modCount++;
Entry<?,?> tab[] = table;
if (count >= threshold) {
// Rehash the table if the threshold is exceeded
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);
count++;
}
@SuppressWarnings("unchecked")
protected void rehash() {
int oldCapacity = table.length;
Entry<?,?>[] oldMap = table;
// overflow-conscious code
int newCapacity = (oldCapacity << 1) + 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);
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;
}
}
}
扩容总结:
条件:count >= threshold 当当前元素量大于或等于阈值
发生:int newCapacity = (oldCapacity << 1) + 1; 扩大2n+1
下面是一些关于面试的题目
1 Hashtable扩容的数组长度为什么时旧数组长度乘以2加1?
2 Hashtable中数组的长度尽量为素数或者奇数,同时Hashtable采用取模的方式来计算数组下标,这样减少Hash碰撞,计算出来的数组下标更加均匀。但是这样效率会比HashMap利用位运算计算数组下标低。
3 Hashtable为什么采用头插法的方式迁移数组?
采用头插法的方式效率更高。如果采用尾插法需要遍历数组将元素放置到链表的末尾,而采用头插法将结点放置到链表的头部,减少了遍历数组的时间,效率更高。
4JDK1.8前HashMap也是采用头插法迁移数据,多线程情况下会造成死循环,JDK1.8对HashMap做出了优化,为什么JDK1.8Hashtable还是采用头插法的方式迁移数据?
5 Hashtable是线程安全的,所以Hashtable不需要考虑并发冲突问题,可以采用效率更高的头插法。
6 为什么Hashtable渐渐被弃用?
Hashtable使用synchronized来实现线程安全,在多并发的情况下效率低下。