源码解读:Hashtable源码解析(JDK8)

时间:2020-03-08
本文章向大家介绍源码解读:Hashtable源码解析(JDK8),主要包括源码解读:Hashtable源码解析(JDK8)使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

Hashtable源码分析(JDK8)

一、简述

注意是Hashtable不是HashTable(t为小写),这不是违背了驼峰定理了嘛?这是因为Hashtable是在Java1.0的时候创建的,而集合的统一规范命名是在后来的Java2开始约定的,而当时又发布了新的集合代替它,所以这个命名也一直使用到现在,所以Hashtable是一个过时的集合了,现在已经不推荐使用这个类,虽说Hashtable过时了,我们还是有必要分析一下它,以便对Java集合框架有一个整体的认知。

二、特性

  1. Hashtable是基于哈希表实现的,每个元素是一个key-value对,其内部也是通过拉链法解决冲突问题。
  2. Hashtable是线程安全的,键值不允许为null
  3. Hashtable实现了Serializable接口,它支持序列化,实现了Cloneable接口,能被克隆。
  4. Hashtable采用数组+链表存储元素,HashMap则是采用数组+链表/红黑树(JDK8)存储元素。

三、成员变量

    //Entry类型数组
    private transient Entry<?,?>[] table;
    //元素数量
    private transient int count;
    //阈值,判断是否调整table长度
    private int threshold;
    //加载因子
    private float loadFactor;
    //集合修改次数
    private transient int modCount = 0;
    //序列化ID
    private static final long serialVersionUID = 1421746759512286392L;

四、构造函数

    //默认构造 默认容量11,加载因子0.75
    public Hashtable() {
        this(11, 0.75f);
    }
    //指定初始容量
    public Hashtable(int initialCapacity) {
        this(initialCapacity, 0.75f);
    }   
    //指定初始容量 与 加载因子 的构造函数
    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) //初始容量为0
            initialCapacity = 1;//指定初始容量为1
        this.loadFactor = loadFactor;
        table = new Entry<?,?>[initialCapacity]; //创建Entry类型数组
        //确定阈值,(容量*阈值)或 (MAX_ARRAY_SIZE+1)之中小的值
        threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
    }
    //根据另一个map创建table
    public Hashtable(Map<? extends K, ? extends V> t) {
        //初始容量 max(t的容量的2倍,11)
        this(Math.max(2*t.size(), 11), 0.75f);
        putAll(t);//将t中的元素加入table
    }   
  1. Hashtable默认容量是11,默认加载因子是0.75
  2. 指定了初始容量和加载因子的构造函数,确认阈值要与MAX_ARRAY_SIZE+1比较。
  3. 利用map构造Hashtable,初始容量是map元素容量的2倍。

五、Entry节点类

    //Entry类
    private static class Entry<K,V> implements Map.Entry<K,V> {
        final int hash; //哈希值
        final 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;
        }

        @SuppressWarnings("unchecked")
        protected Object clone() {
            //new Entry()
            //next存在,调用clone()继续克隆
            return new Entry<>(hash, key, value,
                                  (next==null ? null : (Entry<K,V>) next.clone()));
        }
        // Map.Entry Ops
        public K getKey() {
            return key;
        }
        public V getValue() {
            return value;
        }
        public V setValue(V value) {
            if (value == null)
                throw new NullPointerException();
            V oldValue = this.value;
            this.value = value;
            return oldValue;
        }
        //Entry equals
        public boolean equals(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry<?,?> e = (Map.Entry<?,?>)o;
            //key是否equals value是否equals
            return (key==null ? e.getKey()==null : key.equals(e.getKey())) &&
               (value==null ? e.getValue()==null : value.equals(e.getValue()));
        }
        //entry的hashcode hash与value的hashcode异或
        public int hashCode() {
            return hash ^ Objects.hashCode(value);
        }
        //输出key与value
        public String toString() {
            return key.toString()+"="+value.toString();
        }
    }
  1. 可以看出是个单链表。
  2. 节点类重写了clone(),可以使用clone()克隆整条链表,而HashMap的Entry类没有重写。

