Java集合(四)HashMap、HashTable解析

本文目錄

1 HashMap

1.1 HashMap定義

1.2  HashMap的數據結構

1.3 HashMap的存儲實現

1.4 HashMap的擴容

1.5 HashMap中常用方法

1.5.1 構造函數

1.5.2 讀取實現:

2 HashTable

2.1 定義

2.2 構造方法

2.3 主要方法

3 HashTable與HashMap的異同點

3.1 異同點

3.2 HashMap和HashTable的區別總結


1 HashMap

HashMap是使用非常多的Collection,它是基於哈希表的 Map 接口的實現,以key-value的形式存在。在HashMap中,key-value總是會當做一個整體來處理,系統會根據hash算法來來計算key-value的存儲位置,可以通過key快速地存、取value。下面就來分析HashMap的存取。

1.1 HashMap定義

HashMap的定義如下:

public class HashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable

HashMap實現了Map接口,繼承AbstractMap。其中Map接口定義了鍵映射到值的規則,而AbstractMap類提供 Map 接口的骨幹實現,以最大限度地減少實現此接口所需的工作,其實AbstractMap類已經實現了Map。

1.2  HashMap的數據結構

一般來說,數據結構有鏈表和數組兩種實現對數據的存儲。

(1)數組:

數組存儲區間是連續的,佔用內存嚴重,故空間複雜度很大。但數組的二分查找時間複雜度小,爲O(1);數組的特點是:尋址容易,插入和刪除困難;

空間複雜度:指的是執行算法所需要的內存空間

時間複雜度:執行算法所需要的計算工作量

(2)鏈表:

鏈表存儲區間離散,佔用內存比較寬鬆,故空間複雜度很小,但時間複雜度很大,達O(N)。鏈表的特點是:尋址困難,插入和刪除容易。

(3)哈希表:

那麼我們能不能綜合兩者的特性,做出一種尋址容易,插入刪除也容易的數據結構?答案是肯定的,這就是我們要提起的哈希表。
哈希表((Hash table)既滿足了數據的查找方便,同時不佔用太多的內容空間,使用也十分方便。   

哈希表有多種不同的實現方法,我接下來解釋的是最常用的一種方法—— 拉鍊法,我們可以理解爲“鏈表的數組” ,如圖:

在這裏插入圖片描述

在這裏插入圖片描述

 

 

 從上圖我們可以發現哈希表是由數組+鏈表組成的,一個長度爲16的數組中,每個元素存儲的是一個鏈表的頭結點。
     那麼這些元素是按照什麼樣的規則存儲到數組中呢。一般情況是通過hash(key)%len獲得,也就是元素的key的哈希值對數組長度取模得到。比如上述哈希表中:

12%16=12,28%16=12,108%16=12,140%16=12。所以12、28、108以及140都存儲在數組下標爲12的位置。   

HashMap其實也是一個線性的數組實現的,所以可以理解爲其存儲數據的容器就是一個線性數組。 這可能讓我們很不解,一個線性的數組怎麼實現按鍵值對來存取數據呢?這裏HashMap有做一些處理。  

首先HashMap裏面實現一個靜態內部類Entry,其重要的屬性有 key , value, next,從屬性key,value我們就能很明顯的看出來Entry就是HashMap鍵值對實現的一個基礎bean,我們上面說到HashMap的基礎就是一個線性數組,這個數組就是 Entry[],Map裏面的內容都保存在Entry[]裏面。

1.3 HashMap的存儲實現

下面以put(key,value)來說明:

源碼如下:

public V put(K key, V value) {
        //當key爲null,調用putForNullKey方法,保存null與table第一個位置中,這是HashMap允許爲null的原因
        if (key == null)
            return putForNullKey(value);
        //計算key的hash值,此處對原來元素的hashcode進行了再次hash
        int hash = hash(key.hashCode());                  ------(1)
        //計算key hash 值在 table 數組中的位置
        int i = indexFor(hash, table.length);             ------(2)
        //從i出開始迭代 e,找到 key 保存的位置
        for (Entry<K, V> e = table[i]; e != null; e = e.next) {
            Object k;
            //判斷該條鏈上是否有hash值相同的(key相同)
            //若存在相同,則直接覆蓋value,返回舊value
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;    //舊值 = 新值
                e.value = value;
                e.recordAccess(this);
                return oldValue;     //返回舊值
            }
        }
        //修改次數增加1
        modCount++;
        //將key、value添加至i位置處
        addEntry(hash, key, value, i);
        return null;
    }

