JDK之HashTable源码解析

15 篇文章 0 订阅
13 篇文章 0 订阅

刚入java不久的程序猿,对于简单的使用已毫不满足,最终为了一探究竟,翻开了JDK的源码,以下观点为自己的理解及看了多篇博客的总结,欢迎各位大神指出不对的地方,当然也欢迎和我一样刚学的同学,一起加油努力吧~~

Hashtable是什么

Hashtable和HashMap一样是一个集合,不过不同于HashMap的是,HashMap允许null键与null值,而Hashtable不允许,HashMap是线程不安全的,而Hashtable是线程安全的,由于线程安全性问题,HashMap相对于Hashtable效率会更高一些

Hashtable源码解析

由于之前楼主也没看过Hashtable的源码,所以在这里我们就一起来学习一下吧,按照以往惯例,先了解下整体的结构

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

在这里,我们看到Hashtable继承自Dictionary这个字典类,并实现了Map,Cloneable,Serializable接口,具体的这些接口作用以及Map接口里的方法已在之前的HashMap解析中介绍过,没看过的同学可以点击链接JDK之HashMap源码解析,下面着重看下Dictionary这个抽象类中的方法

public abstract
class Dictionary<K,V> {
    /**
     * 一个空的构造方法
     */
    public Dictionary() {
    }

    /**
     * 一个用于计算长度的抽象方法
     */
    abstract public int size();

    /**
     * 一个用于判断是否为空的抽象方法
     */
    abstract public boolean isEmpty();

    /**
     * 一个用于取出key的抽象方法,取出类型为枚举
     */
    abstract public Enumeration<K> keys();

    /**
     * 一个用于取出value的抽象方法,取出类型为枚举
     */
    abstract public Enumeration<V> elements();

    /**
     * 根据key值取出value
     */
    abstract public V get(Object key);

    /**
     * 以key对应value的形式存放值
     */
    abstract public V put(K key, V value);

    /**
     * 根据key值移除某个元素
     */
    abstract public V remove(Object key);
}

这里我给每个方法都标了注释,由于这个类是抽象类,当继承这个类需实现其抽象方法,然而我们发现这个类中除了构造方法外都是抽象方法,这样在Hashtable是其子类需实现这些方法,接下来我们来看Hashtable这个类,先来看看变量


    /**
     * 存储数据的table数组
     */
    private transient Entry<K,V>[] table;

    /**
     * Hashtable中元素的总数
     */
    private transient int count;

    /**
     * 阈值
     */
    private int threshold;

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

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

    /** 版本ID号 */
    private static final long serialVersionUID = 1421746759512286392L;

    /**
     * 容量阈值,大小为Integer的最大容量
     */
    static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;