六、增/改

    //添加元素(同步方法)
    public synchronized V put(K key, V value) {
        // Make sure the value is not null
        //确保value不为空
        if (value == null) {
            throw new NullPointerException();
        }
        
        // Makes sure the key is not already in the hashtable.
        //确保key在table中不存在
        Entry<?,?> tab[] = table; //获取table
        int hash = key.hashCode(); //计算key的hashcode(构建entry使用的hash)
        int index = (hash & 0x7FFFFFFF) % tab.length; //保证hash值为正数,得到hash在table中的索引
        @SuppressWarnings("unchecked")
        Entry<K,V> entry = (Entry<K,V>)tab[index]; //获取index位置的头节点
        //遍历
        for(; entry != null ; entry = entry.next) {
            //如果entry的hash与entry key的hash都相等,说明键值对已经存在
            if ((entry.hash == hash) && entry.key.equals(key)) {
                V old = entry.value; //获取旧值
                entry.value = value; //替换value
                return old;         //返回旧值
            }
        }
        //key在table中不存在
        //插入
        addEntry(hash, key, value, index);
        return null;
    }
    //将集合t中的元素添加入talbe
    public synchronized void putAll(Map<? extends K, ? extends V> t) {
        for (Map.Entry<? extends K, ? extends V> e : t.entrySet()) //遍历map的entrySet
            put(e.getKey(), e.getValue()); //put添加
    }
  1. Hashtable中大部分方法都已经使用synchronized进行同步,所以保证线程安全。
  2. put()一开始判断value是否为空,保证value不为null。
  3. key.hashCode()当key为null时,抛出异常,保证key不为null。
  4. hash & 0x7FFFFFFF int类型的任何值 &0x7FFFFFFF 都是一个正数,保证取模运算后,索引得到的是一个正数
  5. 每次添加新元素,会根据计算出得index遍历链表,如果发现key已经存在,则替换并返回value,没有找到则添加新节点。
  6. putAll()采用遍历entrySet的方式,将每个entry添加入table。
    //添加entry
    private void addEntry(int hash, K key, V value, int index) {
        modCount++;
        //获取table
        Entry<?,?> tab[] = table;
        if (count >= threshold) { //元素数量 》= 阈值,扩容
            // Rehash the table if the threshold is exceeded
            rehash();
            
            tab = table; //扩容后获取新table
            hash = key.hashCode(); //计算entry的hash
            index = (hash & 0x7FFFFFFF) % tab.length; //计算新的index
        }

        // Creates the new entry.
        @SuppressWarnings("unchecked")
        Entry<K,V> e = (Entry<K,V>) tab[index]; //获取index处的节点e
        //将新元素放到index处,新节点的后继节点指向e
        tab[index] = new Entry<>(hash, key, value, e);
        count++;
    }

Hashtable将新节点插入链表时,采用的是头部插入,这与HashMap的尾部插入有所不同。

七、扩容(重点)

    //扩容
    @SuppressWarnings("unchecked")
    protected void rehash() {
        int oldCapacity = table.length; //获取旧的数组长度
        Entry<?,?>[] oldMap = table;    //获取talbe

        // overflow-conscious code
        //在应用数据分布在等差数据集合(如偶数)上时,如果公差与桶容量有公约数n,则至少有(n-1)/n数量的桶是利用        不到的。
        //实际上 HashMap 也会有此问题,并且不能指定桶容量。所以 HashMap 会在取模哈希前先做一次哈希
        int newCapacity = (oldCapacity << 1) + 1; //新数组容量是旧数组的2倍+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; //table 指向 新数组

        //从oldCapacity处向前遍历旧数组,将旧数组元素复制到新数组
        for (int i = oldCapacity ; i-- > 0 ;) {
            //遍历链表,移动链表上的元素
            for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {
                Entry<K,V> e = old; //保存当前entry
                old = old.next;     //old指向下一个entry

                int index = (e.hash & 0x7FFFFFFF) % newCapacity; //计算元素新的位置
                 //当前entry的后继节点 = index处的entry(上一个添加的entry)
                e.next = (Entry<K,V>)newMap[index];
                newMap[index] = e; //index索引指向e
            }
        }
    }
  1. 数组扩容时,新数组是旧数组的2倍+1,这样能至少能保证扩容后数组长度是奇数,这样做是为了让元素散列,减小哈希冲突概率。
  2. 数组最大长度是Integer.MAX_VALUE - 8,而不是Integer.MAX_VALUE,即使MAX_ARRAY_SIZE依旧容量不足,也继续使用MAX_ARRAY_SIZE当作数组长度。
  3. HashMap中当Integer.MAX_VALUE - 8依旧容量不足时,可以扩容为Integer.MAX_VALUE
  4. 创建新数组后,重新计算每个元素的index,使用头部插入添加到新位置。

