HashTable与HashMap的区别:
HashTable底层数据结构与HashMap相差不多,可以看一下
简谈JAVA基础--HashMap
HashTable由于每个方法都通过synchronized关键字来进行修饰,所以说是线程安全的。
而HashMap当多个线程同时操作时,没有同步机制所以会导致数据的不安全。
HashTable键值都不可以为Null。而HashMap的key可以为null。
如果在程序中对HashTable进行类似put( null , null )这样的操作时;编译器是可以通过的,但是会在实际运行时抛出异常。
HashMap的长度为2的幂次方,而HashTable为2的幂次方+1。
所以在扩容操作时,HashMap扩展为原先二倍,HashTable为二倍+1。
基本参数:
Entry<?,?>[] table -- 存放Entry数组(实际上每个Entry就是一个键值对,当Hash值相同时,以链表形式保存)
count -- 相当于size长度,代表当前已存在键值对个数
threshold -- 容量:可最大存储键值对个数
loadFactor -- 加载因子
initialCapacity -- 实例化长度。
实例化源码:
public Hashtable(int initialCapacity, float loadFactor) {
// 初始化长度不能小于0
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
// 加载因子不能小于等于0,loadFactor不能为非数字
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal Load: "+loadFactor);
// 如果初始化长度为0,设置为1
if (initialCapacity==0)
initialCapacity = 1;
// 加载因子赋值
this.loadFactor = loadFactor;
// 创建长度为initialCapacity的Entry数组
table = new Entry<?,?>[initialCapacity];
// 容量为最大数 与 初始长度 * 加载因子中较小的一个
threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
}
Put操作源码:
public synchronized V put(K key, V value) {
// Make sure the value is not null
// value不能为空
if (value == null) {
throw new NullPointerException();
}
// 将当前table赋给tab[]
// Makes sure the key is not already in the hashtable.
Entry<?,?> tab[] = table;
// 得到key的hash
int hash = key.hashCode();
// 获得数组中的索引
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
// 将tab数组中该索引地址内容赋给 entry
Entry<K,V> entry = (Entry<K,V>)tab[index];
// 如果有相同key的存在,覆盖value, return
for(; entry != null ; entry = entry.next) {
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
return old;
}
}
// 没有key值存在则添加键值
addEntry(hash, key, value, index);
return null;
}
addEntry源码:
private void addEntry(int hash, K key, V value, int index) {
modCount++;
// 原对象table赋给tab[]
Entry<?,?> tab[] = table;
// 如果当前键值对个数大于等于 最大可容纳数量
if (count >= threshold) {
// Rehash the table if the threshold is exceeded
// 进行扩容操作
rehash();
// 扩容后的table赋给tab
tab = table;
// 获取数组索引
hash = key.hashCode();
index = (hash & 0x7FFFFFFF) % tab.length;
}
// Creates the new entry.
// 创建一个Enrty赋给数组索引位置
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>) tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
}
rehash(扩容)源码:
protected void rehash() {
// 原数组长度
int oldCapacity = table.length;
// 原数组
Entry<?,?>[] oldMap = table;
// overflow-conscious code
// 新数组长度为原数组长度 * 2 + 1
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];
// 操作统计+1
modCount++;
// 新数组可容纳键值对数量
threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
// 修改当前table引用为新数组
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;
}
}
}