HashMap 併發 Map 是什麼 內部原理什麼 存儲方式 hashcode 擴容 默認容量等

1. HashMap的數據結構

http://blog.csdn.net/gaopu12345/article/details/50831631   ??看一下java

 

數據結構中有數組和鏈表來實現對數據的存儲,但這二者基本上是兩個極端。算法

  數組

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

鏈表

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

哈希表

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

  哈希表有多種不一樣的實現方法,我接下來解釋的是最經常使用的一種方法—— 拉鍊法,咱們能夠理解爲「鏈表的數組」 ,如圖:dom

 

 

  從上圖咱們能夠發現哈希表是由數組+鏈表組成的,一個長度爲16的數組中,每一個元素存儲的是一個鏈表的頭結點。那麼這些元素是按照什麼樣的規則存儲到數組中呢。通常狀況是經過hash(key)%len得到,也就是元素的key的哈希值對數組長度取模獲得。好比上述哈希表中,12%16=12,28%16=12,108%16=12,140%16=12。因此十二、2八、108以及140都存儲在數組下標爲12的位置。函數

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

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

    /**優化

     * The table, resized as necessary. Length MUST Always be a power of two.

     */

    transient Entry[] table;

2. HashMap的存取實現

     既然是線性數組,爲何能隨機存取?這裏HashMap用了一個小算法,大體是這樣實現:

// 存儲時:
int hash = key.hashCode(); // 這個hashCode方法這裏不詳述,只要理解每一個key的hash是一個固定的int值
int index = hash % Entry[].length;
Entry[index] = value;

// 取值時:
int hash = key.hashCode();
int index = hash % Entry[].length;
return Entry[index];

 

1)put

疑問:若是兩個key經過hash%Entry[].length獲得的index相同,會不會有覆蓋的危險?

  這裏HashMap裏面用到鏈式數據結構的一個概念。上面咱們提到過Entry類裏面有一個next屬性,做用是指向下一個Entry。打個比方, 第一個鍵值對A進來,經過計算其key的hash獲得的index=0,記作:Entry[0] = A。一會後又進來一個鍵值對B,經過計算其index也等於0,如今怎麼辦?HashMap會這樣作:B.next = A,Entry[0] = B,若是又進來C,index也等於0,那麼C.next = B,Entry[0] = C;這樣咱們發現index=0的地方其實存取了A,B,C三個鍵值對,他們經過next這個屬性連接在一塊兒。因此疑問不用擔憂。也就是說數組中存儲的是最後插入的元素。到這裏爲止,HashMap的大體實現,咱們應該已經清楚了。

 public V put(K key, V value) {

        if (key == null)

            return putForNullKey(value); //null老是放在數組的第一個鏈表中

        int hash = hash(key.hashCode());

        int i = indexFor(hash, table.length);

        //遍歷鏈表

        for (Entry<K,V> e = table[i]; e != null; e = e.next) {

            Object k;

            //若是key在鏈表中已存在,則替換爲新value

            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {

                V oldValue = e.value;

                e.value = value;

                e.recordAccess(this);

                return oldValue;

            }

        }

 

        modCount++;

        addEntry(hash, key, value, i);

        return null;

    }

 

void addEntry(int hash, K key, V value, int bucketIndex) {

    Entry<K,V> e = table[bucketIndex];

    table[bucketIndex] = new Entry<K,V>(hash, key, value, e); //參數e, 是Entry.next

    //若是size超過threshold,則擴充table大小。再散列

    if (size++ >= threshold)

            resize(2 * table.length);

}

  固然HashMap裏面也包含一些優化方面的實現,這裏也說一下。好比:Entry[]的長度必定後,隨着map裏面數據的愈來愈長,這樣同一個index的鏈就會很長,會不會影響性能?HashMap裏面設置一個因子,隨着map的size愈來愈大,Entry[]會以必定的規則加長長度。

2)get

 public V get(Object key) {

        if (key == null)

            return getForNullKey();

        int hash = hash(key.hashCode());

        //先定位到數組元素,再遍歷該元素處的鏈表

        for (Entry<K,V> e = table[indexFor(hash, table.length)];

             e != null;

             e = e.next) {

            Object k;

            if (e.hash == hash && ((k = e.key) == key || key.equals(k)))

                return e.value;

        }

        return null;

}

 

3)null key的存取