通過源碼我們可以清晰看到HashMap保存數據的過程爲:首先判斷key是否爲null,若爲null,則直接調用putForNullKey方法。

若不爲空則先計算key的hash值,然後根據hash值搜索在table數組中的索引位置,如果table數組在該位置處有元素,則通過比較是否存在相同的key,若存在則覆蓋原來key的value,==否則將該元素保存在鏈頭(最先保存的元素放在鏈尾)==。

若table在該處沒有元素,則直接保存。這個過程看似比較簡單,其實深有內幕。有如下幾點:

  • 1) 先看迭代處。此處迭代原因就是爲了防止存在相同的key值,若發現兩個hash值(key)相同時,HashMap的處理方式是用新value替換舊value,這裏並沒有處理key,這就解釋了HashMap中沒有兩個相同的key。
  • 2) 再看(1)、(2)處。這裏是HashMap的精華所在。首先是hash方法,該方法爲一個純粹的數學計算,就是計算h的hash值。
static int hash(int h) {
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

對於HashMap的table而言,數據分佈需要均勻(最好每項都只有一個元素,這樣就可以直接找到),不能太緊也不能太鬆,太緊會導致查詢速度慢,太鬆則浪費空間。計算hash值後,怎麼才能保證table元素分佈均與呢?我們會想到取模,但是由於取模的消耗較大,HashMap是這樣處理的:調用indexFor方法。

static int indexFor(int h, int length) {
        return h & (length-1);
    }

HashMap的底層數組長度總是2的n次方,在構造函數中存在:capacity <<= 1;這樣做總是能夠保證HashMap的底層數組長度爲2的n次方。當length爲2的n次方時,h&(length - 1)就相當於對length取模,而且速度比直接取模快得多,這是HashMap在速度上的一個優化。至於爲什麼是2的n次方,下面解釋。

對length取模來得到hash是常用的hash索引方法,這裏採用位運算的話效率更高。

我們回到indexFor方法,該方法僅有一條語句:h&(length - 1),這句話除了上面的取模運算外還有一個非常重要的責任:均勻分佈table數據和充分利用空間。

這裏我們假設length爲16(2^n)和15,h爲5、6、7。當n=15時,6和7的結果一樣,這樣表示他們在table存儲的位置是相同的,也就是產生了碰撞,6、7就會在一個位置形成鏈表,這樣就會導致查詢速度降低。誠然這裏只分析三個數字不是很多,那麼我們就看0-15。

而當length = 16時,length – 1 = 15 即1111,那麼進行低位&運算時,值總是與原來hash值相同,而進行高位運算時,其值等於其低位值。所以說當length = 2^n時,不同的hash值發生碰撞的概率比較小,這樣就會使得數據在table數組中分佈較均勻,查詢速度也較快。

這裏我們再來複習put的流程:當我們想一個HashMap中添加一對key-value時,系統首先會計算key的hash值,然後根據hash值確認在table中存儲的位置。若該位置沒有元素,則直接插入。否則迭代該處元素鏈表並依此比較其key的hash值。

如果兩個hash值相等且key值相等(e.hash == hash && ((k = e.key) == key || key.equals(k))),則用新的Entry的value覆蓋原來節點的value。如果兩個hash值相等但key值不等 ,則將該節點插入該鏈表的鏈頭。具體的實現過程見addEntry方法,如下:

 

void addEntry(int hash, K key, V value, int bucketIndex) {
        //獲取bucketIndex處的Entry
        Entry<K, V> e = table[bucketIndex];
        //將新創建的 Entry 放入 bucketIndex 索引處,並讓新的 Entry 指向原來的 Entry 
        table[bucketIndex] = new Entry<K, V>(hash, key, value, e);
        //若HashMap中元素的個數超過極限了,則容量擴大兩倍
        if (size++ >= threshold)
            resize(2 * table.length);
    }

有兩點需要注意:

(1)是鏈的產生。

這是一個非常優雅的設計。系統總是將新的Entry對象添加到bucketIndex處。如果bucketIndex處已經有了對象,那麼新添加的Entry對象將指向原有的Entry對象,形成一條Entry鏈,但是若bucketIndex處沒有Entry對象,也就是e==null,那麼新添加的Entry對象指向null,也就不會產生Entry鏈了。

(2)擴容問題。

隨着HashMap中元素的數量越來越多,發生碰撞的概率就越來越大,所產生的鏈表長度就會越來越長,這樣勢必會影響HashMap的速度,爲了保證HashMap的效率,系統必須要在某個臨界點進行擴容處理。該臨界點在當HashMap中元素的數量等於table數組長度*加載因子。但是擴容是一個非常耗時的過程,因爲它需要重新計算這些數據在新table數組中的位置並進行復制處理。所以如果我們已經預知HashMap中元素的個數,那麼預設元素的個數能夠有效的提高HashMap的性能。

1.4 HashMap的擴容

如上所述:當HashMap中元素的數量等於table數組長度*加載因子,系統將對HashMap進行擴容處理。擴容處理的resize方法如下:

final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        if (oldCap > 0) {
            //如果原容量大於最大空間,則讓閾值爲最大值。因爲不能再擴容了,最大容量就是整數最大值。
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            //兩倍擴容,閾值也跟着變爲兩倍
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
        if (oldTab != null) {
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    if (e.next == null)
                        //當後面沒有節點時,直接插入即可 //每個元素重新計算索引位置,此處的hash值並沒有變,只是改變索引值
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order
                    //否則,就從頭到尾依次將節點進行索引然後插入新數組,這樣插入後的鏈表順序會和原來的順序相反。
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }

 

1.5 HashMap中常用方法

1.5.1 構造函數

HashMap提供了三個構造函數:

(1)HashMap():構造一個具有默認初始容量 (16) 和默認加載因子 (0.75) 的空 HashMap。

(2)HashMap(int initialCapacity):構造一個帶指定初始容量和默認加載因子 (0.75) 的空 HashMap。

(3)HashMap(int initialCapacity, float loadFactor):構造一個帶指定初始容量和加載因子的空 HashMap。

在這裏提到了兩個參數:初始容量,加載因子:

這兩個參數是影響HashMap性能的重要參數,其中容量表示哈希表中桶的數量,初始容量是創建哈希表時的容量,加載因子是哈希表在其容量自動增加之前可以達到多滿的一種尺度,它衡量的是一個散列表的空間的使用程度,負載因子越大表示散列表的裝填程度越高,反之愈小。

對於使用鏈表法的散列表來說,查找一個元素的平均時間是O(1+a),因此如果負載因子越大,對空間的利用更充分,然而後果是查找效率的降低;如果負載因子太小,那麼散列表的數據將過於稀疏,對空間造成嚴重浪費。系統默認負載因子爲0.75,一般情況下我們是無需修改的。HashMap是一種支持快速存取的數據結構,要了解它的性能可從上述的數據結構進行分析。

1.5.2 讀取實現:

HashMap的讀取實現get(key)如下:

//相對於HashMap的存而言,取就顯得比較簡單了。通過key的hash值找到在table數組中的索引處的Entry,然後返回該key對應的value即可。

public V get(Object key) {
        // 若爲null,調用getForNullKey方法返回相對應的value
        if (key == null)
            return getForNullKey();
        // 根據該 key 的 hashCode 值計算它的 hash 碼  
        int hash = hash(key.hashCode());
        // 取出 table 數組中指定索引處的值
        for (Entry<K, V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) {
            Object k;
            //若搜索的key與查找的key相同,則返回相對應的value
            if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
                return e.value;
        }
        return null;
    }

在這裏能夠根據key快速的取到value除了和HashMap的數據結構密不可分外,還和Entry有莫大的關係,在前面就提到過,HashMap在存儲過程中並沒有將key,value分開來存儲,而是當做一個整體key-value來處理的,這個整體就是Entry對象。

同時value也只相當於key的附屬而已。在存儲的過程中,系統根據key的hashcode來決定Entry在table數組中的存儲位置,在取的過程中同樣根據key的hashcode取出相對應的Entry對象。

在java中與有兩個類都提供了一個多種用途的hashTable機制,他們都可以將可以key和value結合起來構成鍵值對通過put(key,value)方法保存起來,然後通過get(key)方法獲取相對應的value值。

2 HashTable

對於HashTable而言,它在很大程度上和HashMap的實現差不多 ,我們可根據HashMap來理解HashTable。

2.1 定義

HashTable的定義如下:

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

從中我們可以看出HashTable繼承Dictionary類,實現Map接口。其中Dictionary類是任何可將鍵映射到相應值的類(如 Hashtable)的抽象父類。每個鍵和每個值都是一個對象。在任何一個 Dictionary 對象中,每個鍵至多與一個值相關聯。其中:

(1) HashTable採用"拉鍊法"實現哈希表,它定義了幾個重要的參數:table、count、threshold、loadFactor、modCount。

(2)table:爲一個Entry[]數組類型,Entry代表了“拉鍊”的節點,每一個Entry代表了一個鍵值對,哈希表的"key-value鍵值對"都是存儲在Entry數組中的。

(3)count:HashTable的大小,注意這個大小並不是HashTable的容器大小,而是他所包含Entry鍵值對的數量。

(4)threshold:Hashtable的閾值,用於判斷是否需要調整Hashtable的容量。threshold的值="容量*加載因子"。

(5)loadFactor:加載因子。

(6) modCount:用來實現“fail-fast”機制的(也就是快速失敗)。所謂快速失敗就是在併發集合中,其進行迭代操作時,若有其他線程對其進行結構性的修改,這時迭代器會立馬感知到,並且立即拋出ConcurrentModificationException異常,而不是等到迭代完成之後才告訴你(你已經出錯了)。

2.2 構造方法

 在HashTabel中存在5個構造函數。通過這5個構造函數我們構建出一個我想要的HashTable。

// 默認構造函數,容量爲11,加載因子爲0.75。
public Hashtable() {
        this(11, 0.75f);
    }
     
// 用指定初始容量和默認的加載因子 (0.75) 構造一個新的空哈希表。
public Hashtable(int initialCapacity) {
        this(initialCapacity, 0.75f);
    }
     
//制定初始容量和加載因子來構造哈希表
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,獲得大小爲initialCapacity的table數組
        table = new Entry[initialCapacity];
        //計算閥值
        threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
        //初始化HashSeed值
        initHashSeedAsNeeded(initialCapacity);
    }

用指定初始容量和指定加載因子構造一個新的空哈希表。其中initHashSeedAsNeeded方法用於初始化hashSeed參數,其中hashSeed用於計算key的hash值,它與key的hashCode進行按位異或運算。這個hashSeed是一個與實例相關的隨機值,主要用於解決hash衝突。  

private int hash(Object k) {
        return hashSeed ^ k.hashCode();
    }

構造一個與給定的 Map 具有相同映射關係的新哈希表。

public Hashtable(Map<? extends K, ? extends V> t) {
        //設置table容器大小,其值==t.size * 2 + 1
        this(Math.max(2*t.size(), 11), 0.75f);
        putAll(t);
    }

2.3 主要方法

HashTable的API對外提供了許多方法,這些方法能夠很好幫助我們操作HashTable,但是這裏我只介紹兩個最根本的方法:put、get。 首先我們先看put方法:將指定 key 映射到此哈希表中的指定 value。注意這裏鍵key和值value都不可爲空。


public synchronized V put(K key, V value) {
        // 確保value不爲null
        if (value == null) {
            throw new NullPointerException();
        }

        /*
         * 確保key在table[]是不重複的
         * 處理過程:
         * 1、計算key的hash值,確認在table[]中的索引位置
         * 2、迭代index索引位置,如果該位置處的鏈表中存在一個一樣的key,則替換其value,返回舊值
         */
        Entry tab[] = table;
        int hash = hash(key);    //計算key的hash值
        int index = (hash & 0x7FFFFFFF) % tab.length;     //確認該key的索引位置
        //迭代,尋找該key,替換
        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;
            }
        }

        modCount++;
        if (count >= threshold) {  //如果容器中的元素數量已經達到閥值,則進行擴容操作
            rehash();
            tab = table;
            hash = hash(key);
            index = (hash & 0x7FFFFFFF) % tab.length;
        }

        // 在索引位置處插入一個新的節點
        Entry<K,V> e = tab[index];
        tab[index] = new Entry<>(hash, key, value, e);
        //容器中元素+1
        count++;
        return null;
    }

