最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java的HashTable源碼解讀

 更新時間:2023年12月29日 10:21:45   作者:理想萬歲萬萬歲  
這篇文章主要介紹了Java的HashTable源碼解讀,HashTable繼承了Dictionary類,提供了一些字典相關的基本功能如添加、刪除、判空、獲取元素數(shù)量等,需要的朋友可以參考下

一、介紹

在往期文章中,我們從源碼分析了java集合框架中Map一族的HashMap,它為我們提供了保存 <key, value>鍵值對的一系列方法,底層是基于哈希表+鏈表+紅黑樹實現(xiàn)的,但是在多線程并發(fā)的環(huán)境下,表現(xiàn)出線程不安全的特性,今天我們介紹另一個同樣是保存 <key, value>鍵值對但底層是基于哈希表+鏈表 且 線程安全的HashTable。

下面看一下HashTable的UML圖

在這里插入圖片描述

二、類的聲明

我們來看一下HashMap的聲明,可以大致了解他的功能。

public class Hashtable<K,V> extends Dictionary<K,V>
						    implements Map<K,V>, Cloneable, java.io.Serializable
  • 繼承了Dictionary類,提供了一些字典相關的基本功能如添加、刪除、判空、獲取元素數(shù)量等。
  • 實現(xiàn)了Map接口,說明HashMap是一個以<K,V>鍵值對存儲數(shù)據(jù)的結(jié)構
  • 實現(xiàn)了Cloneable接口,提供了對象克隆方法,但請注意,是淺克隆。
  • 實現(xiàn)了Serializable接口,支持序列化。

Dictionary類是我們在java集合框架學習過程中首次見到的類,我們看一下他是什么

從下面源碼中可以看到,Dictionary是一個抽象類,該類中聲明的方法全是抽象方法,沒有默認實現(xiàn),且這些抽象方法在Map接口中都有體現(xiàn),因此我們可以忽略Dictionary類,將注意點都放在HashTable本身。

另外還需要注意到一個事實,在Dictionary的文檔中我們看到,jdk已經(jīng)建議我們忽略它,并且將用Map接口代替它。

/**
 * NOTE: This class is obsolete. New implementations should implement the Map interface, rather than extending this class.
 *
 **/
public abstract class Dictionary<K,V> {
    public Dictionary() {
    }
    abstract public int size();
    abstract public boolean isEmpty();
    abstract public Enumeration<K> keys();
    abstract public Enumeration<V> elements();
    abstract public V get(Object key);
    abstract public V put(K key, V value);
    abstract public V remove(Object key);
}

三、底層實現(xiàn)

HashTable的底層實現(xiàn)為哈希表+鏈表,相較于底層為哈希表+鏈表+紅黑樹實現(xiàn)的HashMap,少了紅黑樹的結(jié)構,因此并沒有那么復雜,如下圖所示

在這里插入圖片描述

四、內(nèi)部類Entry

在HashTable中,將鍵值對封裝成節(jié)點的類為其內(nèi)部類Entry,該內(nèi)部類繼承于Map接口的內(nèi)部接口Entry,從上面HashTable的UML圖中也有所體現(xiàn)。我們看一下該內(nèi)部類的源碼:

private static class Entry<K,V> implements Map.Entry<K,V> {
    // 鍵值對節(jié)點中key的哈希值
    final int hash;
    // 鍵值對節(jié)點中的key
    final K key;
    // 鍵值對節(jié)點中的value
    V value;
    // 鏈表中當前節(jié)點的下一個節(jié)點
    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;
    }
}

從源碼可以看出,HashTable中保存鍵值對的節(jié)點類Entry其實與HashMap中保存鍵值對的節(jié)點類Node是完全相同的。

五、成員變量

// 哈希表中的數(shù)組
private transient Entry<?,?>[] table;
// 哈希表中鍵值對節(jié)點數(shù)量
private transient int count;
// 擴容閾值
private int threshold;
// 加載因子,默認0.75
private float loadFactor;
// 結(jié)構性修改次數(shù),用于快速失敗
private transient int modCount = 0;

