Hashtable源码阅读

本文详细剖析了Hashtable的内部结构,包括其成员变量、构造器、put方法及rehash机制。通过源码解读,揭示了Hashtable如何实现线程安全及高效的键值对存储。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

我们之前已经阅读过HashMap的源码了,我们知道Hashtable与HashMap都可以用来存储<K,V>键值对,但是HashMap相对高效,而Hashtable可以保证线程安全。那么接下来我们就一起深入Hashtable的源码来一探究竟。

正文

1、Hashtable的成员变量

先介绍下Hashtable的成员变量:

    /**
     * The hash table data.
     */
    private transient Entry<?,?>[] table;

一个由Entry<?,?>组成的数组,来看看Entry:

    /**
     * Hashtable bucket collision list 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();
        }
    }

可以看到,Entry继承自 Map.Entry<K,V>,是一个带有下一个节点指针的键值对,即链表结构。

    /**
     * The total number of entries in the hash table.
     */
    private transient int count;

hashtable中键值对的总数量。

    /**
     * The table is rehashed when its size exceeds this threshold.  (The
     * value of this field is (int)(capacity * loadFactor).)
     *
     * @serial
     */
    private int threshold;

阈值:值=容量*负载因子。当hashtable的大小超过阈值时,键值对数组将进行rehash操作。

    /**
     * The load factor for the hashtable.
     *
     * @serial
     */
    private float loadFactor;

hashtable的负载因子。

    /**
     * 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;

hashtable进行结构性变化的次数。

    /**
     * Each of these fields are initialized to contain an instance of the
     * appropriate view the first time this view is requested.  The views are
     * stateless, so there's no reason to create more than one of each.
     */
    private transient volatile Set<K> keySet;
    private transient volatile Set<Map.Entry<K,V>> entrySet;
    private transient volatile Collection<V> values;

在第一次请求该视图时,这些字段都初始化为包含适当视图的实例。视图是无状态的,因此没有理由创建多个视图。

    // Types of Enumerations/Iterations
    private static final int KEYS = 0;
    private static final int VALUES = 1;
    private static final int ENTRIES = 2;

Enumerations/Iterations的类型。

2、Hashtable的构造器

再来看看Hashtable的构造器:

(1)有参构造器

a、传入初始容量和负载因子作为构造参数:
    /**
     * Constructs a new, empty hashtable with the specified initial
     * capacity and the specified load factor.
     *
     * @param      initialCapacity   the initial capacity of the hashtable.
     * @param      loadFactor        the load factor of the hashtable.
     * @exception  IllegalArgumentException  if the initial capacity is less
     *             than zero, or if the load factor is nonpositive.
     */
    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);
    }

初始容量被用来构建Entry<?,?>数组的实例,而阈值利用初始容量与负载因子被计算出。

b、传入初始容量作为构造参数:
    /**
     * 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);
    }

使用初始容量与默认负载因子0.75来调用构造函数。

c、传入Map对象作为构造参数:
    /**
     * Constructs a new hashtable with the same mappings as the given
     * Map.  The hashtable is created with an initial capacity sufficient to
     * hold the mappings in the given Map and a default load factor (0.75).
     *
     * @param t the map whose mappings are to be placed in this map.
     * @throws NullPointerException if the specified map is null.
     * @since   1.2
     */
    public Hashtable(Map<? extends K, ? extends V> t) {
        this(Math.max(2*t.size(), 11), 0.75f);
        putAll(t);
    }

利用Map参数的大小计算出容量,加上默认负载因子0.75调用构造函数。
再调用putAll方法:

    /**
     * Copies all of the mappings from the specified map to this hashtable.
     * These mappings will replace any mappings that this hashtable had for any
     * of the keys currently in the specified map.
     *
     * @param t mappings to be stored in this map
     * @throws NullPointerException if the specified map is null
     * @since 1.2
     */
    public synchronized void putAll(Map<? extends K, ? extends V> t) {
        for (Map.Entry<? extends K, ? extends V> e : t.entrySet())
            put(e.getKey(), e.getValue());
    }

此处通过遍历Map对象的Entry集合来获取单个Entry对象,再调用put方法放入Entry对象。

(2)无参构造器

