您的位置:首页 > 编程语言 > Java开发

jdk1.8的Hashtable源码解析

2018-02-14 20:13 459 查看

Hashtable声明

public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, Cloneable, java.io.Serializable
    Hashtable继承于Dictionary类(Dictionary类声明了操作键值对的接口方法),实现Map接口(定义键值对接口);

    Hashtable大部分方法都加了
synchronized
关键字,所以虽然是线程安全的,但在多线程并发环境下的效率较低。                     

1、Hashtable的几个重要变量

private transient Entry<?,?>[] table
:键值对/Entry数组,每个Entry本质上是一个单向链表的表头

private int threshold
:rehash阈值,当超过该阈值会rehash(重排序)

private float loadFactor
:装填因子

private transient int modCount = 0
: Hashtable结构化修改次数,用来实现fail-fast机制;

private transient volatile Set<Map.Entry<K,V>> entrySet
:键值对集合,不可重复;

private transient volatile Set<K> keySet
:key的集合,不可重复;

private transient volatile Collection<V> values
:value集合,可重复;

private static final int Max_ARRAY_SIZE = Integer.MAX_VALUE-8;
:散列表容量经过n次扩容之后,设置上限的阈值

2、Hashtable的几个内部类

键值对—Entry

private static class Entry<K,V> implements Map.Entry<K,V> {
final int hash;    //哈希值
final K key;       //key
V value;           //value
Entry<K, V> next;  //下一个entry结点
...
// 计算键值对的hashCode
public int hashCode() {
// "^" 按位异或, hash在调用构造器时传入
return hash ^ Objects.hashCode(value);
}
}
Value集合——ValueCollection
private class ValueCollection extends AbstractCollection<V> {        public Iterator<V> iterator() {            return getIterator(VALUES);        }        public int size() {            returncount;        }        public boolean contains(Object o) {            return containsValue(o);        }        public void clear() {            Hashtable.this.clear();        }
    }
EntrySet集合private class EntrySet extendsAbstractSet<Map.Entry<K,V>> {        public Iterator<Map.Entry<K,V>>iterator() {            return getIterator(ENTRIES);        }        publicboolean add(Map.Entry<K,V> o) {            returnsuper.add(o);        }        ......}      
Keys集合
private class KeySet extendsAbstractSet<K> {        public Iterator<K> iterator() {            return getIterator(KEYS);        }        publicint size() {            returncount;        }        public boolean contains(Object o) {            return containsKey(o);        }        public boolean remove(Object o) {            return Hashtable.this.remove(o) != null;        }        publicvoid clear() {            Hashtable.this.clear();        }    }3、Hashtable几个重要的方法分析
     主要方法:Hashtable()、contains()、get()、rehash()、addEntry()、put(K,V)、remove(Object),像size()、keys()、values()、isEmpty()、elements()比较简单就不一一介绍了。