六、構造方法

HashMap提供了以下四個構造方法來創(chuàng)建實例

無參構造

通過默認初始容量11默認加載因子為0.75 構造實例

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

指定初始容量和加載因子

對指定的初始容量和加載因子進行校驗后,設置初始容量大小的數(shù)組和加載因子,并計算出擴容閾值。

還記得在HashMap中,數(shù)組和擴容閾值都是在第一次擴容時初始化的。而在HashTable中,取消了這種延時初始化。

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);
}

指定初始容量

指定初始容量,并使用默認的加載因子0.75。

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

通過傳入一個Map對象實例化

通過這種方式構造HashTable實例時,會先創(chuàng)建實例,再調(diào)用putAll()方法將map中的數(shù)據(jù)批量保存到實例中。

注意:在構造HashTable實例時,初始容量為 map容量*2+1默認初始容量11較小值,

public Hashtable(Map<? extends K, ? extends V> t) {
    this(Math.max(2*t.size(), 11), 0.75f);
    putAll(t);
}

七、擴容方法rehash()

該方法的源碼如下,相較于HashMap那么長的擴容resize()方法,是否簡單了一點呢?在HashMap的擴容resize()方法中,還要一堆判斷去確定數(shù)組容量、擴容閾值等信息,而且由于HashMap中數(shù)組長度為2的n次方,還需要判斷哪些鍵值對在擴容前后的數(shù)組下標是否不變等等。

而在HashTable中,由于在擴容前哈希表就已經(jīng)完成初始化了,且由于哈希表數(shù)組長度可能為任意值,也不存在擴容后鍵值對位于數(shù)組的下標不變的情況,因此,簡單粗暴的,直接擴容,然后將原哈希表中的鍵值對重新計算數(shù)組下標,放在擴容后的哈希表中。

要注意一點:在HashMap中,原哈希表中鏈表上的元素采用尾插法放在新哈希表。而在HashTable中采用的是頭插法

protected void rehash() {
    // 擴容前的哈希數(shù)組容量
    int oldCapacity = table.length;
    // 擴容前的哈希數(shù)組
    Entry<?,?>[] oldMap = table;
    // 1.計算擴容后的容量,新容量= 原容量*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;
    // 2. 遍歷原哈希表,將原哈希表中的鍵值對重新計算數(shù)組下標后,通過頭插法,放至新的哈希表中。
    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;
            // 重新計算數(shù)組下標
            int index = (e.hash & 0x7FFFFFFF) % newCapacity;
            // 頭插法
            e.next = (Entry<K,V>)newMap[index];
            newMap[index] = e;
        }
    }
}

八、addEntry()

該方法采用頭插法的方式向哈希表中添加元素,在添加元素前,判斷是否需要先調(diào)用rehash()方法進行擴容。

