zoukankan      html  css  js  c++  java
  • 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日,晚上。

  • 相关阅读:
    108. Convert Sorted Array to Binary Search Tree
    How to check if one path is a child of another path?
    Why there is two completely different version of Reverse for List and IEnumerable?
    在Jenkins中集成Sonarqube
    如何查看sonarqube的版本 how to check the version of sonarqube
    Queue
    BFS广度优先 vs DFS深度优先 for Binary Tree
    Depth-first search and Breadth-first search 深度优先搜索和广度优先搜索
    102. Binary Tree Level Order Traversal 广度优先遍历
    How do I check if a type is a subtype OR the type of an object?
  • 原文地址:https://www.cnblogs.com/developer_chan/p/11384103.html
Copyright © 2011-2022 走看看