null key老是存放在Entry[]數組的第一個元素。

   private V putForNullKey(V value) {

        for (Entry<K,V> e = table[0]; e != null; e = e.next) {

            if (e.key == null) {

                V oldValue = e.value;

                e.value = value;

                e.recordAccess(this);

                return oldValue;

            }

        }

        modCount++;

        addEntry(0, null, value, 0);

        return null;

    }

 

    private V getForNullKey() {

        for (Entry<K,V> e = table[0]; e != null; e = e.next) {

            if (e.key == null)

                return e.value;

        }

        return null;

    }

 

4)肯定數組index:hashcode % table.length取模

HashMap存取時,都須要計算當前key應該對應Entry[]數組哪一個元素,即計算數組下標;算法以下:

   /**

     * Returns index for hash code h.

     */

    static int indexFor(int h, int length) {

        return h & (length-1);

    }

 

按位取並,做用上至關於取模mod或者取餘%。

這意味着數組下標相同,並不表示hashCode相同。

5)table初始大小

 

  public HashMap(int initialCapacity, float loadFactor) {

        .....

 

        // Find a power of 2 >= initialCapacity

        int capacity = 1;

        while (capacity < initialCapacity)

            capacity <<= 1;

 

        this.loadFactor = loadFactor;

        threshold = (int)(capacity * loadFactor);

        table = new Entry[capacity];

        init();

    }

 

注意table初始大小並非構造函數中的initialCapacity!!

而是 >= initialCapacity的2的n次冪!!!!

————爲何這麼設計呢?—— 

3. 解決hash衝突的辦法 (下文詳解)

  1. 開放定址法(線性探測再散列,二次探測再散列,僞隨機探測再散列)
  2. 再哈希法
  3. 鏈地址法
  4. 創建一個公共溢出區

Java中hashmap的解決辦法就是採用的鏈地址法。

 

4. 再散列rehash過程

當哈希表的容量超過默認容量時,必須調整table的大小。當容量已經達到最大可能值時,那麼該方法就將容量調整到Integer.MAX_VALUE返回,這時,須要建立一張新表,將原表的映射到新表中。

   /**

     * Rehashes the contents of this map into a new array with a

     * larger capacity.  This method is called automatically when the

     * number of keys in this map reaches its threshold.

     *

     * If current capacity is MAXIMUM_CAPACITY, this method does not

     * resize the map, but sets threshold to Integer.MAX_VALUE.

     * This has the effect of preventing future calls.

     *

     * @param newCapacity the new capacity, MUST be a power of two;

     *        must be greater than current capacity unless current

     *        capacity is MAXIMUM_CAPACITY (in which case value

     *        is irrelevant).

     */

    void resize(int newCapacity) {

        Entry[] oldTable = table;

        int oldCapacity = oldTable.length;

        if (oldCapacity == MAXIMUM_CAPACITY) {

            threshold = Integer.MAX_VALUE;

            return;

        }

 

        Entry[] newTable = new Entry[newCapacity];

        transfer(newTable);

        table = newTable;

        threshold = (int)(newCapacity * loadFactor);

    }

 

    /**

     * Transfers all entries from current table to newTable.

     */

    void transfer(Entry[] newTable) {

        Entry[] src = table;

        int newCapacity = newTable.length;

        for (int j = 0; j < src.length; j++) {

            Entry<K,V> e = src[j];

            if (e != null) {

                src[j] = null;

                do {

                    Entry<K,V> next = e.next;

                    //從新計算index

                    int i = indexFor(e.hash, newCapacity);

                    e.next = newTable[i];

                    newTable[i] = e;

                    e = next;

                } while (e != null);

            }

        }

    }

 

=============================     華麗的分割線   ==============

hashmap的擴容機制

一、當咱們往hashmap中put元素的時候,先根據key的hash值獲得這個元素在 數組中的位置(即下標),而後就能夠把這個元素放到對應的位置中了。若是這個元素所在的位子上已經存放有其餘元素了,那麼在同一個位子上的元素將以鏈表的 形式存放,新加入的放在鏈頭,好比a->b->c,新加入的d放到a的位置前面,最早加入的放在鏈尾,也就是c。最後變成d->a->b->c,從hashmap中get元素時,首先計算key的hashcode,找到數組中對應位置的某一元素, 而後經過key的equals方法在對應位置的鏈表中找到須要的元素。

二、