private void addEntry(int hash, K key, V value, int index) {
    modCount++;
    Entry<?,?> tab[] = table;
    if (count >= threshold) {
        // 擴容
        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++;
}

有一點需要注意,HashTable不像HashMap那樣對hashcode()值的高16位和低16位按位與得到hash值,再對數(shù)組長度取余,從而得到數(shù)組下標,而是直接通過** hashcode()值與 0x7FFFFFFF 按位與,在對數(shù)組長度取余,從而得到數(shù)組下標**。

我們分析一下index = (hash & 0x7FFFFFFF) % tab.length

0x7FFFFFFF為16進制表示法,換成2進制為:0111 1111 1111 1111 1111 1111 1111 1111共32位,其中最高位為0,而我們知道,在二進制表示中,最高位為0表示正數(shù),1表示負數(shù),因此(hash & 0x7FFFFFFF)表示將key的hashcode()值的最高位設置為0,解決了key的hashcode()值為負數(shù)的情況,再對數(shù)組長度取余,便可得到數(shù)組下標。

九、常用方法

size()

獲取哈希表中鍵值對節(jié)點的數(shù)量。該方法被synchronized修飾,表示線程安全

public synchronized int size() {
    return count;
}

isEmpty()

判斷哈希表中是否不存在鍵值對,該方法被synchronized修飾,表示線程安全

public synchronized boolean isEmpty() {
    return count == 0;
}

contains() 與 containsValue()

判斷哈希表中是否存在指定的value,遍歷方式與HashMap相同,該方法被synchronized修飾,表示線程安全

從方法首行的判空語句可知,HashTable中不允許value值為空

public synchronized boolean contains(Object value) {
    if (value == null) {
        throw new NullPointerException();
    }
    Entry<?,?> tab[] = table;
    for (int i = tab.length ; i-- > 0 ;) {
        for (Entry<?,?> e = tab[i] ; e != null ; e = e.next) {
            if (e.value.equals(value)) {
                return true;
            }
        }
    }
    return false;
}
public boolean containsValue(Object value) {
    return contains(value);
}

containsKey()

判斷哈希表中是否存在指定的key,遍歷方式與HashMap相同,該方法被synchronized修飾,表示線程安全

從方法第二行int hash = key.hashCode();來看,HashTable中不允許key值為空,否則會拋出空指針異常。

邏輯與HashMap相同,都是先通過計算哈希值確認哈希表中數(shù)組的下標,再通過遍歷鏈表的形式,查找是否存在鍵值對的key與指定的key相同。

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;
}

get()

根據(jù)指定的key,從哈希表中獲取該key對應的value,遍歷方式與HashMap相同,該方法被synchronized修飾,表示線程安全

public synchronized V get(Object key) {
    Entry<?,?> tab[] = table;
    // 計算哈希值確認哈希表中數(shù)組的下標
    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;
}

put()

將指定的 <key, value> 鍵值對保存,該方法被synchronized修飾,表示線程安全

從方法首行的判空語句可知,HashTable中不允許value值為空。

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;
    // 根據(jù)參數(shù)key計算出對應的數(shù)組下標
    int hash = key.hashCode();
    int index = (hash & 0x7FFFFFFF) % tab.length;
    @SuppressWarnings("unchecked")
    Entry<K,V> entry = (Entry<K,V>)tab[index];
    // 對該數(shù)組下標上的鏈表進行遍歷,如果存在key相等的鍵值對,則對其對應的value值進行覆蓋
    for(; entry != null ; entry = entry.next) {
        if ((entry.hash == hash) && entry.key.equals(key)) {
            V old = entry.value;
            // 覆蓋value
            entry.value = value;
            return old;
        }
    }
    // 如果不存在key相等的鍵值對,則調(diào)用addEntry()方法通過頭插法保存鍵值對
    addEntry(hash, key, value, index);
    return null;
}

remove()

public synchronized V remove(Object key) {
    Entry<?,?> tab[] = table;
    // 計算數(shù)組下標
    int hash = key.hashCode();
    int index = (hash & 0x7FFFFFFF) % tab.length;
    @SuppressWarnings("unchecked")
    // 遍歷該數(shù)組下標上的鏈表
    Entry<K,V> e = (Entry<K,V>)tab[index];
    for(Entry<K,V> prev = null ; e != null ; prev = e, e = e.next) {
        if ((e.hash == hash) && e.key.equals(key)) {
            modCount++;
            if (prev != null) {
                prev.next = e.next;
            } else {
                tab[index] = e.next;
            }
            count--;
            V oldValue = e.value;
            e.value = null;
            return oldValue;
        }
    }
    return null;
}

十、總結(jié)

  • 底層結(jié)構為數(shù)組+單鏈表實現(xiàn)的哈希表
  • 當出現(xiàn)哈希沖突或擴容,且當前位置為鏈表時,采用尾插法將節(jié)點插入到鏈表
  • 插入的數(shù)據(jù)是無序的
  • 線程安全
  • 保存鍵值對節(jié)點的類為Entry,而HashMap中保存鍵值對節(jié)點的類為Node
  • 哈希表數(shù)組默認初始容量為11,默認加載因子為0.75
  • 哈希表數(shù)組的容量可任意指定,擴容后的容量 為 擴容前容量*2 + 1
  • 哈希表和擴容閾值在構造方法中完成初始化,是即時加載而不是延時初始化
  • key和value都不允許為空,在HashMap中為key和value都允許為空。
  • HashTable的擴容方法為rehash(),HashMap的擴容方法為resize()
  • 先擴容,后插入。在HashMap中為先插入后擴容。

