美文网首页
jdk源码分析之HashTable

jdk源码分析之HashTable

作者: shoulda | 来源:发表于2018-06-25 21:42 被阅读0次

    1.定义

    HashTable在java中的定义如下:

    public class Hashtable<K,V>
        extends Dictionary<K,V>
        implements Map<K,V>, Cloneable, java.io.Serializable
    

    可以看出HashTable继承Dictionary类,实现Map接口。其中Dictionary类是可以将任何键映射到值的类的抽象父类。每个键和每个值都是一个对象。

    HashTable定义了几个重要的参数:table,count,threhold,loadFactor

    table:为一个Entry[]数组类型,Entry代表了节点,每一个Entry代表一个键值对。
    count:HashTable的大小,指的是他包含Entry键值对的数量。
    threshold:HashTable的阈值
    loadFactor:加载因子

    2.构造方法

    public Hashtable() {
            this(11, 0.75f);
        }
    

    默认构造函数,容量为11,加载因子为0.75

    public Hashtable(int initialCapacity) {
            this(initialCapacity, 0.75f);
        }
    

    用指定初始容量和默认的加载因子(0.75)构造一个新的空哈希表。

    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,获得大小为initialCapacity的table数组
            table = new Entry[initialCapacity];
            //计算阀值
            threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
            //初始化HashSeed值
            initHashSeedAsNeeded(initialCapacity);
        }
    

    指定初始容量和指定加载因子,initHashSeedAsNeeded方法用于初始化hashSeed参数,其用于计算key的hash值,它与key的hashCode进行按位异或运算,hashSeed随机值主要是为了解决hash冲突。

    3.主要方法

    public synchronized V put(K key, V value) {
            // 确保value不为null
            if (value == null) {
                throw new NullPointerException();
            }
    
            /*
             * 确保key在table[]是不重复的
             * 处理过程:
             * 1、计算key的hash值,确认在table[]中的索引位置
             * 2、迭代index索引位置,如果该位置处的链表中存在一个一样的key,则替换其value,返回旧值
             */
            Entry tab[] = table;
            int hash = hash(key);    //计算key的hash值
            int index = (hash & 0x7FFFFFFF) % tab.length;     //确认该key的索引位置
            //迭代,寻找该key,替换
            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();
                tab = table;
                hash = hash(key);
                index = (hash & 0x7FFFFFFF) % tab.length;
            }
    
            // 在索引位置处插入一个新的节点
            Entry<K,V> e = tab[index];
            tab[index] = new Entry<>(hash, key, value, e);
            //容器中元素+1
            count++;
            return null;
        }
    

    由上可知,根据key的hash值,根据hash值获得key在table数组中的索引位置,然后迭代该key处的Entry链表,如果该链表存在一个这样的key对象,那么直接替换value值即可,否则就讲该key-value节点插入到节点处。

    public synchronized V get(Object key) {
            Entry tab[] = table;
            int hash = hash(key);
            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 e.value;
                }
            }
            return null;
        }
    

    get方法比较简单,计算key的hash值,判断在table数组中的索引位置。

    HashTable的put方法中的扩容操作

    HashTable中的扩容操作

    protected void rehash() {
            int oldCapacity = table.length;
            //元素
            Entry<K,V>[] 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;
            }
    
            //新建一个size = newCapacity 的HashTable
            Entry<K,V>[] newMap = new Entry[];
    
            modCount++;
            //重新计算阀值
            threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
            //重新计算hashSeed
            boolean rehash = initHashSeedAsNeeded(newCapacity);
    
            table = newMap;
            //将原来的元素拷贝到新的HashTable中
            for (int i = oldCapacity ; i-- > 0 ;) {
                for (Entry<K,V> old = oldMap[i] ; old != null ; ) {
                    Entry<K,V> e = old;
                    old = old.next;
    
                    if (rehash) {
                        e.hash = hash(e.key);
                    }
                    int index = (e.hash & 0x7FFFFFFF) % newCapacity;
                    e.next = newMap[index];
                    newMap[index] = e;
                }
            }
        }
    

    rehash()是将原来的容量扩大两倍 + 1,同时需要将原来的HashTable中的元素复制到新的HashTable中。

    4.HashTable和HashMap的区别

    1.HashTable是基于Dictionary类,而HashMap是基于AbstractMap.
    2.HashMap可以允许存在一个为null的key和任意个为null的value,但是HashTable的key和value都不能为null.
    3.HashTable的方法是同步的,而HashMap不是,所以一般涉及多线程都推荐用HashTable。

    相关文章

      网友评论

          本文标题:jdk源码分析之HashTable

          本文链接:https://www.haomeiwen.com/subject/htluyftx.html