八、删

    public synchronized V remove(Object key) {
            Entry<?,?> tab[] = table; //获取table
            int hash = key.hashCode();//计算hashcode
            int index = (hash & 0x7FFFFFFF) % tab.length; //根据hashcode计算索引
            @SuppressWarnings("unchecked")
            Entry<K,V> e = (Entry<K,V>)tab[index]; //获取index的头节点
            //遍历链表
            for(Entry<K,V> prev = null ; e != null ; prev = e, e = e.next) {
                if ((e.hash == hash) && e.key.equals(key)) { //entry的hash与key都相同
                    modCount++;
                    if (prev != null) { //前驱节点不为空
                        prev.next = e.next; //建立新连接
                    } else {            //e是index的头节点
                        tab[index] = e.next;//更新index处entry
                    }
                    count--;
                    V oldValue = e.value;
                    e.value = null;
                    return oldValue; //返回e的value
                }
            }
            return null; //没找到返回null
        }
  1. 删除要先根据key的hashcode计算出元素在数组中的索引,再遍历链表,根据在链表中的位置进行删除。
  2. 删除结束返回节点value。

九、查

    //根据key查找value
    public synchronized V get(Object key) {
        Entry<?,?> tab[] = table;
        int hash = key.hashCode();
        int index = (hash & 0x7FFFFFFF) % tab.length;
        for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
            if ((e.hash == hash) && e.key.equals(key)) { //hash与key都相等
                return (V)e.value;
            }
        }
        return null;
    }
    //key存在返回value,不存在返回null
    @Override
    public synchronized V getOrDefault(Object key, V defaultValue) {
        V result = get(key); //根据key获取value
        //value不为空返回,为空返回null
        return (null == result) ? defaultValue : result;
    }

根据key计算出index,然后遍历链表,节点存在则返回value,不存在则返回null。

十、迭代(重要)

Hashtable提供了两种遍历方式:枚举器迭代器。现在大部分集合框架都只采用迭代器遍历,这两者的区别,我会用另一篇文章讲解。

    //Hashtable枚举类
    //元素数量为0,返回“空枚举类”对象
    private <T> Enumeration<T> getEnumeration(int type) {
        if (count == 0) {
            return Collections.emptyEnumeration(); //返回空枚举类
        } else {
            return new Enumerator<>(type, false); //返回Enumerator对象
        }
    }
    //Hashtable迭代器
    //元素数量为0,返回“空迭代器”对象
    private <T> Iterator<T> getIterator(int type) {
        if (count == 0) {
            return Collections.emptyIterator(); //返回空迭代器
        } else {
            return new Enumerator<>(type, true); //返回迭代器对象
        }
    }
  1. 枚举器与迭代器的构建都使用了Enumerator类,唯一的区别是传入boolean值的不同,这是一个标志,用来确认构建的是枚举器还是迭代器。
  2. 当集合中没有元素时,一个返回空枚举器对象,一个返回空迭代器对象。

枚举类型

    // Types of Enumerations/Iterations
    // 枚举类型
    private static final int KEYS = 0;  //键
    private static final int VALUES = 1; //值
    private static final int ENTRIES = 2; //键值对