在hashmap中要找到某個元素,須要根據key的hash值來求得對應數組中的位置。如何計算這個位置就是hash算法。前面說過hashmap的數據結構是數組和鏈表的結合,因此咱們固然但願這個hashmap裏面的元素位置儘可能的分佈均勻些,儘可能使得每一個位置上的元素數量只有一個,那麼當咱們用hash算法求得這個位置 的時候,立刻就能夠知道對應位置的元素就是咱們要的,而不用再去遍歷鏈表。因此咱們首先想到的就是把hashcode對數組長度取模運算,這樣一來,元素的分佈相對來講是比較均勻的。可是,「模」運算的消耗仍是比較大的,能不能找一種更快速,消耗更小的方式那?java中時這樣作的,

Java代碼

staticintindexFor(inth,intlength){ 
returnh&(length-1); 
}

首 先算得key得hashcode值,而後跟數組的長度-1作一次「與」運算(&)。看上去很簡單,其實比較有玄機。好比數組的長度是2的4次方, 那麼hashcode就會和2的4次方-1作「與」運算。不少人都有這個疑問,爲何hashmap的數組初始化大小都是2的次方大小時,hashmap 的效率最高,我以2的4次方舉例,來解釋一下爲何數組大小爲2的冪時hashmap訪問的性能最高。 看下圖,左邊兩組是數組長度爲16(2的4次方),右邊兩組是數組長度爲15。兩組的hashcode均爲8和9,可是很明顯,當它們和1110「與」的 時候,產生了相同的結果,也就是說它們會定位到數組中的同一個位置上去,這就產生了碰撞,8和9會被放到同一個鏈表上,那麼查詢的時候就須要遍歷這個鏈 表,獲得8或者9,這樣就下降了查詢的效率。同時,咱們也能夠發現,當數組長度爲15的時候,hashcode的值會與14(1110)進行「與」,那麼 最後一位永遠是0,而0001,0011,0101,1001,1011,0111,1101這幾個位置永遠都不能存放元素了,空間浪費至關大,更糟的是 這種狀況中,數組可使用的位置比數組長度小了不少,這意味着進一步增長了碰撞的概率,減慢了查詢的效率!因此說,當數組長度爲2的n次冪的時候,不一樣的key算得得index相同的概率較小,那麼數據在數組上分佈就比較均勻,也就是說碰撞的概率小,相對的,查詢的時候就不用遍歷某個位置上的鏈表,這樣查詢效率也就較高了。說到這裏,咱們再回頭看一下hashmap中默認的數組大小是多少,查看源代碼能夠得知是16,爲何是16,而不是15,也不是20呢,看到上面 annegu的解釋以後咱們就清楚了吧,顯然是由於16是2的整數次冪的緣由,在小數據量的狀況下16比15和20更能減小key之間的碰撞,而加快查詢 的效率。

三、

當hashmap中的元素愈來愈多的時候,碰撞的概率也就愈來愈高(由於數組的長度是固定的),因此爲了提升查詢的效率,就要對hashmap的數組進行 擴容,數組擴容這個操做也會出如今ArrayList中,因此這是一個通用的操做,不少人對它的性能表示過懷疑,不過想一想咱們的「均攤」原理,就釋然了, 而在hashmap數組擴容以後,最消耗性能的點就出現了:原數組中的數據必須從新計算其在新數組中的位置,並放進去,這就是resize。

那麼hashmap何時進行擴容呢?當hashmap中的元素個數超過數組大小*loadFactor時,就會進行數組擴容,loadFactor的 默認值爲0.75,也就是說,默認狀況下,數組大小爲16,那麼當hashmap中元素個數超過16*0.75=12的時候,就把數組的大小擴展爲 2*16=32,即擴大一倍,而後從新計算每一個元素在數組中的位置,而這是一個很是消耗性能的操做,因此若是咱們已經預知hashmap中元素的個數,那 麼預設元素的個數可以有效的提升hashmap的性能。

好比說,咱們有1000個元素new HashMap(1000), 可是理論上來說new HashMap(1024)更合適,不過上面annegu已經說過,即便是1000,hashmap也自動會將其設置爲1024。 可是new HashMap(1024)還不是更合適的,由於0.75*1000 < 1000, 也就是說爲了讓0.75 * size > 1000, 咱們必須這樣new HashMap(2048)才最合適,既考慮了&的問題,也避免了resize的問題。

 

======================= 華麗的分割線     =====================================

哈希表及處理hash衝突的方法

看了ConcurrentHashMap的實現, 使用的是拉鍊法.

