zoukankan      html  css  js  c++  java
  • HashTable源码阅读

    本文基于JDK1.8 >读完本文预计需要15分钟

    摘要

    想必HashTable大家都不会陌生(虽然现在不推荐用它了,推荐ConCurrentHashMap),但我们说起HashMap很自然的就会想到HashTable,也经常拿它两做对比。

    大部分人想到的第一点就是:HashMap线程不安全,HashTable线程安全!看源码会发现HashTable的所有方法都是synchronized修饰,看到这个我们就知道原因了。我们先来看一遍HashTable的源码,它两的源码对比着看,效果更佳。我们篇幅按一样的顺序写便于对比观看。HashMap源码阅读

    首先我们了解到 HashMap继承自AbstractMap实现的是Map接口,而HashTable继承自Dictionary实现的是Map接口。不幸的是Dictionary已经被弃用。

    • 主要的不同点:
      • 线程安全: HashMap线程不安全 <--> HashTable线程安全
      • 实现方式: HashMap用数组+链表+红黑树 <--> HashTable用数组+链表
      • 默认初始容量: HashMap是16 <--> HashTable是11
      • key是否可为null: HashMap可以允许存在一个为null的key和任意个为null的value <--> HashTable中的key和value都不允许为null

    关键变量:

        //内部存放的键值对数组
        private transient Entry<?,?>[] table;
    
        //存放的键值对的数量
        private transient int count;
    
        //扩容的阈值
        private int threshold;
    
        //加载因子
        private float loadFactor;
    

    四个构造方法:

        //构造方法1
        //用指定的初始容量,加载因子搞一个空的hash表
        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);
        }
    
        //构造方法2
        //用指定的初始容量与默认的加载因子0.75搞一个空的hash表
        public Hashtable(int initialCapacity) {
            this(initialCapacity, 0.75f);
        }
    
        //构造方法3
        //空参构造 默认使用这个 默认初始容量11(HashMap是16),加载因子0.75调用构造方法1搞一个新的hash表
        public Hashtable() {
            this(11, 0.75f);
        }
        
        //构造方法4
        //传入一个map搞一个与此map具用相同映射的新hash表
        public Hashtable(Map<? extends K, ? extends V> t) {
            this(Math.max(2*t.size(), 11), 0.75f);
            putAll(t);
        }
    
    

    对四个构造方法简单总结一下,与HashMap看起来十分相似啊,但有所区别:

    • 构造函数并不像HashMap用的是一种懒加载的方式(用到的时候才去初始化),而是咔,一上来就给你先整一个新的空Hash表在那放着。
    • 默认初始容量:HashMap是16,HashTable是11。

    主要方法

    hashtable->hashCode()

            
            public synchronized boolean containsKey(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 true;
                    }
                }
                return false;
            }
    
    

    我们拎出来containsKey()方法举例说明:

    • HashTable中没有像HashMap一样搞一个专门的hash()方法,它直接调用了Object提供的本地方法hashCode();
      int hash = key.hashCode(),在HashTable中都是以这种方式获取hashCode

    hashtable->put()

    以下是源代码(带注释):

        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];
            //如果存在相同的key则遍历链表把那个key对应的value换成新的value
            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;
        }
          
    
        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);
            //把表中容量加1
            count++;
        }
    
    

    那么我们来总结一下put方法:

    • 对value判空,hashtable不允许key、value为空
    • 根据key的hashCode值找到对应的索引
    • 如果存在相同的key则遍历链表把那个key对应的value换成新的value
    • 判断表中键值对数量是否大于阈值
      是,调用rehash()方法扩容
    • 直接把键值对插到哈希槽(链表第一位)

    hashtable->get()

        public synchronized V get(Object key) {
            Entry<?,?> tab[] = table;
            int hash = key.hashCode();
            //寻址
            int index = (hash & 0x7FFFFFFF) % tab.length;
            //遍历链表取出key对应的value
            for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
                if ((e.hash == hash) && e.key.equals(key)) {
                    return (V)e.value;
                }
            }
            return null;
        }
    
    

    那么我们来总结一下get()方法:

    • 根据key的hashCode值找到对应索引,遍历链表取值

    hashtable->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<?,?>[] 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;
                }
            }
        }
    
    

    总结一下rehash()方法:

    • 求出新的容量(旧容量2倍+1)

    • 判断是否达到最大容量

    • 根据新容量初始化一个新表

    • 求出新的阈值

    • 把旧表中的数据迁移到新表

    我想这会阅读结束之后应该对HashTable有了一定的认识,希望能在面试或者工作中帮到您!

    参考文献

    Hongchen闲谈

    放一张去年的照片【忽略腿毛】,这是我去打羽毛球随手拍的一张照片,当时是出门去球馆。我高中时期常打,但是上大学这个羽毛球就荒废了,来北京之后就没个运动爱好了。

    恰好,我看合租的室友天天拎个拍搁客厅晃悠。于是,我让他拉上我,准备重新给捡起来玩一玩,还是得把这身体锻炼好了,否则,怎么扛得住996呢【狗头】。

    稍作思考,还是需要发展一下自己的体育爱好,毕竟运动之后的快感还是有的。怕自己长时间躺着刷抖音之后的精神空洞【狗头】。

    如果有喜欢打球的小伙伴可以一块打球,联系我!hahahahaha

    年初了,感觉疫情常态化了,也去打了疫苗,希望疫情早点过去!peaceeeee

    感谢阅读

    才疏学浅,如有你发现有错误的地方,可以在后台或者评论提出来,我会加以修改。
    感谢您的阅读,欢迎并感谢关注!wxgzh:Hongchen的博客

  • 相关阅读:
    使用eclipse新建一个SWT工程
    C++类的构造函数
    D3D编程的常见报错及解决
    D3D窗口的初始化
    C++联合体的内存使用
    QT程序如何编译
    Restart
    HTML
    信号、槽位及布局
    QT对话框程序
  • 原文地址:https://www.cnblogs.com/root1/p/14496681.html
Copyright © 2011-2022 走看看