这里对Hashtable的变量进行了相应的注释,下面我们继续往下看

    /**
     * 静态内部类Holder,调用该类时初始化值
     */
    private static class Holder {

        /**
         * 容量阈值,初始化hashSeed时候使用
         */
        static final int ALTERNATIVE_HASHING_THRESHOLD;

        static {
            /**
             * 获取系统变量jdk.map.althashing.threshold
             */
            String altThreshold = java.security.AccessController.doPrivileged(
                new sun.security.action.GetPropertyAction(
                    "jdk.map.althashing.threshold"));

            int threshold;
            try {
                /**
                 * 判断altThreshold是否为空,并为阈值赋值
                 */
                threshold = (null != altThreshold)
                        ? Integer.parseInt(altThreshold)
                        : ALTERNATIVE_HASHING_THRESHOLD_DEFAULT;

                // 如果该阈值为-1,则将Integer最大值赋予阈值
                if (threshold == -1) {
                    threshold = Integer.MAX_VALUE;
                }
                //当阈值小于0时,抛出IllegalArgumentException异常
                if (threshold < 0) {
                    throw new IllegalArgumentException("value must be positive integer.");
                }
            } catch(IllegalArgumentException failed) {
                throw new Error("Illegal value for 'jdk.map.althashing.threshold'", failed);
            }

            ALTERNATIVE_HASHING_THRESHOLD = threshold;
        }
    }

    /**
     * 后面计算hash值时用到
     */
    transient int hashSeed;

    /**
     * 初始化hashseed
     */
    final boolean initHashSeedAsNeeded(int capacity) {
        boolean currentAltHashing = hashSeed != 0;
        boolean useAltHashing = sun.misc.VM.isBooted() &&
                (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
        boolean switching = currentAltHashing ^ useAltHashing;
        if (switching) {
            hashSeed = useAltHashing
                ? sun.misc.Hashing.randomHashSeed(this)
                : 0;
        }
        return switching;
    }
    /**
     * 根据key值和hashseed计算哈希值
     */
    private int hash(Object k) {
        return hashSeed ^ k.hashCode();
    }

    /**
     * 返回hashCode的值
     */
    public synchronized int hashCode() {
        int h = 0;
        if (count == 0 || loadFactor < 0)
            return h;  // 返回0

        loadFactor = -loadFactor;  //标记计算过程中的hashCode
        Entry[] tab = table;
        for (Entry<K,V> entry : tab)
            while (entry != null) {
                h += entry.hashCode();
                entry = entry.next;
            }
        loadFactor = -loadFactor;  //标记完成后的hashCode
        //返回哈希值
        return h;
    }

这里和HashMap中一样,就不多做解释了,继续看代码,Hashtable中几个构造方法

    /**
     * @param  initialCapacity 初始化容量大小(但并不是真正初始化大小)
     * @param  loadFactor      加载因子
     * @throws IllegalArgumentException 抛出IllegalArgumentException异常
     */
    public Hashtable(int initialCapacity, float loadFactor) {
        //当初始化容量大小小于0时,抛出IllegalArgumentException异常
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        //当加载因子小于0或者为无效数字时,抛出IllegalArgumentException异常                               
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal Load: "+loadFactor);
        //当初始化容量等于0时赋值
        if (initialCapacity==0)
            initialCapacity = 1;
        //加载因子     
        this.loadFactor = loadFactor;
        //初始化table
        table = new Entry[initialCapacity];
        //阈值
        threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
        //初始化hashseed
        initHashSeedAsNeeded(initialCapacity);
    }

    /**
     * @param  initialCapacity 初始化容量
     * @throws IllegalArgumentException 抛出IllegalArgumentException异常
     * 调用HashMap重载的构造方法,参数分别为初始化容量和加载因子0.75f
     */
    public Hashtable(int initialCapacity) {
        this(initialCapacity, 0.75f);
    }

    /**
     * 调用HashMap重载的构造方法,参数分别为初始化容量11和加载因子0.75f
     */
    public Hashtable() {
        this(11, 0.75f);
    }

    /**
     * @param   Map对象
     * this调用相应构造方法,传入初始化容量Math.max(2*t.size()和0.75f)
     */
    public Hashtable(Map<? extends K, ? extends V> t) {
        this(Math.max(2*t.size(), 11), 0.75f);
        putAll(t);
    }

看到这里我们发现基本上和HashMap差不多,毕竟都是以键值对的形式存在的,好了,下面看一下从父类继承过来的抽象方法和Map接口中的方法

    /**
     * 返回Hashtable中元素的数量
     */
    public synchronized int size() {
        return count;
    }

    /**
     * 判断Hashtable是否为空
     */
    public synchronized boolean isEmpty() {
        return count == 0;
    }

    /**
     * 获取HashTable中所有的key
     */
    public synchronized Enumeration<K> keys() {
        return this.<K>getEnumeration(KEYS);
    }

    /**
     * 具体的获取key的方法,参数为传入类型0、1、2,分别代表key,value,entries
     */
    private <T> Enumeration<T> getEnumeration(int type) {
        //当HashTable里无元素时,返回空的枚举集合,否则返回一个新建的
        if (count == 0) {
            return Collections.emptyEnumeration();
        } else {
            return new Enumerator<>(type, false);
        }
    }

   /**
     * 获取HashTable中所有的value
     */
    public synchronized Enumeration<V> elements() {
        return this.<V>getEnumeration(VALUES);
    }

    /**
     * 判断某个值是否在HashTable中存在的具体方法
     */
    public synchronized boolean contains(Object value) {
        if (value == null) {
            throw new NullPointerException();
        }

        Entry tab[] = table;
        //遍历判断
        for (int i = tab.length ; i-- > 0 ;) {
            for (Entry<K,V> e = tab[i] ; e != null ; e = e.next) {
                if (e.value.equals(value)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 判断某个值是否在HashTable中存在
     */
    public boolean containsValue(Object value) {
        return contains(value);
    }
    /**
     * 判断某个key是否在HashTable中存在
     */
    public synchronized boolean containsKey(Object key) {
        Entry tab[] = table;
        //计算哈希值
        int hash = hash(key);
        //计算在数组中的位置
        int index = (hash & 0x7FFFFFFF) % tab.length;
        for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
            //判断是否存在该key
            if ((e.hash == hash) && e.key.equals(key)) {
                return true;
            }
        }
        return false;
    }

这里先列出了部分方法,相应的地方也做了注释,刚刚我们说到HashTable相对于HashMap是线程安全的,原因就在方法上,我们可以看到每个用到的方法上都加了锁synchronized,HashTable就是因为有锁所以线程是安全的,当然牺牲了部分的效率问题,接下来我们来看几个常用的方法

    /**
     * 被允许的最大值为Integer的最大值减去8
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    /**
     * 根据key值获取元素value
     */
    public synchronized V get(Object key) {
        Entry tab[] = table;
        //计算哈希值
        int hash = hash(key);
        //计算在数组中的位置
        int index = (hash & 0x7FFFFFFF) % tab.length;
        for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
            if ((e.hash == hash) && e.key.equals(key)) {
                return e.value;
            }
        }
        return null;
    }

    /**
     * 存放键值对key,value
     * HashTable键值不能为空
     */
    public synchronized V put(K key, V value) {
        //值不能为空,否则抛空指针异常
        if (value == null) {
            throw new NullPointerException();
        }

        // 确认HashTable中,有无这个key
        Entry tab[] = table;
        int hash = hash(key);
        int index = (hash & 0x7FFFFFFF) % tab.length;
        for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
            //如果有的话,替换值,并返回旧值
            if ((e.hash == hash) && e.key.equals(key)) {
                V old = e.value;
                e.value = value;
                return old;
            }
        }
        //修改次数+1
        modCount++;
        //如果元素数量大于阈值,重新计算哈希值
        if (count >= threshold) {
            //扩容并重新计算哈希值
            rehash();

            tab = table;
            hash = hash(key);
            index = (hash & 0x7FFFFFFF) % tab.length;
        }

        //创建新的entry
        Entry<K,V> e = tab[index];
        //放到数组中
        tab[index] = new Entry<>(hash, key, value, e);
        //元素数量+1
        count++;
        return null;
    }

    /**
     * 扩容并重新计算哈希值
     */
    protected void rehash() {
        //HashTable旧的容量
        int oldCapacity = table.length;
        Entry<K,V>[] oldMap = table;

        //新的容量,为原大小的一倍加1
        int newCapacity = (oldCapacity << 1) + 1;
        //判断新容量是否超出的最大值,如超出了更改为最大值
        if (newCapacity - MAX_ARRAY_SIZE > 0) {
            if (oldCapacity == MAX_ARRAY_SIZE)
                //如果旧容量已经是最大值了返回
                return;
            newCapacity = MAX_ARRAY_SIZE;
        }
        //用新的容量创建的数组
        Entry<K,V>[] newMap = new Entry[newCapacity];
        //修改次数+1
        modCount++;
        //重新计算阈值
        threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
        //根据新的容量重新初始化hashseed
        boolean rehash = initHashSeedAsNeeded(newCapacity);

        table = newMap;
        //循环遍历旧的元素
        for (int i = oldCapacity ; i-- > 0 ;) {
            for (Entry<K,V> old = oldMap[i] ; old != null ; ) {
                Entry<K,V> e = old;
                old = old.next;

                if (rehash) {
                    //重新计算哈希值
                    e.hash = hash(e.key);
                }
                //重新计算位置
                int index = (e.hash & 0x7FFFFFFF) % newCapacity;
                //设置元素
                e.next = newMap[index];
                //放入元素
                newMap[index] = e;
            }
        }
    }

    /**
     * 根据key值移除某个元素
     */
    public synchronized V remove(Object key) {
        Entry tab[] = table;
        //计算哈希值
        int hash = hash(key);
        //计算在数组中位置
        int index = (hash & 0x7FFFFFFF) % tab.length;
        //遍历找到该元素
        for (Entry<K,V> e = tab[index], prev = null ; e != null ; prev = e, e = e.next) {
            if ((e.hash == hash) && e.key.equals(key)) {
                //修改次数+1
                modCount++;
                //判断不是第一位,则将元素移动
                if (prev != null) {
                    prev.next = e.next;
                } else {
                    tab[index] = e.next;
                }
                //元素数量-1
                count--;
                //将该元素置空,并返回旧的值
                V oldValue = e.value;
                e.value = null;
                return oldValue;
            }
        }
        return null;
    }

这里我们看完了几个常用的方法,看了源码,想必大家都知道为什么HashTable不能存放空值空键了,最后还剩一些方法,大致说明下

    /**
     * 将所有元素放入HashTable,循环遍历调用put方法
     */
    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());
    }

    /**
     * 清空HashTable,数组置空,元素个数置0
     */
    public synchronized void clear() {
        Entry tab[] = table;
        modCount++;
        for (int index = tab.length; --index >= 0; )
            tab[index] = null;
        count = 0;
    }

    /**
     * 克隆方法
     */
    public synchronized Object clone() {
        try {
            Hashtable<K,V> t = (Hashtable<K,V>) super.clone();
            t.table = new Entry[table.length];
            for (int i = table.length ; i-- > 0 ; ) {
                t.table[i] = (table[i] != null)
                    ? (Entry<K,V>) table[i].clone() : null;
            }
            t.keySet = null;
            t.entrySet = null;
            t.values = null;
            t.modCount = 0;
            return t;
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError();
        }
    }

    /**
     * toString方法,控制台输出调用
     */
    public synchronized String toString() {
        int max = size() - 1;
        if (max == -1)
            return "{}";

        StringBuilder sb = new StringBuilder();
        Iterator<Map.Entry<K,V>> it = entrySet().iterator();

        sb.append('{');
        for (int i = 0; ; i++) {
            Map.Entry<K,V> e = it.next();
            K key = e.getKey();
            V value = e.getValue();
            sb.append(key   == this ? "(this Map)" : key.toString());
            sb.append('=');
            sb.append(value == this ? "(this Map)" : value.toString());

            if (i == max)
                return sb.append('}').toString();
            sb.append(", ");
        }
    }

    /**
     * 根据类型转换为迭代器
     * /
    private <T> Iterator<T> getIterator(int type) {
        if (count == 0) {
            return Collections.emptyIterator();
        } else {
            return new Enumerator<>(type, true);
        }
    }

    /**
     * 比较值是否相等
     */
    public synchronized boolean equals(Object o) {
        if (o == this)
            return true;

        if (!(o instanceof Map))
            return false;
        Map<K,V> t = (Map<K,V>) o;
        if (t.size() != size())
            return false;

        try {
            Iterator<Map.Entry<K,V>> i = entrySet().iterator();
            while (i.hasNext()) {
                Map.Entry<K,V> e = i.next();
                K key = e.getKey();
                V value = e.getValue();
                if (value == null) {
                    if (!(t.get(key)==null && t.containsKey(key)))
                        return false;
                } else {
                    if (!value.equals(t.get(key)))
                        return false;
                }
            }
        } catch (ClassCastException unused)   {
            return false;
        } catch (NullPointerException unused) {
            return false;
        }

        return true;
    }

到这里HashTable源码解析基本完成,还有一些转Set,writeObject之类的方法,感兴趣的同学可以自己打开源码库看一下,最后总结一下,HashTable和HashMap由于都实现了Map接口,所以其也有很多相似之处,如果看过HashMap的再来看HashTable应该会觉得很轻松,毕竟差别还是很小的,如果有什么错误的地方还望指出

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值