HashTable类源码详解

一、源码分析

1.继承的父类&实现的接口

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

2.Entry节点类

   /**
     * Hashtable bucket collision list entry
     * Entry节点类
     */
    private static class Entry<K,V> implements Map.Entry<K,V> {
        final int hash;//当前节点的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;
        }

        //重写Object类中的equals()方法
        //由原来默认“==”比较引用数据类型地址引用的方式,
        //改为现在比较对象的属性中的具体内容
        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()方法
        public int hashCode() {
            return hash ^ Objects.hashCode(value);
        }

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

3.重要属性

/**
 * The hash table data.
 * 哈希表的数组部分
 */
private transient Entry<?,?>[] table;

/**
 * The total number of entries in the hash table.
 * 哈希表table中的entry个数。
 */
private transient int count;

/**
 * The table is rehashed when its size exceeds this threshold.  (The
 * value of this field is (int)(capacity * loadFactor).)
 *
 * 当表的大小超过这个扩容阈值时,表将被重新散列。
 * (threshold = (int)(capacity * loadFactor)。
 */
private int threshold;

/**
 * The load factor for the hashtable.
 * 哈希表的加载因子。
 */
private float loadFactor;

/**
 * The number of times this Hashtable has been structurally modified
 * Structural modifications are those that change the number of entries in
 * the Hashtable or otherwise modify its internal structure (e.g.,
 * rehash).  This field is used to make iterators on Collection-views of
 * the Hashtable fail-fast.  (See ConcurrentModificationException).
 */
private transient int modCount = 0;

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

4.构造方法

1>.无参构造

/**
     * Constructs a new, empty hashtable with a default initial capacity (11)
     * and load factor (0.75).
     * 构造一个空哈希表,
     * 具有默认的初始容量(11)和负载因子(0.75)。
     */
    public Hashtable() {
        this(11, 0.75f);
    }

2>.有参构造

1.Hashtable(int initialCapacity, float loadFactor)方法:
//构造一个空哈希表,具有指定的初始容量和指定的负载因子。
    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;
        //然后根据 初始容量initialCapacity 初始化table数组。
        //而HashMap中,构造函数中不做数组初始化处理,
        //是在第一次put元素时,在扩容方法resize()中new table数组。
        table = new Entry<?,?>[initialCapacity];
        //扩容阈值threshold = initialCapacity(table的长度) * loadFactor(负载因子)
        threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
    }
2.Hashtable(int initialCapacity)方法:
//使用指定的初始容量和默认加载因子(0.75)构造一个新的空哈希表。
    public Hashtable(int initialCapacity) {
        this(initialCapacity, 0.75f);//利用上面的构造方法
    }

3>.利用集合构造集合

//利用集合构造集合
    public Hashtable(Map<? extends K, ? extends V> t) {
        this(Math.max(2*t.size(), 11), 0.75f);
        putAll(t);
    }

5.常用方法

1>.增加put(K key, V value)方法:

//添加:将键值对添加到哈希表中。
    //key重复则执行value值的更新,key不存在则正常添此加键值对。
    public synchronized V put(K key, V value) {
        // Make sure the value is not null
        if (value == null) {
            //value不允许为null,null时会抛出异常
            throw new NullPointerException();
        }

        // Makes sure the key is not already in the hashtable.
        Entry<?,?> tab[] = table;
        int hash = key.hashCode();
        //计算此key的索引值index
        int index = (hash & 0x7FFFFFFF) % tab.length;
        @SuppressWarnings("unchecked")
        Entry<K,V> entry = (Entry<K,V>)tab[index];//获得该桶中的头节点
        //依次遍历此hash值链表中的节点
        for(; entry != null ; entry = entry.next) {
            //比对是否有 同hash 同key 的重复节点
            if ((entry.hash == hash) && entry.key.equals(key)) {
                V old = entry.value;
                //如果存在重复的key,则进行值覆盖
                entry.value = value;
                return old;
            }
        }

        //如果此key不是重复元素,则调用函数进行节点添加
        addEntry(hash, key, value, index);
        return null;
    }


	//put()方法的具体实现
    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();//则执行扩容操作

            tab = table;//tab:记录扩容后的数组引用
            hash = key.hashCode();//更新key的hash值
            index = (hash & 0x7FFFFFFF) % tab.length;//更新索引下标
        }

        // Creates the new entry.
        @SuppressWarnings("unchecked")
        Entry<K,V> e = (Entry<K,V>) tab[index];//e:此桶中的头节点
        //新节点作为头节点,此处选择的是头插法
        tab[index] = new Entry<>(hash, key, value, e);
        count++;
    }

2>.删除remove(Object key)方法:

	//删除:根据key删除对应节点
    public synchronized V remove(Object key) {
        Entry<?,?> tab[] = table;
        int hash = key.hashCode();
        //index:记录此key的索引值
        int index = (hash & 0x7FFFFFFF) % tab.length;
        @SuppressWarnings("unchecked")
        Entry<K,V> e = (Entry<K,V>)tab[index];//e:记录此索引值的头节点
        //通过循环遍历此链表后面的节点
        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;
    }