到此這篇關于Java的HashTable源碼解讀的文章就介紹到這了,更多相關HashTable源碼解讀內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • Spring中ApplicationContext的拓展功能詳解

    Spring中ApplicationContext的拓展功能詳解

    這篇文章主要介紹了Spring中ApplicationContext的拓展功能詳解,相對于BeanFactory來說,ApplicationContext除了提供BeanFactory的所有功能外,還有一些其他的功能,主要包括國際化支持、資源訪問、事件傳遞,需要的朋友可以參考下
    2024-01-01
  • Spring 中 BeanFactoryPostProcessor 的作用和示例源碼分析

    Spring 中 BeanFactoryPostProcessor 的作用和示例源碼分析

    Spring的BeanFactoryPostProcessor是容器初始化的擴展接口,允許在Bean實例化前修改或擴展Bean的配置元數(shù)據(jù),本文給大家介紹Spring 中 BeanFactoryPostProcessor 的作用和示例源碼分析,感興趣的朋友一起看看吧
    2025-03-03
  • Spring 依賴注入與循環(huán)依賴總結(jié)

    Spring 依賴注入與循環(huán)依賴總結(jié)

    這篇文章給大家介紹Spring 依賴注入與循環(huán)依賴總結(jié)篇,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2025-08-08
  • 最新版?IDEA?2022.1?正式上線新功能一覽

    最新版?IDEA?2022.1?正式上線新功能一覽

    4月12日,最新版的IDEA?2022.1正式發(fā)布,無論是從UI上,還是功能上,都有了很大的改進,完善,一起來看一下都有那些重要的更新
    2022-04-04
  • JavaAgent原理及實踐分享

    JavaAgent原理及實踐分享

    這篇文章主要介紹了JavaAgent原理及實踐,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2025-04-04
  • h2database在springboot中的使用教程

    h2database在springboot中的使用教程

    這篇文章主要介紹了h2database在springboot中的使用,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-10-10
  • Java中ThreadPoolExecutor拒絕策略踩坑

    Java中ThreadPoolExecutor拒絕策略踩坑

    本文主要介紹了Java中ThreadPoolExecutor拒絕策略踩坑,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-06-06
  • Spring使用注解進行引用類型的自動裝配逐步分析

    Spring使用注解進行引用類型的自動裝配逐步分析

    自動裝配是springboot的核心,一般提到自動裝配就會和springboot聯(lián)系在一起。實際上Spring Framework早就實現(xiàn)了這個功能。Spring Boot只是在其基礎上,通過SPI的方式,做了進一步優(yōu)化
    2023-03-03
  • java微信企業(yè)號開發(fā)之通訊錄

    java微信企業(yè)號開發(fā)之通訊錄

    這篇文章主要為大家詳細介紹了java微信企業(yè)號開發(fā)之通訊錄的相關資料,感興趣的小伙伴們可以參考一下
    2016-06-06
  • Java list利用遍歷進行刪除操作3種方法解析

    Java list利用遍歷進行刪除操作3種方法解析

    這篇文章主要介紹了Java list利用遍歷進行刪除操作3種方法解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-01-01

最新評論

津市市| 张家口市| 沁水县| 石楼县| 会宁县| 浙江省| 子洲县| 松滋市| 银川市| 阜宁县| 伽师县| 宿迁市| 灵寿县| 江门市| 嘉义市| 淮北市| 怀来县| 呼伦贝尔市| 光山县| 道孚县| 卢湾区| 阳江市| 措勤县| 正蓝旗| 肥东县| 大安市| 嘉祥县| 定日县| 铜川市| 平陆县| 柳河县| 阿城市| 民权县| 沂水县| 吴川市| 南安市| 望奎县| 石柱| 合水县| 丽江市| 永州市|