HashTable源码分析

基于JDK1.8进行分析的,Hashtable类的实现也是基于“数组和链表”来实现的。

继承结构

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

通过上面可以看到继承的是Dictionary类,实现了Map、Cloneable、Serializable。

成员属性

    //用来实现Hashtable所借助的数组,默认大小为11.
    private transient Entry<?,?>[] table;

    //用来记录table数组中存储元素的个数
    private transient int count;

    //扩容的阈值
    //即如果数组table中存储的元素个数大于threshold,则扩大数组table的大小
    private int threshold;

    //加载因子,默认值为0.75f
    //扩容阈值threshold的值等于loadFactor与数组table的容量的乘积.
    private float loadFactor;

    //修改次数
    private transient int modCount = 0;

    //序列化的时候使用
    private static final long serialVersionUID = 1421746759512286392L;

构造函数

    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)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
    }

    /**
     * Constructs a new, empty hashtable with the specified initial capacity
     * and default load factor (0.75).
     *
     * @param     initialCapacity   the initial capacity of the hashtable.
     * @exception IllegalArgumentException if the initial capacity is less
     *              than zero.
     */
    public Hashtable(int initialCapacity) {
        this(initialCapacity, 0.75f);
    }

    /**
     * Constructs a new, empty hashtable with a default initial capacity (11)
     * and load factor (0.75).
     */
    public Hashtable() {
        this(11, 0.75f);
    }
    public Hashtable(Map<? extends K, ? extends V> t) {
        this(Math.max(2*t.size(), 11), 0.75f);
        putAll(t);
    }

可以看到,其他构造方法都是调用第一个构造方法实现的。下面看看这个构造方法。

    //构造方法
    //initialCapacity为初始容量,默认大小为 11
    //loadFactor为加载因子,默认大小为0.75f,好比是你的内容超过了 总长度了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);
        
        //如果初始化的时候传入参数为0,那么构建一个长度为1的table
        if (initialCapacity==0)
            initialCapacity = 1;
        this.loadFactor = loadFactor;
        table = new Entry<?,?>[initialCapacity];
        //计算扩容阈值
        threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
    }

成员方法

put(K key, V value)

    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计算位置
        Entry<?,?> tab[] = table;
        int hash = key.hashCode();
        int index = (hash & 0x7FFFFFFF) % tab.length;
        @SuppressWarnings("unchecked")
        Entry<K,V> entry = (Entry<K,V>)tab[index];
        //key存在的时候,在链表中进行查找,然后更新对应的元素
        for(; entry != null ; entry = entry.next) {
            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;
    }

首先看到的是synchronized,上锁,所以可以体现线程安全性。整体实现思想如下:首先根据拿到key的hashcode,然后根据hashCode找到此元素即将要存储的位置index.如果位置index已经有元素链表了,则在此链表中,寻找是否有key已经存在了,如果存在,则更新value值,如果不存在,则将此value存储在index位置上。然后看到了调用了addEntry方法。下面看看这个的源码。

addEntry(int hash, K key, V value, int index) 

     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();
            
            //扩容之后,对key的hashcode、以及即将要存储的位置index重新进行更新
            tab = table;
            hash = key.hashCode();
            index = (hash & 0x7FFFFFFF) % tab.length;
        }

        // Creates the new entry.
        @SuppressWarnings("unchecked")
        //key不存在的时候,直接创建新元素并存入
        Entry<K,V> e = (Entry<K,V>) tab[index];
        tab[index] = new Entry<>(hash, key, value, e);
        //元素个数+1
        count++;
    }

整体思想如下:首先检查下数组table存储的元素个数是否大于扩容门限threshold.如果大于,则扩容,扩容之后,重新获取key的hashcode,并根据hashcode重新计算要存储的位置index.最后将要存储的数据存储到table[index]中。这里要注意的一点是,如果table[index]中已经有其它元素了,那么在同一个位子上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。那么我们来看下rehash方法。

rehash()

    protected void rehash() {
        //原来的大小
        int oldCapacity = table.length;
        //保存原来的数据
        Entry<?,?>[] oldMap = table;

        // overflow-conscious code
        //扩容大小为 *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
        Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];
        
        //修改修改次数
        modCount++;
        //计算阈值
        threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
        //将原来table指向新entry
        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;

                int index = (e.hash & 0x7FFFFFFF) % newCapacity;
                e.next = (Entry<K,V>)newMap[index];
                newMap[index] = e;
            }
        }
    }

实现思想如下:首先对原来的长度乘以2+1就为即将扩容的长度。然后新建一个newCapacity的数组,将原来的table数组的元素拷贝到新的数组中去即可。

内部类Entry

    private static class Entry<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Entry<K,V> next;

        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() {
            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;
        }

        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()));
        }

        public int hashCode() {
            return hash ^ Objects.hashCode(value);
        }

        public String toString() {
            return key.toString()+"="+value.toString();
        }
    }