无参构造器:

    /**
     * Constructs a new, empty hashtable with a default initial capacity (11)
     * and load factor (0.75).
     */
    public Hashtable() {
        this(11, 0.75f);
    }

使用默认的初始容量11与默认负载因子0.75来调用构造函数。

3、put方法与rehash

上面讲到put方法,我们来看看:

    /**
     * Maps the specified <code>key</code> to the specified
     * <code>value</code> in this hashtable. Neither the key nor the
     * value can be <code>null</code>. <p>
     *
     * The value can be retrieved by calling the <code>get</code> method
     * with a key that is equal to the original key.
     *
     * @param      key     the hashtable key
     * @param      value   the value
     * @return     the previous value of the specified key in this hashtable,
     *             or <code>null</code> if it did not have one
     * @exception  NullPointerException  if the key or value is
     *               <code>null</code>
     * @see     Object#equals(Object)
     * @see     #get(Object)
     */
    public synchronized V put(K key, V value) {
        // Make sure the value is not null
        if (value == null) {
            throw new NullPointerException();
        }

        // Makes sure the key is not already in the hashtable.
        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;
    }

首先,put方法对传入的value进行判空,为空抛空指针异常。
接着,利用key的hash值计算出目标键值对的索引位置((hash & 0x7FFFFFFF) % tab.length),将目标键值与key进行hash比较和equals比较,如果比较结果相等,就直接返回原键值对的value。
如果key值还不在hashtable中,则调用addEntry方法,在Entry数组中新增一个Entry对象。

addEntry方法,我们来看看:

    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;
            hash = key.hashCode();
            index = (hash & 0x7FFFFFFF) % tab.length;
        }

        // Creates the new entry.
        @SuppressWarnings("unchecked")
        Entry<K,V> e = (Entry<K,V>) tab[index];
        tab[index] = new Entry<>(hash, key, value, e);
        count++;
    }

首先将modCount自增加1;
接着,判断Hashtable中键值对的总数是否超过阈值,
如果判断不成立,则将新的Entry放在链表的头节点。
注意:这里表明出现hash碰撞时,最先放入的节点在链表末端,而最后放入的节点则在链表表头。
如果判断成立,就进行rehash操作:

    /**
     * Increases the capacity of and internally reorganizes this
     * hashtable, in order to accommodate and access its entries more
     * efficiently.  This method is called automatically when the
     * number of keys in the hashtable exceeds this hashtable's capacity
     * and load factor.
     */
    @SuppressWarnings("unchecked")
    protected void rehash() {
        int oldCapacity = table.length;
        Entry<?,?>[] oldMap = table;

        // overflow-conscious code
        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;
                e.next = (Entry<K,V>)newMap[index];
                newMap[index] = e;
            }
        }
    }

为了更高效地容纳和访问Hashtable中的键值对,增加容量并进行hashtable的内部调整。当hashtable中key的数量超过容量与负载因子的乘积时,该方法会自动被调用。

首先,将原容量的2倍加1作为新容量,(最大为Integer.MAX_VALUE - 8)
其次,利用新容量构建出新的Entry数组,modCount自增加1后,利用新容量计算出新的阈值;
接着,将Hashtable的Entry数组指向新的Entry数组;
最后,通过遍历原Entry数组中的键值对,将原Entry对象转移到新的Entry数组上。

4、测试验证

建立测试案例如下;

    @Test
    public void test(){
        Map<String,Object> hashtable = new Hashtable<>();
        hashtable.put("a","");
        hashtable.put("b","");
        hashtable.put("c","");
        hashtable.put("d","");
        hashtable.put("Aa","");
        hashtable.put("BB","");
        hashtable.put("Ab","");
        hashtable.put("BC","");
        hashtable.put("Ac","");
        hashtable.put("BD","");
        hashtable.put("Ad","");
        hashtable.put("BE","");
    }

经过debug调试得知,Hashtable的rehash过程可以分为两个维度来总结:
从链表数组的维度看,Hashtable是从数组中索引最大的节点开始以由大到小的顺序来进行重新调整位置。
从链表数组的某个元素来看,若当前位置上有多个节点组成链表,则沿着链表从头到尾地依次进行位置的重新调整。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值