雖然咱們不但願發生衝突,但實際上發生衝突的可能性還是存在的。當關鍵字值域遠大於哈希表的長度,並且事先並不知道關鍵字的具體取值時。衝突就不免會發生。

另外,當關鍵字的實際取值大於哈希表的長度時,並且表中已裝滿了記錄,若是插入一個新記錄,不只發生衝突,並且還會發生溢出。

所以,處理衝突和溢出是哈希技術中的兩個重要問題。

 

 

哈希法又稱散列法、雜湊法以及關鍵字地址計算法等,相應的表稱爲哈希表。這種方法的基本思想是:首先在元素的關鍵字k和元素的存儲位置p之間創建一個對應關係f,使得p=f(k),f稱爲哈希函數。建立哈希表時,把關鍵字爲k的元素直接存入地址爲f(k)的單元;之後當查找關鍵字爲k的元素時,再利用哈希函數計算出該元素的存儲位置p=f(k),從而達到按關鍵字直接存取元素的目的。

   當關鍵字集合很大時,關鍵字值不一樣的元素可能會映象到哈希表的同一地址上,即 k1≠k2 ,但 H(k1)=H(k2),這種現象稱爲衝突,此時稱k1和k2爲同義詞。實際中,衝突是不可避免的,只能經過改進哈希函數的性能來減小衝突。

綜上所述,哈希法主要包括如下兩方面的內容:

 1)如何構造哈希函數

 2)如何處理衝突。

8.4.1   哈希函數的構造方法

    構造哈希函數的原則是:①函數自己便於計算;②計算出來的地址分佈均勻,即對任一關鍵字k,f(k) 對應不一樣地址的機率相等,目的是儘量減小衝突。

下面介紹構造哈希函數經常使用的五種方法。

1. 數字分析法

      若是事先知道關鍵字集合,而且每一個關鍵字的位數比哈希表的地址碼位數多時,能夠從關鍵字中選出分佈較均勻的若干位,構成哈希地址。例如,有80個記錄,關鍵字爲8位十進制整數d1d2d3…d7d8,如哈希表長取100,則哈希表的地址空間爲:00~99。假設通過分析,各關鍵字中 d4和d7的取值分佈較均勻,則哈希函數爲:h(key)=h(d1d2d3…d7d8)=d4d7。例如,h(81346532)=43,h(81301367)=06。相反,假設通過分析,各關鍵字中 d1和d8的取值分佈極不均勻, d1 都等於5,d8 都等於2,此時,若是哈希函數爲:h(key)=h(d1d2d3…d7d8)=d1d8,則全部關鍵字的地址碼都是52,顯然不可取。

2. 平方取中法

當沒法肯定關鍵字中哪幾位分佈較均勻時,能夠先求出關鍵字的平方值,而後按須要取平方值的中間幾位做爲哈希地址。這是由於:平方後中間幾位和關鍵字中每一位都相關,故不一樣關鍵字會以較高的機率產生不一樣的哈希地址。

例:咱們把英文字母在字母表中的位置序號做爲該英文字母的內部編碼。例如K的內部編碼爲11,E的內部編碼爲05,Y的內部編碼爲25,A的內部編碼爲01, B的內部編碼爲02。由此組成關鍵字「KEYA」的內部代碼爲11052501,同理咱們能夠獲得關鍵字「KYAB」、「AKEY」、「BKEY」的內部編碼。以後對關鍵字進行平方運算後,取出第7到第9位做爲該關鍵字哈希地址,如圖8.23所示。

 

 

關鍵字

內部編碼

內部編碼的平方值

H(k)關鍵字的哈希地址

KEYA

11050201

122157778355001

778

KYAB

11250102

126564795010404

795

AKEY

01110525

001233265775625

265

BKEY

02110525

004454315775625

315

圖8.23平方取中法求得的哈希地址

3. 分段疊加法

      這種方法是按哈希表地址位數將關鍵字分紅位數相等的幾部分(最後一部分能夠較短),而後將這幾部分相加,捨棄最高進位後的結果就是該關鍵字的哈希地址。具體方法有摺疊法移位法。移位法是將分割後的每部分低位對齊相加,摺疊法是從一端向另外一端沿分割界來回摺疊(奇數段爲正序,偶數段爲倒序),而後將各段相加。例如:key=12360324711202065,哈希表長度爲1000,則應把關鍵字分紅3位一段,在此捨去最低的兩位65,分別進行移位疊加和摺疊疊加,求得哈希地址爲105和907,如圖8.24所示。

 

 