这是里面的一个内部类。整体对于hashtable的整个维护体现了链表的作用。

get(Object key)

    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)) {
                return (V)e.value;
            }
        }
        return null;
    }

从源码可以看到,实现思路如下,首先获取key的hashcode,然后根据hashcode得到存储位置index,最后在table[index]取出元素即可,不过要注意的是,这里可能存储了多个元素构成了一个链表,因此要进行一个key和hash的判断。

remove(Object key)

该方法用于根据键删除键值对。

    public synchronized V remove(Object key) {
        //保存现有数据变量
        Entry<?,?> tab[] = table;
        //根据key计算位置
        int hash = key.hashCode();
        int index = (hash & 0x7FFFFFFF) % tab.length;
        @SuppressWarnings("unchecked")
        Entry<K,V> e = (Entry<K,V>)tab[index];
        //链表的经典删除操作
        for(Entry<K,V> 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;
    }

其他方法如size,isEmpty,contains,containsValue,containsKey,clear,clone,方法不在一一粘贴。

迭代器部分

由于Hashtable没有实现Iterable接口,所以不能foreach循环遍历其键值,这是因为Hashtable从JDK1.0起就存在了,不过可以使用keys()方法得到键的集合,使用values()得到值的集合。keys()方法的实现如下:

    public synchronized Enumeration<K> keys() {
        return this.<K>getEnumeration(KEYS);
    }
    public Collection<V> values() {
        if (values==null)
            values = Collections.synchronizedCollection(new ValueCollection(),
                                                        this);
        return values;
    }

其中Enumeration是一种类似于Iterator的接口,可以使用该类进行遍历。下面我们看一下getEnumeration方法的源码

     private <T> Enumeration<T> getEnumeration(int type) {
        if (count == 0) {
            return Collections.emptyEnumeration();
        } else {
            return new Enumerator<>(type, false);
        }
    }

可以看到,在哈希表不为空时,返回Enumerator对象,该类既实现了Enumeration接口,也实现了Iterator接口,构造方法中指明了是否使用Iterator接口的方法。源码如下:

    private class Enumerator<T> implements Enumeration<T>, Iterator<T> {
        Entry<?,?>[] table = Hashtable.this.table;
        int index = table.length;
        Entry<?,?> entry;
        Entry<?,?> lastReturned;
        int type;

        /**
         * Indicates whether this Enumerator is serving as an Iterator
         * or an Enumeration.  (true -> Iterator).
         */
        boolean iterator;

        /**
         * The modCount value that the iterator believes that the backing
         * Hashtable should have.  If this expectation is violated, the iterator
         * has detected concurrent modification.
         */
        protected int expectedModCount = modCount;

        Enumerator(int type, boolean iterator) {
            this.type = type;
            this.iterator = iterator;
        }

        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;
                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();

            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];
                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();
            }
        }
    }

Enumeration接口有如下方法

boolean hasMoreElements();
E nextElement();

Iterator接口源码如下:

    boolean hasNext();
    
    E next();

    default void remove() {
        throw new UnsupportedOperationException("remove");
    }

    default void forEachRemaining(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        while (hasNext())
            action.accept(next());
    }

下面两个方法先不用关心,是从jdk1.8开始,标注有@since 1.8。

看到该两个接口基本是一致的。在Enumerator的实现中可以发现,除了remove()方法,Iterator接口的另外两个方法都是使用的Enumeration接口的实现,而remove()方法只有在iterator参数为true时才能使用,否则抛出异常。 在keys()的调用过程中可以发现传入的iterator这个参数为false。在使用values()方法得到值的集合时,iterator参数会为true。由于values()的返回值是一个Collection,必须支持foreach遍历,并且由于Hashtable是线程安全的,所以values使用了Collections.synchronziedCollection()方法对ValueCollection就行了同步封装。而通过values源码可以看到:

    public Collection<V> values() {
        if (values==null)
            values = Collections.synchronizedCollection(new ValueCollection(),
                                                        this);
        return values;
    }

然后我们可以看到如果values为空的时候,通过调用Collections.synchronziedCollection()实现。

     private class ValueCollection extends AbstractCollection<V> {
        public Iterator<V> iterator() {
            return getIterator(VALUES);
        }
        public int size() {
            return count;
        }
        public boolean contains(Object o) {
            return containsValue(o);
        }
        public void clear() {
            Hashtable.this.clear();
        }
    }

源码中iterator方法通过调用内部的getIterator方法实现。源码如下:

    private <T> Iterator<T> getIterator(int type) {
        if (count == 0) {
            return Collections.emptyIterator();
        } else {
            return new Enumerator<>(type, true);
        }
    }

可以看到这时Enumerator的第二个参数为true。

以上就是本次对hashTable整个源码的分析过程,当然还有一些,没有分析到或者分析出来的,以后有机会再进行补充。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值