Hashtable源码

1.Entry结构

  HashMap的节点结构叫Node(JDK1.8,JDK1.8以前也叫Entry),Hashtable为Entry,都包含了四个相同的字段。

private static class Entry<K,V> implements Map.Entry<K,V> {
        //hash值
        final int hash;
        //键值
        final K key;
        //value
        V value;
        //下一个Entry的引用
        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;
        }

        /**
         * equal方法
         * @param o
         * @return
         */
        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方法
         * @return
         */
        public int hashCode() {
            return hash ^ Objects.hashCode(value);
        }

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

2.字段和常量

  Hashtable同样使用数组作为哈希表,有加载因子和阈值

/**
     * 哈希表,数组实现,存储数据
     */
    private transient Entry<?,?>[] table;

    /**
     * 哈希表中实际存储对象的个数
     */
    private transient int count;

    /**
     * 阈值,容量*加载因子
     */
    private int threshold;

    /**
     * 加载因子
     */
    private float loadFactor;

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

    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    private static final long serialVersionUID = 1421746759512286392L;

/**
     * 最大数组大小
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

3.构造函数

  Hashtable默认初始容量是11,加载因子0.75,容量不用是2的整数次幂

 /**
     * 带有初始容量和加载因子的构造函数
     */
    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
        if (initialCapacity==0)
            initialCapacity = 1;
        this.loadFactor = loadFactor;
        //创建哈希表
        table = new Entry<?,?>[initialCapacity];
        //设置阈值
        threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
    }

    /**
     * 带有初始容量的构造函数,加载因子使用默认的值
     */
    public Hashtable(int initialCapacity) {
        this(initialCapacity, 0.75f);
    }

    /**
     * 无参构造函数,创建一个初始容量为11,加载因子为0.75的哈希表
     */
    public Hashtable() {
        this(11, 0.75f);
    }

    /**
     * 根据指定的Map创建哈希表
     */
    public Hashtable(Map<? extends K, ? extends V> t) {
        //map中对象个数的2倍与11比较,选取大的那个数作为初始容量
        this(Math.max(2*t.size(), 11), 0.75f);
        //将map中的元素加入到哈希表
        putAll(t);
    }

4.获取方法

(1)计算key的hash值时直接使用的key的hashcode

(2)计算索引时先将hash与0x7FFFFFFF做&运算,再对哈希表长度取余数。hash&0x7FFFFFFF是为了将负的hash转为正值。

 /**
     * 根据key获取对象
     */
    @SuppressWarnings("unchecked")
    public synchronized V get(Object key) {
        Entry<?,?> tab[] = table;
        //计算hash
        int hash = key.hashCode();
        //计算索引
        int index = (hash & 0x7FFFFFFF) % tab.length;
        //定位到index索引处,遍历链表寻找对象
        for (Entry<?,?> e = tab[index]; e != null ; e = e.next) {
            if ((e.hash == hash) && e.key.equals(key)) {
                return (V)e.value;
            }
        }
        return null;
    }

5.添加方法

(1)hashtable添加对象时value不能为null,如果为null抛出异常。

(2)添加对象时首先会判断是否超过阈值,如果超过调用rehash方法对哈希表扩容。扩容时新容量为原来容量的2倍+1,并且会重写计算每一个节点在新哈希表中的索引,产生哈希冲突时,使用头插法插入链表,因此链表内容会倒置。

(3)在addEntry方法中可以看出,添加新节点时产生哈希冲突同样使用头插法插入链表。