1   2   3                    1   2   3

6   0   3                    3   0   6

2   4   7                    2   4   7

1   1   2                    2   1   1

+)   0   2   0               +)  0   2   0

        ————————            —————————

        1   1   0   5                    9   0   7

 

(a)移位疊加                    (b) 摺疊疊加

 

                      圖8.24 由疊加法求哈希地址

 

4. 除留餘數法

假設哈希表長爲m,p爲小於等於m的最大素數,則哈希函數爲

h(k)=k  %  p ,其中%爲模p取餘運算。

例如,已知待散列元素爲(18,75,60,43,54,90,46),表長m=10,p=7,則有

    h(18)=18 % 7=4    h(75)=75 % 7=5    h(60)=60 % 7=4   

    h(43)=43 % 7=1    h(54)=54 % 7=5    h(90)=90 % 7=6   

    h(46)=46 % 7=4

此時衝突較多。爲減小衝突,可取較大的m值和p值,如m=p=13,結果以下:

    h(18)=18 % 13=5    h(75)=75 % 13=10    h(60)=60 % 13=8    

    h(43)=43 % 13=4    h(54)=54 % 13=2    h(90)=90 % 13=12   

    h(46)=46 % 13=7

此時沒有衝突,如圖8.25所示。

 

0      1      2     3     4     5      6     7     8     9     10     11    12

 

 

 

54

 

43

18

 

46

60

 

75

 

90

                      圖8.25  除留餘數法求哈希地址

 

5. 僞隨機數法

    採用一個僞隨機函數作哈希函數,即h(key)=random(key)。

在實際應用中,應根據具體狀況,靈活採用不一樣的方法,並用實際數據測試它的性能,以便作出正確斷定。一般應考慮如下五個因素 :

l         計算哈希函數所需時間 (簡單)。

l         關鍵字的長度。

l         哈希表大小。

l         關鍵字分佈狀況。

l         記錄查找頻率

8.4.2   處理衝突的方法

   經過構造性能良好的哈希函數,能夠減小衝突,但通常不可能徹底避免衝突,所以解決衝突是哈希法的另外一個關鍵問題。建立哈希表和查找哈希表都會遇到衝突,兩種狀況下解決衝突的方法應該一致。下面以建立哈希表爲例,說明解決衝突的方法。經常使用的解決衝突方法有如下四種:

1.         開放定址法

這種方法也稱再散列法,其基本思想是:當關鍵字key的哈希地址p=H(key)出現衝突時,以p爲基礎,產生另外一個哈希地址p1,若是p1仍然衝突,再以p爲基礎,產生另外一個哈希地址p2,…,直到找出一個不衝突的哈希地址pi ,將相應元素存入其中。這種方法有一個通用的再散列函數形式:

          Hi=(H(key)+di)% m   i=1,2,…,n

    其中H(key)爲哈希函數,m 爲表長,di稱爲增量序列。增量序列的取值方式不一樣,相應的再散列方式也不一樣。主要有如下三種:

l         線性探測再散列

    dii=1,2,3,…,m-1

這種方法的特色是:衝突發生時,順序查看錶中下一單元,直到找出一個空單元或查遍全表。

l         二次探測再散列

    di=12,-12,22,-22,…,k2,-k2    ( k<=m/2 )

    這種方法的特色是:衝突發生時,在表的左右進行跳躍式探測,比較靈活。

l         僞隨機探測再散列

    di=僞隨機數序列。

具體實現時,應創建一個僞隨機數發生器,(如i=(i+p) % m),並給定一個隨機數作起點。

例如,已知哈希表長度m=11,哈希函數爲:H(key)= key  %  11,則H(47)=3,H(26)=4,H(60)=5,假設下一個關鍵字爲69,則H(69)=3,與47衝突。若是用線性探測再散列處理衝突,下一個哈希地址爲H1=(3 + 1)% 11 = 4,仍然衝突,再找下一個哈希地址爲H2=(3 + 2)% 11 = 5,仍是衝突,繼續找下一個哈希地址爲H3=(3 + 3)% 11 = 6,此時再也不衝突,將69填入5號單元,參圖8.26 (a)。若是用二次探測再散列處理衝突,下一個哈希地址爲H1=(3 + 12)% 11 = 4,仍然衝突,再找下一個哈希地址爲H2=(3 - 12)% 11 = 2,此時再也不衝突,將69填入2號單元,參圖8.26 (b)。若是用僞隨機探測再散列處理衝突,且僞隨機數序列爲:2,5,9,……..,則下一個哈希地址爲H1=(3 + 2)% 11 = 5,仍然衝突,再找下一個哈希地址爲H2=(3 + 5)% 11 = 8,此時再也不衝突,將69填入8號單元,參圖8.26 (c)。

 

 