put方法的整個處理流程是:計算key的hash值,根據hash值獲得key在table數組中的索引位置,然後迭代該key處的Entry鏈表(我們暫且理解爲鏈表),若該鏈表中存在一個這個的key對象,那麼就直接替換其value值即可,否則在將改key-value節點插入該index索引位置處 。

在HashTabled的put方法中有兩個地方需要注意:

1)HashTable的擴容操作,在put方法中,如果需要向table[]中添加Entry元素,會首先進行容量校驗,如果容量已經達到了閥值,HashTable就會進行擴容處理rehash(),如下:

protected void rehash() {
        int oldCapacity = table.length;
        //元素
        Entry<K,V>[] oldMap = table;

        //新容量=舊容量 * 2 + 1
        int newCapacity = (oldCapacity << 1) + 1;
        if (newCapacity - MAX_ARRAY_SIZE > 0) {
            if (oldCapacity == MAX_ARRAY_SIZE)
                return;
            newCapacity = MAX_ARRAY_SIZE;
        }

        //新建一個size = newCapacity 的HashTable
        Entry<K,V>[] newMap = new Entry[];

        modCount++;
        //重新計算閥值
        threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
        //重新計算hashSeed
        boolean rehash = initHashSeedAsNeeded(newCapacity);

        table = newMap;
        //將原來的元素拷貝到新的HashTable中
        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;
            }
        }
    }