Enumerator

    //提供了通过“elements()”和“keys()”遍历hashtable的接口
    private class Enumerator<T> implements Enumeration<T>, Iterator<T> {
        Entry<?,?>[] table = Hashtable.this.table; //获取table
        int index = table.length;//获取table长度
        Entry<?,?> entry;
        Entry<?,?> lastReturned;
        int type;
        
        //Enumerator是迭代器还是枚举类的标志
        // true是迭代器遍历,false是枚举遍历
        boolean iterator;
        
        protected int expectedModCount = modCount;
        
        Enumerator(int type, boolean iterator) {
            this.type = type;
            this.iterator = iterator;
        }
        //从后向前遍历table数组,直到找到不为null的entry
        public boolean hasMoreElements() {
            Entry<?,?> e = entry;
            int i = index;
            Entry<?,?>[] t = table;
            /* Use locals for faster loop iteration */
            while (e == null && i > 0) {
                e = t[--i];
            }
            entry = e;
            index = i;
            return e != null;
        }

        //向下遍历链表
        @SuppressWarnings("unchecked")
        public T nextElement() {
            Entry<?,?> et = entry;
            int i = index;
            Entry<?,?>[] t = table;
            /* Use locals for faster loop iteration */
            while (et == null && i > 0) {
                et = t[--i];
            }
            entry = et;
            index = i;
            if (et != null) {
                Entry<?,?> e = lastReturned = entry;
                entry = e.next;
                //根据type返回枚举类型
                return type == KEYS ? (T)e.key : (type == VALUES ? (T)e.value : (T)e);
            }
            throw new NoSuchElementException("Hashtable Enumerator");
        }

        // Iterator methods
        // 判断是否存在下一个元素
        public boolean hasNext() {
            return hasMoreElements();
        }
        //获取下一个元素
        public T next() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            return nextElement();
        }
        //删除元素
        public void remove() {
            if (!iterator) //说明是枚举类,不能迭代
                throw new UnsupportedOperationException();
            if (lastReturned == null) //当前元素为空
                throw new IllegalStateException("Hashtable Enumerator");
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            //遍历table
            synchronized(Hashtable.this) {
                Entry<?,?>[] tab = Hashtable.this.table;
                int index = (lastReturned.hash & 0x7FFFFFFF) % tab.length;

                @SuppressWarnings("unchecked")
                Entry<K,V> e = (Entry<K,V>)tab[index];
                //遍历链表,找到entry,删除
                for(Entry<K,V> prev = null; e != null; prev = e, e = e.next) {
                    if (e == lastReturned) {
                        modCount++;
                        expectedModCount++;
                        if (prev == null)
                            tab[index] = e.next;
                        else
                            prev.next = e.next;
                        count--;
                        lastReturned = null;
                        return;
                    }
                }
                throw new ConcurrentModificationException();
            }
        }
    }
  1. Enumerator类定义了一个iterator变量来确认构造的是枚举器还是迭代器。
  2. 枚举器使用hasMoreElements()nextElement()来遍历元素,一个方法用来定位位置,另一个方法用来返回元素。
  3. 枚举器无法删除元素,在remove()中,会首先判断iterator,如果为false则抛出异常。
    //返回“所有key”的枚举对象
    public synchronized Enumeration<K> keys() {
        return this.<K>getEnumeration(KEYS);
    }
    //返回“所有value”的枚举对象
    public synchronized Enumeration<V> elements() {
        return this.<V>getEnumeration(VALUES);
    }

使用枚举器只能遍历Key集合value集合,无法遍历key-value集合

Iterator

    private transient volatile Set<K> keySet; //key集合
    private transient volatile Set<Map.Entry<K,V>> entrySet; //entry集合
    private transient volatile Collection<V> values; //value集合
    //获得keySet
    public Set<K> keySet() {
        if (keySet == null) //keySet为空
            keySet = Collections.synchronizedSet(new KeySet(), this); //同步创建一个keySet
        return keySet;//返回
    }
    //KeySet类
    private class KeySet extends AbstractSet<K> {
        public Iterator<K> iterator() {
            return getIterator(KEYS); //返回一个key迭代器
        }
    }
    //获得entrySet
    public Set<Map.Entry<K,V>> entrySet() {
        if (entrySet==null)
            entrySet = Collections.synchronizedSet(new EntrySet(), this); //同步创建entrySet
        return entrySet;
    }
    //EntrySet类
    private class EntrySet extends AbstractSet<Map.Entry<K,V>> {
        public Iterator<Map.Entry<K,V>> iterator() {
            return getIterator(ENTRIES); //返回一个entry迭代器
        }
    }
    //获得value类、集合
    public Collection<V> values() {
        if (values==null)
            values = Collections.synchronizedCollection(new ValueCollection(),
                                                        this); //同步创建values
        return values;
    }
    //ValueCollection类
    private class ValueCollection extends AbstractCollection<V> {
        public Iterator<V> iterator() {
            return getIterator(VALUES); //返回一个value迭代器
        }
    }

可以看出,迭代器遍历,就是调用getIterator(int type)返回一个迭代器,再使用hashNext()next()remove()对集合进行遍历或删除操作。

原文地址:https://www.cnblogs.com/le-le/p/12442505.html