1. HashTable简介
1.1 HashTable继承关系
java.lang.Object
↳ java.util.Dictionary<K, V>
↳ java.util.Hashtable<K, V>
public class Hashtable<K,V> extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable { }
1.2 HashTable关系图:
1.3 HashTable描述:
(1)HashTable是一个散列表,存储的是键值对映射。
(2)HashTable是线程安全的,它的key-value不能为空,为空会报错,HashTable的映射是无序的。
(3)HashTable中的两个重要参数:初始容量和加载因子。
2. HashTable重点源码分析
HashTable的源码比较简单,这里只分析几个重要的方法。对比过jdk6和jdk8没有什么变动,这里列出jdk6的代码。
2.1 HashTable属性
//保存key-value的数组
private transient Entry[] table;
//HashTable中元素的实际数量
private transient int count;
//阀值,用于判断是否需要扩容
private int threshold;
//加载因子
private float loadFactor;
//HashTable的改变次数
private transient int modCount = 0;
2.2 HashTable数据节点Entry的数据结构
private static class Entry<K,V> implements Map.Entry<K,V> {
int hash;
K key;
V value;
Entry<K,V> next;//指向下一个Entry,即链表的下一个节点
protected Entry(int hash, K key, V value, Entry<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
protected Object clone() {
return new Entry<K,V>(hash, key, value,
(next==null ? null : (Entry<K,V>) next.clone()));
}
// Map.Entry Ops
public K getKey() {
return key;
}
public V getValue() {
return value;
}
//设置value,可以看到如果value的值为Null抛出空指针隐藏
public V setValue(V value) {
if (value == null)
throw new NullPointerException();
V oldValue = this.value;
this.value = value;
return oldValue;
}
//覆盖equals方法,判断两个Entry是否相等。如果两个Entry的key和value都相等,则相等
public boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry e = (Map.Entry)o;
return (key==null ? e.getKey()==null : key.equals(e.getKey())) &&
(value==null ? e.getValue()==null : value.equals(e.getValue()));
}
//覆盖hashCode
public int hashCode() {
return hash ^ (value==null ? 0 : value.hashCode());
}
public String toString() {
return key.toString()+"="+value.toString();
}
}
这里思考一个问题:为什么重写equals()方法就要重写hashCode()方法?
首先equals和hashCode之间的关系是这样的:
1.如果两个对象相同(equals比较返回true),那么他们的hashCode()的值一定相等;
2.如果两个对象的hashCode相同,他们并不一定相同(equals方法返回false) 我的理解:
(1)为了提高程序的效率才实现了hashCode方法,先进行hashCode的比较,如果不同,那么就没比较在进行equals的比较了,这样就大大减少了equals的比较次数,对于效率提升很快。这方面我们在集合中有很深的体会,我们在集合的源码中经常能看到,put对象的是,源码中都是先计算出hash值,先比较hash值,再进行equals的比较。 (2)为了保证同一个对象,保证在equals相同的情况下hashCode值必定相同,如果重写了equals并未重写hashCode方法,就可能出现两个没有关系的对象equals相同,但是hashCode不同。
2.3 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;
this.loadFactor = loadFactor;
table = new Entry[initialCapacity];
threshold = (int)(initialCapacity * loadFactor);
}
//指定容量大小的构造函数,默认加载因子0.75
public Hashtable(int initialCapacity) {
this(initialCapacity, 0.75f);
}
//默认构造函数,默认容量11
public Hashtable() {
this(11, 0.75f);
}
//包含子Map的构造函数
public Hashtable(Map<? extends K, ? extends V> t) {
this(Math.max(2*t.size(), 11), 0.75f);
putAll(t);
}
2.4 HashTable主要对外调用方法
2.4.1 put()
public synchronized V put(K key, V value) {
// Make sure the value is not null
//HashTable中不能插入value值为Null,空指针异常
if (value == null) {
throw new NullPointerException();
}
//根据传入的key获取到hash值,判断元素是否存在,存在用新值替代旧值
Entry tab[] = table;
int hash = key.hashCode();
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;
index = (hash & 0x7FFFFFFF) % tab.length;
}
// Creates the new entry.
Entry<K,V> e = tab[index];
tab[index] = new Entry<K,V>(hash, key, value, e);
count++;
return null;
}
2.4.2 内部扩容函数rehash()
protected void rehash() {
//原容量值
int oldCapacity = table.length;
//原table
Entry[] oldMap = table;
//新容量=原容量*2+1
int newCapacity = oldCapacity * 2 + 1;
Entry[] newMap = new Entry[newCapacity];
modCount++;
//重新计算阀值
threshold = (int)(newCapacity * loadFactor);
table = newMap;
//遍历原来的table,重新计算hash值放到新的table中
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.4.3 clear()
//将HashTable中table数组的值全部设置为null
public synchronized void clear() {
Entry tab[] = table;
modCount++;
for (int index = tab.length; --index >= 0; )
tab[index] = null;
count = 0;
}
2.4.4 contains() 和containsValue(),containsKey()
public synchronized boolean contains(Object value) {
if (value == null) {
throw new NullPointerException();
}
//从前向后遍历table中大元帅,判断节点值是否等于value
Entry tab[] = table;
for (int i = tab.length ; i-- > 0 ;) {
for (Entry<K,V> e = tab[i] ; e != null ; e = e.next) {
if (e.value.equals(value)) {
return true;
}
}
}
return false;
}
public boolean containsValue(Object value) {
return contains(value);
}
//根据传入的Key值计算hash值,先判断hash值是否相等,然后判断key是否相等
public synchronized boolean containsKey(Object key) {
Entry tab[] = table;
int hash = key.hashCode();
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)) {
return true;
}
}
return false;
}
2.4.5 remove()
//根据传入的key获取hash值,并遍历table删除数据
public synchronized V remove(Object key) {
Entry tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<K,V> e = tab[index], 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;
}
3. HashTable遍历
3.1 通过Iterator
通过entry获取键值对集合,再通过Iterator迭代器遍历集合
//1.获取键值对集合 Iterator iterator = table.entrySet().iterator(); //2.获取键的set集合
Iterator iter = table.keySet().iterator(); //3.获取value集合 Collection c = table.values(); Iterator iter= c.iterator(); while (iterator.hasNext()){ Map.Entry entry = (Map.Entry) iterator.next(); System.out.println("key="+entry.getKey()+",value="+entry.getValue()); }
3.2 通过Enumeration遍历
//1.获取key集合
Enumeration enumeration = table.keys();
//2.获取value集合
while (enumeration.hasMoreElements()){
System.out.println(enumeration.nextElement());
}