在這個rehash()方法中我們可以看到容量擴大兩倍+1,同時需要將原來HashTable中的元素一一複製到新的HashTable中,這個過程是比較消耗時間的,同時還需要重新計算hashSeed的,畢竟容量已經變了。

比如初始值11、加載因子默認0.75,那麼這個時候閥值threshold=8,當容器中的元素達到8時,HashTable進行一次擴容操作,容量 = 8 * 2 + 1 =17,而閥值threshold=17*0.75 = 13,當容器元素再一次達到閥值時,HashTable還會進行擴容操作,依次類推。

下面是計算key的hash值,這裏hashSeed發揮了作用。

private int hash(Object k) {
        return hashSeed ^ k.hashCode();
    }

相對於put方法,get方法就會比較簡單,處理過程就是計算key的hash值,判斷在table數組中的索引位置,然後迭代鏈表,匹配直到找到相對應key的value,若沒有找到返回null。

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

HashTable與HashMap的異同點

3.1 異同點

HashTable和HashMap存在很多的相同點,但是他們還是有幾個比較重要的不同點。

(1)我們從他們的定義就可以看出他們的不同,HashTable基於Dictionary類,而HashMap是基於AbstractMap。Dictionary是什麼?它是任何可將鍵映射到相應值的類的抽象父類,而AbstractMap是基於Map接口的骨幹實現,它以最大限度地減少實現此接口所需的工作。

