• Hashtable源码分析


    前言:Hashtable线程安全的集合类,虽然它线程安全,然而在日常开发中使用的频率很低,毕竟锁的颗粒度太大了。但是这并不妨碍我们对其内部原理进行了解。

    注:本文jdk源码版本为jdk1.8.0_172。


    1.Hashtable基本概念

    Hashtable与HashMap一样,都是以键值对的形式存储数据。但是Hashtable的键值不能为null,而HashMap的键值是可以为null的。Hashtable线程安全,因为它的元素操作方法上都加了synchronized关键字,这就导致锁的粒度太大,因此日常开发中一般建议使用ConcurrentHashMap。注意Hashtable的映射关系不是有序的,毕竟是以hashCode散列存储。

    首先来看Hashtable的继承关系:

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

    Hashtable继承了Dictionary,实现了Map、Cloneable、Serializable接口。

    HashTable构造函数

    Hashtable共有4个构造函数。

    #1.默认构造函数

    1  public Hashtable() {
    2         this(11, 0.75f);
    3     }

    分析:

    从默认构造函数可知:Hashtable的默认容量为11,扩容因子为0.75。

    #2.确定容量的构造函数

    1   public Hashtable(int initialCapacity) {
    2         this(initialCapacity, 0.75f);
    3     }

    其他构造函数可翻看相关源码,还是比较简单的。

    2.put操作

     1 public synchronized V put(K key, V value) {
     2         // Make sure the value is not null
     3         // 如果value值为空,直接抛出空指针异常
     4         if (value == null) {
     5             throw new NullPointerException();
     6         }
     7 
     8         // Makes sure the key is not already in the Hashtable.
     9         Entry<?,?> tab[] = table;
    10         // 如果key为null,这里同样会抛出空指针
    11         int hash = key.hashCode();
    12         // 通过hash值与table长度取余,确定元素的位置
    13         int index = (hash & 0x7FFFFFFF) % tab.length;
    14         @SuppressWarnings("unchecked")
    15         // 取出当前位置上的元素        
    16         Entry<K,V> entry = (Entry<K,V>)tab[index];
    17         // 如果当前位置上存在值,则进行循环,因为位置上的值是以链表形式存储的
    18         for(; entry != null ; entry = entry.next) {
    19             // 查找是否具有相同hash值和key的元素,有则替换
    20             if ((entry.hash == hash) && entry.key.equals(key)) {
    21                 V old = entry.value;
    22                 entry.value = value;
    23                 return old;
    24             }
    25         }
    26         // 添加元素
    27         addEntry(hash, key, value, index);
    28         return null;
    29     }

    分析:put操作的逻辑比较简单明了,通过元素的hash值确定元素在数组上的位置,然后判断是否需要对原值进行替换,如果不进行替换则直接进行插入操作。

    注意:整个put操作的流程和HashMap类似,但从以上源码可以看出Hashtable是不允许[key,value]为null。

    addEntry函数(插入元素),这里会涉及扩容,因此还是很有必要看下

     1 private void addEntry(int hash, K key, V value, int index) {
     2         // modCount++表示进行了修改
     3         modCount++;
     4 
     5         Entry<?,?> tab[] = table;
     6         // 如果元素总量大于threshold
     7         // threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
     8         // 在容量不超过最大值的时候,阈值等于容量与扩容因子的乘积
     9         if (count >= threshold) {
    10             // Rehash the table if the threshold is exceeded
    11             // 扩容
    12             rehash();
    13 
    14             tab = table;
    15             hash = key.hashCode();
    16             // 重新计算元素的位置
    17             index = (hash & 0x7FFFFFFF) % tab.length;
    18         }
    19 
    20         // Creates the new entry.
    21         @SuppressWarnings("unchecked")
    22         // 取出当前位置上的元素
    23         Entry<K,V> e = (Entry<K,V>) tab[index];
    24         // 进行插入操作,从这里可以看出新的元素总是在链表头的位置
    25         tab[index] = new Entry<>(hash, key, value, e);
    26         count++;
    27     }

    分析:

    在该函数中涉及扩容,但是由于put操作为线程安全,所以扩容时也是线程安全的。扩容要求:当前元素个数大于等于容量与扩容因子的乘积。还有一点需注意插入的新节点总是在链表头

    接下来看下addEntry函数中的重点rehash(扩容函数)

     1 protected void rehash() {
     2         // 旧的容量大小
     3         int oldCapacity = table.length;
     4         Entry<?,?>[] oldMap = table;
     5 
     6         // overflow-conscious code
     7         // 扩容后新的容量等于原来容量的2倍+1
     8         int newCapacity = (oldCapacity << 1) + 1;
     9         // 容量大小控制,不要超过最大值
    10         if (newCapacity - MAX_ARRAY_SIZE > 0) {
    11             if (oldCapacity == MAX_ARRAY_SIZE)
    12                 // Keep running with MAX_ARRAY_SIZE buckets
    13                 return;
    14             newCapacity = MAX_ARRAY_SIZE;
    15         }
    16         // 创建新的数组
    17         Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];
    18 
    19         modCount++;
    20         // 更新扩容因子
    21         threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
    22         table = newMap;
    23         // 数组转移 这里是从数组尾往前搬移
    24         for (int i = oldCapacity ; i-- > 0 ;) {
    25             for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {
    26                 Entry<K,V> e = old;
    27                 old = old.next;
    28                 // 计算元素在新数组中的位置
    29                 int index = (e.hash & 0x7FFFFFFF) % newCapacity;
    30                 // 进行元素插入,注意这里是头插法,元素会倒序
    31                 e.next = (Entry<K,V>)newMap[index];
    32                 newMap[index] = e;
    33             }
    34         }
    35     }

    分析:

    整个扩容过程比较简单,注意新的数组大小是原来的2倍加1,还有一点比较重要扩容时插入新元素采用的是头插法,元素会进行倒序

    3.get操作

    分析完put操作后,我们再来看下get操作,get操作相对来说就简单许多了

     1 public synchronized V get(Object key) {
     2         Entry<?,?> tab[] = table;
     3         int hash = key.hashCode();
     4         // 计算元素的位置
     5         int index = (hash & 0x7FFFFFFF) % tab.length;
     6         for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
     7             // 遍历 寻找hash和key相同的元素
     8             if ((e.hash == hash) && e.key.equals(key)) {
     9                 return (V)e.value;
    10             }
    11         }
    12         // 如果未发现元素,则返回null
    13         return null;
    14     }

    分析:

    get操作比较简单,通过hash值与key进行查找,找到立即返回,未找到则返回null。

    总结

    本文对Hashtable的主要源码进行了分析,总体来看Hashtable还是比较简单,这里总结一下侧重点:

    #1.Hashtable线程安全元素无序(因为以hashCode为基准进行散列存储),不允许[key,value]为null

    #2.Hashtable默认容量为11,与HashMap不同(默认容量16),扩容时容量增长为2*n+1(HashMap直接增长为2倍)。

    #3.扩容转移元素时采用的是头插法


    by Shawn Chen,2019.08.20日,晚上。

  • 相关阅读:
    thinkphp中<eq>标签的使用
    Thinkphp中的eq比较标签
    select取数据库值设为默认值,TP框架模板中ifelse
    fastadmin 前端根据status自定义显示不同的内容
    CMS自定义表单无法切换“是否需要登录”开关
    js获取域名
    fastadmin 页面添加编辑日期时间
    bootstrap-table给单元格添加链接
    python相关资料
    区块链共识机制 —— PoW共识的Python实现
  • 原文地址:https://www.cnblogs.com/developer_chan/p/11384103.html
Copyright © 2020-2023  润新知