0        1       2      3      4      5       6      7      8       9      10    

 

 

 

 

47

26

60

69

 

 

 

 

         (a) 用線性探測再散列處理衝突

 

 

0        1       2      3      4      5       6      7      8       9      10    

 

 

 

69

47

26

60

 

 

 

 

 

         (b) 用二次探測再散列處理衝突

 

 

0        1       2      3      4      5       6      7      8       9      10    

 

 

 

 

47

26

60

 

 

69

 

 

         (c) 用僞隨機探測再散列處理衝突

 

                      圖8.26開放地址法處理衝突

從上述例子能夠看出,線性探測再散列容易產生「二次彙集」,即在處理同義詞的衝突時又致使非同義詞的衝突。例如,當表中i, i+1 ,i+2三個單元已滿時,下一個哈希地址爲i, 或i+1 ,或i+2,或i+3的元素,都將填入i+3這同一個單元,而這四個元素並不是同義詞。線性探測再散列的優勢是:只要哈希表不滿,就必定能找到一個不衝突的哈希地址,而二次探測再散列和僞隨機探測再散列則不必定。

2.         再哈希法

    這種方法是同時構造多個不一樣的哈希函數:

    Hi=RH1(key)  i=1,2,…,k

當哈希地址Hi=RH1(key)發生衝突時,再計算Hi=RH2(key)……,直到衝突再也不產生。這種方法不易產生彙集,但增長了計算時間。

3.         鏈地址法

    這種方法的基本思想是將全部哈希地址爲i的元素構成一個稱爲同義詞鏈的單鏈表,並將單鏈表的頭指針存在哈希表的第i個單元中,於是查找、插入和刪除主要在同義詞鏈中進行。鏈地址法適用於常常進行插入和刪除的狀況。

例如,已知一組關鍵字(32,40,36,53,16,46,71,27,42,24,49,64),哈希表長度爲13,哈希函數爲:H(key)= key % 13,則用鏈地址法處理衝突的結果如圖8.27所示:

 

 
  哈希表及處理衝突的方法(轉) - 另外一片天空 - 仰望天空

 

圖8.27  鏈地址法處理衝突時的哈希表

 

本例的平均查找長度 ASL=(1*7+2*4+3*1)=1.5

 

 

         

(2)拉鍊法的優勢

與開放定址法相比,拉鍊法有以下幾個優勢:

①拉鍊法處理衝突簡單,且無堆積現象,即非同義詞決不會發生衝突,所以平均查找長度較短;

②因爲拉鍊法中各鏈表上的結點空間是動態申請的,故它更適合於造表前沒法肯定表長的狀況;

③開放定址法爲減小衝突,要求裝填因子α較小,故當結點規模較大時會浪費不少空間。而拉鍊法中可取α≥1,且結點較大時,拉鍊法中增長的指針域可忽略不計,所以節省空間;

④在用拉鍊法構造的散列表中,刪除結點的操做易於實現。只要簡單地刪去鏈表上相應的結點便可。而對開放地址法構造的散列表,刪除結點不能簡單地將被刪結 點的空間置爲空,不然將截斷在它以後填人散列表的同義詞結點的查找路徑。這是由於各類開放地址法中,空地址單元(即開放地址)都是查找失敗的條件。所以在 用開放地址法處理衝突的散列表上執行刪除操做,只能在被刪結點上作刪除標記,而不能真正刪除結點。

 

(3)拉鍊法的缺點

     拉鍊法的缺點是:指針須要額外的空間,故當結點規模較小時,開放定址法較爲節省空間,而若將節省的指針空間用來擴大散列表的規模,可以使裝填因子變小,這又減小了開放定址法中的衝突,從而提升平均查找速度。??????

 

4、創建公共溢出區

這種方法的基本思想是:將哈希表分爲基本表溢出表兩部分,凡是和基本表發生衝突的元素,一概填入溢出表

相關文章
相關標籤/搜索