3>.查找get(Object key)方法:

	//查找:根据key获得value
    public synchronized V get(Object key) {
        Entry<?,?> tab[] = table;
        //hash:用于计算key的hash值
        //这里和hashMap的计算方式不一样,详情看HashMap.hash()方法
        int hash = key.hashCode();
        //index:用于记录存储位置的数组下标
        //计算方式:(hash值) & (31个1) ,然后对表长取余作为数组下标
        //这里和hashMap的计算方式有区别,详情看HashMap.put()方法
        int index = (hash & 0x7FFFFFFF) % tab.length;
        //根据数组下标index,循环遍历此hash值链表中的节点
        for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
            //如果hash值和key都比对成功,则返回此key对应的value值
            if ((e.hash == hash) && e.key.equals(key)) {
                return (V)e.value;
            }
        }
        return null;
    }

6.扩容方法

 	//扩容方法
    protected void rehash() {
        int oldCapacity = table.length;//记录原数组长度
        Entry<?,?>[] oldMap = table;//记录原数组的地址索引

        // overflow-conscious code
        //新数组长度 = 原数组的2倍+1
        //而HashMap中,扩容为原来的2倍:newCap = (oldCap << 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;
        }
        //以新长度newCapacity创建一个新数组
        Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];

        modCount++;
        //更新 扩容阈值threshold
        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;

                //以同样的方式,通过对新数组长度取余的方式计算新索引index
                int index = (e.hash & 0x7FFFFFFF) % newCapacity;
                e.next = (Entry<K,V>)newMap[index];
                newMap[index] = e;//将数据存放到新数组中
            }
        }
    }

二、HashTable和HashMap的异同

1.相同点

HashTable和HashMap的存储结构相同,都是用的哈希表(数组+链表)。

JDK1.7中,HashMap用的是数组+链表。

JDK1.8中,HashMap用的是数组+链表/红黑树。

2.不同点

1>.继承的父类不同(实现的接口相同)

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

2>.线程安全问题

HashTable中的大多数方法上都加了synchronized关键字进行修饰,用于保证线程安全,但效率较低。

而HashMap中的方法上,没有synchronized关键字,不能保证线程安全,但效率较高。

3>.对null的支持不同

HashTable中不允许null值(key和value都不可以)。

HashMap中允许null值(key和value都可以)。但是key=null的键只允许存在一个,value=null可以存在多个。

说明:

HashTable中的put()方法,首先通过if判断value是否为null,若为空,则直接抛出空指针异常。

key也不允许为null(因为调用key.hashCode()方法时,若key = null,也会造成空指针异常)。

而在HashMap中,允许value=null,也允许key = null。HashMap.hash()方法中,当key=null时,hash()方法返回的值是0。

4>.初始化不同

1.容量大小不同:

HashTable的无参构造方法中,默认的初始容量是11,负载因子是0.75。

而HashMap中的默认初始容量是16,负载因子是0.75。

2.初始化的位置不同:

HashTable直接在构造方法 Hashtable(int initialCapacity, float loadFactor)中,通过new关键字创建了数组table。

而HashMap中的构造方法,只做全局变量的值传递,不做数组的初始化处理。真正的数组初始化,是在第一次put()值时,通过调用扩容方法resize()对数组进行初始化判断时创建。

5>.扩容大小不同

HashMap中,每次扩容为原来的2倍(resize()方法中,newCap = oldCpa<<1;)。

HashTable中,每次扩容为原来的2倍+1(rehash()方法中,newCapacity = (oldCapacity<<1)+1;)。

6>.hash值和下标的计算

1.hash值的计算

HashMap中的hash()方法:

HashMap.hash()方法中,会通过无符号右移 >>> 16 位的方式,让高16位也参与到最终的下标运算中去。

	//经过计算,返回 key的hash值
    static final int hash(Object key) {
        int h;
        //如果key!=null,借助hashCode()方法,计算key的hash值。
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

而在HashTable中:int hash = key.hashCode();

2.下标的计算方式(相同点:都是利用hash值对表长取余)

HashTable.put()方法中:

//获得 key的hash值
int hash = key.hashCode();
//计算此 key的索引值index
int index = (hash & 0x7FFFFFFF) % tab.length;

HashMap.put()的putVal()方法中,下标 : index = (table.length - 1) & hash;

7>.添加元素的方式

HashTable中用的是头插法(详情看HashTable.addEntry()方法)。

Entry<K,V> e = (Entry<K,V>) tab[index];//e:此桶中的头节点
//将新加节点作为头节点,此处选择的是头插法
tab[index] = new Entry<>(hash, key, value, e);

而HashMap中,JDK1.7用的是头插法,JDK1.8用的是尾插法(详情看HashMap.putVal()方法)。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值