(1) Hashtablepublic 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); //threshold的计算方式,MAX_ARRAY_SIZE防止经过n次扩容后,数组大小超出整数的最大值,所以这里设定一个上限的阈值}
   public Hashtable() { 

         this(11, 0.75f); //默认的table容量为11,装载因子为0.75 }(2) containspublic synchronized boolean contains(Object value) {      if (value == null) {    //Hashtable的value不允许为空,不然会报空指针                thrownew NullPointerException();      }               Entry<?,?> tab[] = table;            //从数组的最后往前遍历            for (int i = tab.length ; i-- > 0 ;) {            //再对每个entry链表进行遍历                for (Entry<?,?> e = tab[i] ; e != null ; e = e.next) {            //如果找到对应的value,返回true                   if (e.value.equals(value)) {                                return true;                   }                }            }            //没找到返回false            return false;}(3) containsKey
public synchronized boolean containsKey(Object key) {            Entry<?,?> tab[] = table;            int hash = key.hashCode();            //计算数组的索引,Hashtable本质上采用除数取余法进行散列分布            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 true;                }            }            return false;}(4) get
public synchronized V get(Object key) {            Entry<?,?> tab[] = table;            int hash = key.hashCode();            //通过key的hash值和table的length,经过运算得到散列表中的索引            int index = (hash & 0x7FFFFFFF) %tab.length;            //遍历tab[index]对应的链表            for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {            //如果entry的key和hash值与期望值一致,则返回                if ((e.hash == hash) && e.key.equals(key)){                   return (V)e.value;                }            }            //否则返回空值            return null; }
(4) rehash
@SuppressWarnings("unchecked")        protectedvoid rehash() {            //保存旧的容量和Entry数组            int oldCapacity = table.length;            Entry<?,?>[] oldMap = table;            //定义新值,新的容量为旧值的2倍加1            int newCapacity = (oldCapacity << 1) + 1;            //判断新的容量是否超过了上限            if (newCapacity - MAX_ARRAY_SIZE > 0) {                if (oldCapacity == MAX_ARRAY_SIZE)            
                   return;                newCapacity = MAX_ARRAY_SIZE;            }            //定义新的数组            Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];            //此时,散列表内的元素发生变化,modCount指针加1            modCount++;            //重新计算新的threshold            threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE+ 1);            table = newMap;                        //对散列表内的所有元素进行“重排列”,顺序从后往前            for (int i = oldCapacity ; i-- > 0 ;) {                //遍历oldMap[i]对应的entry链表                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;                /**                 * 可能e.next = (Entry<K,V>)newMap[index];和newMap[index] = e;比较生硬,我们举例说明                 * 第一次遍历时,newMap[index]为null,所以 e.next=null                 * 第二次遍历时,假定e1=e.next, 然后 e1.next = e, newMap[index] = e1(起始点为e1)                 * ...                 * 依次进行,会发现链表中插入元素的顺序是“从左往右”                 */                }            }  }(5) addEntryprivate void addEntry(int hash, K key, V value, int index) {             //modCount指针,对于修改操作不会自增,而对于内部元素数量的变化会自增               modCount++;               Entry<?,?> tab[] = table;               //如果count大于阈值               if (count >= threshold) {                   //进行重排列操作                    rehash();                    tab = table;                    hash = key.hashCode();                    index = (hash & 0x7FFFFFFF) %tab.length;               }               //如果不大于阈值,则直接插入               @SuppressWarnings("unchecked")               Entry<K,V> e = (Entry<K,V>) tab[index];               [b]//如果e为空,将插入到起始位置,如果e不为空,则从左向右插入[/b]
                tab[index] = new Entry<>(hash, key, value, e);                count++;      }
[b](6) put[/b][b][b][b][b]下图为jdk1.8的hashMap的put方法的过程,Hashtable和其类似,基本上“计算索引,逻辑判断”这个部分是一致的,差别在于Hashtable中没有用到红黑树。[/b][/b]
[/b][/b]                             



public synchronized V put(K key, V value) {            // 首先value不能为空            if (value == null) {                throw new NullPointerException();            }                               Entry<?,?> tab[] = table;            int hash = key.hashCode();            int index = (hash & 0x7FFFFFFF) %tab.length;            @SuppressWarnings("unchecked")            //判断tab[index]是否已经有值            Entry<K,V> entry = (Entry<K,V>)tab[index];            //如果有值,则遍历            for(; entry != null ; entry = entry.next) {                //如果key重复,那么新的value覆盖旧值                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;   }[b][b](7) remove[/b][/b]public synchronized V remove(Object key) {            Entry<?,?> tab[] = table;            int hash = key.hashCode();            //计算索引            int index = (hash & 0x7FFFFFFF) %tab.length;            @SuppressWarnings("unchecked")            //获得索引位置对应的Entry链表            Entry<K,V> e = (Entry<K,V>)tab[index];            //遍历链表中的entry元素            for(Entry<K,V> prev = null ; e != null ; prev = e, e = e.next) {                //如果找到需要的entry,进行删除                if ((e.hash == hash) &&e.key.equals(key)) {                   //修改指针加1                   modCount++;                   //如果prev结点非空                   if (prev != null) {                       //那么pre结点的next指针指向e的next,等价于e被删除                       prev.next = e.next;                   } else {                       //否则,说明需要删除的为起始结点                       tab[index] = e.next;                   }                   count--;                   V oldValue = e.value;                   e.value = null;                   return oldValue;                }            }            return null; }

HashMap和Hashtable的区别

HashMap是非线程安全的,Hashtable是线程安全的,所以Hashtable重量级一些,因为使用了synchronized关键字来保证线程安全。
HashMap允许key和value都为null,而Hashtable都不能为null。
Hashtable继承自 JDK 1.0 的 Dictionary 虚拟类,而HashMap是 JDK 1.2 引进的 Map 接口的一个实现。
Hashtable和HashMap扩容的方法不一样,Hashtable中数组默认大小11,扩容方式是 old*2+1。HashMap中数组的默认大小是16,而且一定是2的指数,增加为原来的2倍。
两者通过hash值散列到hash表的算法不一样,Hashtable是古老的除留余数法,直接使用Object的hashcode,而后者是强制容量为2的幂,重新根据hashcode计算hash值,在使用hash和(hash表长度 – 1)进行与运算,也等价取膜,但更加高效,取得的位置更加分散,偶数,奇数保证了都会分散到。前者就不能保证。
HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。所以当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException,但迭代器本身的remove()方法移除元素则不会抛出ConcurrentModificationException异常。以下面测试case为例:
 
    //测试Hashtable是否是“fast-fail"机制    @Test    public void test2() {        Hashtable<String,String> table = new Hashtable<String,String>();        table.put("a", "gao");        table.put("b", "zhu");        table.put("c", "zhang");        table.put("d", "sun");                Iterator<String> keys =table.keySet().iterator();        while(keys.hasNext()) {            String s = keys.next();            if("c".equals(s)) {                table.remove(s);            }else {                System.out.println(s);            }        }    }    //测试HashMap是否是"fast-fail"机制    @Test    public void test3() {        Map<String,String> table2 = new HashMap<String,String>();        table2.put("a", "gao");        table2.put("b", "zhu");        table2.put("c", "zhang");        table2.put("d", "sun");                Iterator<String> keys =table2.keySet().iterator();        while(keys.hasNext()) {            String s = keys.next();            if("c".equals(s)) {                table2.remove(s);            }else {                System.out.println(s);            }        }      
    }
结果发现:Hashtable不会报ConcurrentModificationException异常,而HashMap会!说明Hashtable的enumerator迭代器不是遵循fail-fast机制的。

 

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Hashtable源码 jdk1.8