HashTable1.8介绍:
HashTable的底层实现是由“数组+链表”来实现的。和HashMap一样,Hashtable 也是一个散列表,它存储的内容是键值对(key-value)映射。无论key还是value都不能为null,且线程安全,实现线程安全的方式是在修改数据时锁住整个HashTable,效率低。但是Hashtable是一种能提供快速插入和查询的数据结构,无论其包含多少Item(条目),执行查询和插入操作的平均时间复杂度总是接近O(1)。
HashTable源码解析:
HashTable的继承关系:
Hashtable 继承于Dictionary,实现了Map、Cloneable、java.io.Serializable接口。
public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable {}
HashTable中的成员参数:
/**
* The hash table data.
*
* 数据保存数组 和hashMap中的tab同理
*/
private transient Entry<?,?>[] table;
/**
* The total number of entries in the hash table.
*
* 总条数 和hashMap中的size同理
*/
private transient int count;
//当表的大小超过此阈值时,将重新对其进行散列。(该字段的*值为(Int)(容量*加载因子))。
//table.length * loadFactor = threshold
private int threshold;
//加载因子
private float loadFactor;
//修改次数
private transient int modCount = 0;
HashTable的构造方法:
//可指定初始容量和加载因子
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;//初始容量最小值为1
this.loadFactor = loadFactor;
table = new Entry[initialCapacity];//创建桶数组
threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);//初始化容量阈值
useAltHashing = sun.misc.VM.isBooted() &&
(initialCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
}
/**
* Constructs a new, empty hashtable with the specified initial capacity
* and default load factor (0.75).
*/
//默认负载因子为0.75
public Hashtable(int initialCapacity) {
this(initialCapacity, 0.75f);
}
//默认容量为11,负载因子为0.75
public Hashtable() {
this(11, 0.75f);
}
/**
* Constructs a new hashtable with the same mappings as the given
* Map. The hashtable is created with an initial capacity sufficient to
* hold the mappings in the given Map and a default load factor (0.75).
*/
public Hashtable(Map<? extends K, ? extends V> t) {
this(Math.max(2*t.size(), 11), 0.75f);
putAll(t);
}
HashTable的原理图:
hash数组—>Entry[];
每个数组元素Entry<>元素中保存的是一个单向链表的头结点。
注意:
1.Hashtable的默认容量为11,默认负载因子为0.75。
2.Hashtable的容量可以为任意整数,最小值为1。
3.跟HashMap一样,Hashtable内部也有一个静态类叫Entry,其实是个键值对对象,保存了键和值的引用。
4.HashTable和HashMap1.7一样也是通过链表来解决hash冲突的。
HashTable的API:
synchronized void clear()
synchronized Object clone()
boolean contains(Object value)
synchronized boolean containsKey(Object key)
synchronized boolean containsValue(Object value)
synchronized Enumeration<V> elements()
synchronized Set<Entry<K, V>> entrySet()
synchronized boolean equals(Object object)
synchronized V get(Object key)
synchronized int hashCode()
synchronized boolean isEmpty()
synchronized Set<K> keySet()
synchronized Enumeration<K> keys()
synchronized V put(K key, V value)
synchronized void putAll(Map<? extends K, ? extends V> map)
synchronized V remove(Object key)
synchronized int size()
synchronized String toString()
synchronized Collection<V> values()
HashTable的重要对外接口
Clear()方法:
clear() 的作用是清空Hashtable。它是将Hashtable的table数组的值全部设为null
public synchronized void clear() {
Entry tab[] = table;
modCount++;
for (int index = tab.length; --index >= 0; )
tab[index] = null;
count = 0;
}
put()方法:
HashMap中key和value可以是null,HashTable中是不可以的。
/**
* Maps the specified <code>key</code> to the specified
* <code>value</code> in this hashtable. Neither the key nor the
* value can be <code>null</code>. <p>
*
* The value can be retrieved by calling the <code>get</code> method
* with a key that is equal to the original key.
*
* @param key the hashtable key
* @param value the value
* @return the previous value of the specified key in this hashtable,
* or <code>null</code> if it did not have one
* @exception NullPointerException if the key or value is
* <code>null</code>
* @see Object#equals(Object)
* @see #get(Object)
*/
// todo 这类加锁保证线程安全 这里就是实例锁,性能相对于concurrentHashMap的分段锁来说 比较慢
public synchronized V put(K key, V value) {
// Make sure the value is not null
// todo 这里说明hashTable中的value不能为空
if (value == null) {
throw new NullPointerException();
}
// 查找key对应的数组下标 以便获取所在的链表
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
//这里判断是否存在当前key
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];
for(; entry != null ; entry = entry.next) {
// 这里也不太一样 hashMap中可以设置判断value是否相等类判断是否覆盖老value
// hashMap中相当于 就有一个cas的原理可供选择
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
return old;
}
}
addEntry(hash, key, value, index);
return null;
}
addEntry() 方法:
这里面就是实际的插入过程,但是这里面判断了是否需要扩容,没有判断是否已经包含,是否包含都是在调用这个方法前判断的。
//这里没有判断 原来的table中是否已经包含这个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++;
}
rehash()方法:
他也是和hashmap差不多 就是直接扩容两倍,但是hashTable中会在加1,并且扩容后要重新计算每个元素对应的数组位子,相对于hashMap来说性能会有点差距,hashMap少一步取余的计算,他们在插入数据时都是头插法。
//扩容操作
@SuppressWarnings("unchecked")
protected void rehash() {
int oldCapacity = table.length;
Entry<?,?>[] oldMap = table;
//hashMap中的length 都市2的次方倍数并且扩容都是 * 2的 但是hashTable 实在原来的基础上* 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];
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;
// 这里是重新计算hash 但是hashMap中优化的比较好,不需要重新计算, 根据二进制来 判断hash和老长度 与运算 是否大于0 大于则在 老长度加上再老数组中的下标 就是新下标 小于则下标不变
int index = (e.hash & 0x7FFFFFFF) % newCapacity;
// 这里相同hashMap和 hashTable都是放在第一个
e.next = (Entry<K,V>)newMap[index];
newMap[index] = e;
}
}
}
其他方法都比较简单。