(2)HashMap可以允許存在一個爲null的key和任意個爲null的value,但是HashTable中的key和value都不允許爲null。

當HashMap遇到爲null的key時,它會調用putForNullKey方法來進行處理。對於value沒有進行任何處理,只要是對象都可以。

if (key == null) return putForNullKey(value);

而當HashTable遇到null時,它會直接拋出NullPointerException異常信息。

if (value == null) {  throw new NullPointerException(); }

(3)Hashtable的方法是同步的,而HashMap的方法不是。所以有人一般都建議如果是涉及到多線程同步時採用HashTable,沒有涉及就採用HashMap,但是在Collections類中存在一個靜態方法:synchronizedMap(),該方法創建了一個線程安全的Map對象,並把它作爲一個封裝的對象來返回,所以通過Collections類的synchronizedMap方法是可以我們你同步訪問潛在的HashMap。

3.2 HashMap和HashTable的區別總結

(1)HashMap線程不安全,HashTable是線程安全的。HashMap內部實現沒有任何線程同步相關的代碼,所以相對而言性能要好一點。如果在多線程中使用HashMap需要自己管理線程同步。HashTable大部分對外接口都使用synchronized包裹,所以是線程安全的,但是性能會相對差一些。

(2)二者的基類不一樣。HashMap派生於AbstractMap,HashTable派生於Dictionary。它們都實現Map, Cloneable, Serializable這些接口。AbstractMap中提供的基礎方法更多,並且實現了多個通用的方法,而在Dictionary中只有少量的接口,並且都是abstract類型。

(3)key和value的取值範圍不同。HashMap的key和value都可以爲null,但是HashTable的key和value都不能爲null。對於HashMap如果get返回null,並不能表明HashMap不存在這個key,如果需要判斷HashMap中是否包含某個key,就需要使用containsKey這個方法來判斷。

(3)算法不一樣。HashMap的initialCapacity爲16,而HashTable的initialCapacity爲11。HashMap中初始容量必須是2的冪,如果初始化傳入的initialCapacity不是2的冪,將會自動調整爲大於出入的initialCapacity最小的2的冪。HashMap使用自己的計算hash的方法(會依賴key的hashCode方法),HashTable則使用key的hashCode方法得到。

 

附言:

本文整理來源於網絡、博客等資源,僅做個人學習筆記複習所用。

如果對你學習有用,請點贊共同學習!

如有侵權,請聯繫我刪!

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章