(4)如果已经存在键值为key的对象,新的value会覆盖旧的value。

  /**
     * 调整哈希表大小
     */
    @SuppressWarnings("unchecked")
    protected void rehash() {
        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)
                // 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;
        //遍历旧的哈希表
        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;
                //使用头插法将节点插入到index处
                e.next = (Entry<K,V>)newMap[index];
                newMap[index] = e;
            }
        }
    }

    /**
     * 添加Entry
     * @param hash
     * @param key
     * @param value
     * @param index
     */
    private void addEntry(int hash, K key, V value, int index) {
        //修改次数
        modCount++;

        Entry<?,?> tab[] = table;
        //如果哈希表中容量超过阈值
        if (count >= threshold) {
            // 扩容,创建新的哈希表
            rehash();

            tab = table;
            //根据key重新计算hash和索引
            hash = key.hashCode();
            index = (hash & 0x7FFFFFFF) % tab.length;
        }

        // Creates the new entry.
        @SuppressWarnings("unchecked")
        Entry<K,V> e = (Entry<K,V>) tab[index];//获取index处的元素
        //创建新节点,next指向原来index处的对象e,然后将新节点放到index位置,可以看出使用的是头插法插入链表
        tab[index] = new Entry<>(hash, key, value, e);
        count++;
    }

    /**
     * 添加
     */
    public synchronized V put(K key, V value) {
        // 如果值为null抛出异常
        if (value == null) {
            throw new NullPointerException();
        }
        Entry<?,?> tab[] = table;
        int hash = key.hashCode();
        //计算索引
        int index = (hash & 0x7FFFFFFF) % tab.length;
        @SuppressWarnings("unchecked")
        Entry<K,V> entry = (Entry<K,V>)tab[index];
        //从指定的索引处查找对象
        for(; entry != null ; entry = entry.next) {
            //如果找到,新值覆盖旧值
            if ((entry.hash == hash) && entry.key.equals(key)) {
                V old = entry.value;
                entry.value = value;
                return old;
            }
        }
        //添加新节点
        addEntry(hash, key, value, index);
        return null;
    }

  /**
     * 将map中的元素添加到哈希表
     */
    public synchronized void putAll(Map<? extends K, ? extends V> t) {
        //遍历map
        for (Map.Entry<? extends K, ? extends V> e : t.entrySet())
            //添加节点
            put(e.getKey(), e.getValue());
    }

6.删除

/**
     * 根据指定的key删除对象
     */
    public synchronized V remove(Object key) {
        Entry<?,?> tab[] = table;
        //计算hash和索引
        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++;
                //如果要删除的节点不是头结点,将要删除节点的前一个节点的next指向要删除节点的下一个节点
                if (prev != null) {
                    prev.next = e.next;
                } else {//否则将要删除节点的下一个节点作为头结点
                    tab[index] = e.next;
                }
                //更改大小
                count--;
                V oldValue = e.value;
                //删除节点
                e.value = null;
                //返回要删除节点的值
                return oldValue;
            }
        }
        return null;
    }

   /**
     * 清空
     */
    public synchronized void clear() {

        Entry<?,?> tab[] = table;
        modCount++;
        //遍历哈希表,将数组清空
        for (int index = tab.length; --index >= 0; )
            tab[index] = null;
        count = 0;
    }


Hashtable与HashMap不同点:

1. Hashtable默认容量为11,并且容量不需要是2的整数次幂,HashMap默认容量为16,容量需要是2的整数次幂。

2. Hashtable的key和value不允许为null,但是从源码中未看到对key做null判断,如果value为空运行时会抛出异常。

    HashMap的key和value允许为null,key为null时hash值为0。

3. Hashtable扩容时,新容量为原来的2倍+1,HashMap扩容时新容量为原来的2倍。

4. Hashtable计算key的hash值时直接使用key的hashcode,计算索引时hash&0x7FFFFFFF后对length-1取余数。

    HashMap计算key的hash值时将hashcode的低16位与高16位做异或,计算索引时hash值对length-1做&运算。(JDK1.8)

5. Hashtable使用链表解决哈希冲突,冲突时使用头插法插入链表。

    HashtMap使用链表+红黑树的结构解决冲突,用尾插法插入链表。(JDK1.8)

6. Hashtable扩容时会重新计算每个节点的索引,而且使用头插法链表会倒置。

    HashMap扩容时进行了优化,每个节点的索引要么不变要么是原来索引+哈希表容量,并且链表不会倒置。(JDK1.8)

7. Hashtable添加、删除等方法使用了synchronized,是线程安全的,HashMap是线程不安全的。

相同点:

1. Hashtable与HashMap都使用数组实现,Hashtable的存储结构Entry与HashMap的存储结构Node虽然类名不同,但是字段相同,都包含了hash、key、value和next。

2.Hashtable和HashMap都实现类Serializable接口,支持序列化,实现了Cloneable,可以被克隆。


参考:【Java集合源码剖析】Hashtable源码剖析

猜你喜欢

转载自blog.csdn.net/lom9357bye/article/details/79832296