国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學(xué)院 > 開發(fā)設(shè)計(jì) > 正文

通過分析JDK源代碼研究Hash存儲機(jī)制

2019-11-14 15:26:32
字體:
供稿:網(wǎng)友

原文出處: 李剛

通過 HashMap、HashSet 的源代碼分析其 Hash 存儲機(jī)制

實(shí)際上,HashSet 和 HashMap 之間有很多相似之處,對于 HashSet 而言,系統(tǒng)采用 Hash 算法決定集合元素的存儲位置,這樣可以保證能快速存、取集合元素;對于 HashMap 而言,系統(tǒng) key-value 當(dāng)成一個(gè)整體進(jìn)行處理,系統(tǒng)總是根據(jù) Hash 算法來計(jì)算 key-value 的存儲位置,這樣可以保證能快速存、取 Map 的 key-value 對。

在介紹集合存儲之前需要指出一點(diǎn):雖然集合號稱存儲的是 java 對象,但實(shí)際上并不會真正將 Java 對象放入 Set 集合中,只是在 Set 集合中保留這些對象的引用而言。也就是說:Java 集合實(shí)際上是多個(gè)引用變量所組成的集合,這些引用變量指向?qū)嶋H的 Java 對象。

HashMap 的存儲實(shí)現(xiàn)

當(dāng)程序試圖將多個(gè) key-value 放入 HashMap 中時(shí),以如下代碼片段為例:

1
2
3
4
HashMap<String , Double> map = new HashMap<String , Double>();
map.put("語文" , 80.0);
map.put("數(shù)學(xué)" , 89.0);
map.put("英語" , 78.2);

HashMap 采用一種所謂的“Hash 算法”來決定每個(gè)元素的存儲位置。

當(dāng)程序執(zhí)行 map.put(“語文” , 80.0); 時(shí),系統(tǒng)將調(diào)用”語文”的 hashCode() 方法得到其 hashCode 值——每個(gè) Java 對象都有 hashCode() 方法,都可通過該方法獲得它的 hashCode 值。得到這個(gè)對象的 hashCode 值之后,系統(tǒng)會根據(jù)該 hashCode 值來決定該元素的存儲位置。

我們可以看 HashMap 類的 put(K key , V value) 方法的源代碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public V put(K key, V value)
{
    // 如果 key 為 null,調(diào)用 putForNullKey 方法進(jìn)行處理
    if (key == null)
        return putForNullKey(value);
    // 根據(jù) key 的 keyCode 計(jì)算 Hash 值
    int hash = hash(key.hashCode());
    // 搜索指定 hash 值在對應(yīng) table 中的索引
<strong> int i = indexFor(hash, table.length);</strong>
    // 如果 i 索引處的 Entry 不為 null,通過循環(huán)不斷遍歷 e 元素的下一個(gè)元素
    for (Entry&lt;K,V&gt; e = table[i]; e != null; e = e.next)
    {
        Object k;
        // 找到指定 key 與需要放入的 key 相等(hash 值相同
        // 通過 equals 比較放回 true)
        if (e.hash == hash &amp;&amp; ((k = e.key) == key
            || key.equals(k)))
        {
            V oldValue = e.value;
            e.value = value;
            e.recordaccess(this);
            return oldValue;
        }
    }
    // 如果 i 索引處的 Entry 為 null,表明此處還沒有 Entry
    modCount++;
    // 將 key、value 添加到 i 索引處
    addEntry(hash, key, value, i);
    return null;
}

上面程序中用到了一個(gè)重要的內(nèi)部接口:Map.Entry,每個(gè) Map.Entry 其實(shí)就是一個(gè) key-value 對。從上面程序中可以看出:當(dāng)系統(tǒng)決定存儲 HashMap 中的 key-value 對時(shí),完全沒有考慮 Entry 中的 value,僅僅只是根據(jù) key 來計(jì)算并決定每個(gè) Entry 的存儲位置。這也說明了前面的結(jié)論:我們完全可以把 Map 集合中的 value 當(dāng)成 key 的附屬,當(dāng)系統(tǒng)決定了 key 的存儲位置之后,value 隨之保存在那里即可。

上面方法提供了一個(gè)根據(jù) hashCode() 返回值來計(jì)算 Hash 碼的方法:hash(),這個(gè)方法是一個(gè)純粹的數(shù)學(xué)計(jì)算,其方法如下:

1
2
3
4
5
static int hash(int h)
{
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
}

對于任意給定的對象,只要它的 hashCode() 返回值相同,那么程序調(diào)用 hash(int h) 方法所計(jì)算得到的 Hash 碼值總是相同的。接下來程序會調(diào)用 indexFor(int h, int length) 方法來計(jì)算該對象應(yīng)該保存在 table 數(shù)組的哪個(gè)索引處。indexFor(int h, int length) 方法的代碼如下:

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

這個(gè)方法非常巧妙,它總是通過 h &(table.length -1) 來得到該對象的保存位置——而 HashMap 底層數(shù)組的長度總是 2 的 n 次方,這一點(diǎn)可參看后面關(guān)于 HashMap 構(gòu)造器的介紹。

當(dāng) length 總是 2 的倍數(shù)時(shí),h & (length-1)將是一個(gè)非常巧妙的設(shè)計(jì):假設(shè) h=5,length=16, 那么 h & length - 1 將得到 5;如果 h=6,length=16, 那么 h & length - 1 將得到 6 &hellip;…如果 h=15,length=16, 那么 h & length - 1 將得到 15;但是當(dāng) h=16 時(shí) , length=16 時(shí),那么 h & length - 1 將得到 0 了;當(dāng) h=17 時(shí) , length=16 時(shí),那么 h & length - 1 將得到 1 了……這樣保證計(jì)算得到的索引值總是位于 table 數(shù)組的索引之內(nèi)。

根據(jù)上面 put 方法的源代碼可以看出,當(dāng)程序試圖將一個(gè) key-value 對放入 HashMap 中時(shí),程序首先根據(jù)該 key 的 hashCode() 返回值決定該 Entry 的存儲位置:如果兩個(gè) Entry 的 key 的 hashCode() 返回值相同,那它們的存儲位置相同。如果這兩個(gè) Entry 的 key 通過 equals 比較返回 true,新添加 Entry 的 value 將覆蓋集合中原有 Entry 的 value,但 key 不會覆蓋。如果這兩個(gè) Entry 的 key 通過 equals 比較返回 false,新添加的 Entry 將與集合中原有 Entry 形成 Entry 鏈,而且新添加的 Entry 位于 Entry 鏈的頭部——具體說明繼續(xù)看 addEntry() 方法的說明。

當(dāng)向 HashMap 中添加 key-value 對,由其 key 的 hashCode() 返回值決定該 key-value 對(就是 Entry 對象)的存儲位置。當(dāng)兩個(gè) Entry 對象的 key 的 hashCode() 返回值相同時(shí),將由 key 通過 eqauls() 比較值決定是采用覆蓋行為(返回 true),還是產(chǎn)生 Entry 鏈(返回 false)。

上面程序中還調(diào)用了 addEntry(hash, key, value, i); 代碼,其中 addEntry 是 HashMap 提供的一個(gè)包訪問權(quán)限的方法,該方法僅用于添加一個(gè) key-value 對。下面是該方法的代碼:

1
2
3
4
5
6
7
8
9
10
11
void addEntry(int hash, K key, V value, int bucketIndex)
{
    // 獲取指定 bucketIndex 索引處的 Entry
    Entry<K,V> e = table[bucketIndex];     // ①
    // 將新創(chuàng)建的 Entry 放入 bucketIndex 索引處,并讓新的 Entry 指向原來的 Entry
    table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
    // 如果 Map 中的 key-value 對的數(shù)量超過了極限
    if (size++ >= threshold)
        // 把 table 對象的長度擴(kuò)充到 2 倍。
        resize(2 * table.length);    // ②
}

上面方法的代碼很簡單,但其中包含了一個(gè)非常優(yōu)雅的設(shè)計(jì):系統(tǒng)總是將新添加的 Entry 對象放入 table 數(shù)組的 bucketIndex 索引處——如果 bucketIndex 索引處已經(jīng)有了一個(gè) Entry 對象,那新添加的 Entry 對象指向原有的 Entry 對象(產(chǎn)生一個(gè) Entry 鏈),如果 bucketIndex 索引處沒有 Entry 對象,也就是上面程序①號代碼的 e 變量是 null,也就是新放入的 Entry 對象指向 null,也就是沒有產(chǎn)生 Entry 鏈。

Hash 算法的性能選項(xiàng)

根據(jù)上面代碼可以看出,在同一個(gè) bucket 存儲 Entry 鏈的情況下,新放入的 Entry 總是位于 bucket 中,而最早放入該 bucket 中的 Entry 則位于這個(gè) Entry 鏈的最末端。

上面程序中還有這樣兩個(gè)變量:

  • size:該變量保存了該 HashMap 中所包含的 key-value 對的數(shù)量。
  • threshold:該變量包含了 HashMap 能容納的 key-value 對的極限,它的值等于 HashMap 的容量乘以負(fù)載因子(load factor)。

從上面程序中②號代碼可以看出,當(dāng) size++ >= threshold 時(shí),HashMap 會自動(dòng)調(diào)用 resize 方法擴(kuò)充 HashMap 的容量。每擴(kuò)充一次,HashMap 的容量就增大一倍。

上面程序中使用的 table 其實(shí)就是一個(gè)普通數(shù)組,每個(gè)數(shù)組都有一個(gè)固定的長度,這個(gè)數(shù)組的長度就是 HashMap 的容量。HashMap 包含如下幾個(gè)構(gòu)造器:

  • HashMap():構(gòu)建一個(gè)初始容量為 16,負(fù)載因子為 0.75 的 HashMap。
  • HashMap(int initialCapacity):構(gòu)建一個(gè)初始容量為 initialCapacity,負(fù)載因子為 0.75 的 HashMap。
  • HashMap(int initialCapacity, float loadFactor):以指定初始容量、指定的負(fù)載因子創(chuàng)建一個(gè) HashMap。

當(dāng)創(chuàng)建一個(gè) HashMap 時(shí),系統(tǒng)會自動(dòng)創(chuàng)建一個(gè) table 數(shù)組來保存 HashMap 中的 Entry,下面是 HashMap 中一個(gè)構(gòu)造器的代碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 以指定初始化容量、負(fù)載因子創(chuàng)建 HashMap
public HashMap(int initialCapacity, float loadFactor)
{
    // 初始容量不能為負(fù)數(shù)
    if (initialCapacity < 0)
        throw new IllegalArgumentException(
       "Illegal initial capacity: " +
            initialCapacity);
    // 如果初始容量大于最大容量,讓出示容量
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    // 負(fù)載因子必須大于 0 的數(shù)值
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException(
        loadFactor);
    // 計(jì)算出大于 initialCapacity 的最小的 2 的 n 次方值。
    int capacity = 1;
    while (capacity < initialCapacity)
        capacity <<= 1;
    this.loadFactor = loadFactor;
    // 設(shè)置容量極限等于容量 * 負(fù)載因子
    threshold = (int)(capacity * loadFactor);
    // 初始化 table 數(shù)組
    table = new Entry[capacity];            // ①
    init();
}

上面代碼中粗體字代碼包含了一個(gè)簡潔的代碼實(shí)現(xiàn):找出大于 initialCapacity 的、最小的 2 的 n 次方值,并將其作為 HashMap 的實(shí)際容量(由 capacity 變量保存)。例如給定 initialCapacity 為 10,那么該 HashMap 的實(shí)際容量就是 16。

程序①號代碼處可以看到:table 的實(shí)質(zhì)就是一個(gè)數(shù)組,一個(gè)長度為 capacity 的數(shù)組。

對于 HashMap 及其子類而言,它們采用 Hash 算法來決定集合中元素的存儲位置。當(dāng)系統(tǒng)開始初始化 HashMap 時(shí),系統(tǒng)會創(chuàng)建一個(gè)長度為 capacity 的 Entry 數(shù)組,這個(gè)數(shù)組里可以存儲元素的位置被稱為“桶(bucket)”,每個(gè) bucket 都有其指定索引,系統(tǒng)可以根據(jù)其索引快速訪問該 bucket 里存儲的元素。

無論何時(shí),HashMap 的每個(gè)“桶”只存儲一個(gè)元素(也就是一個(gè) Entry),由于 Entry 對象可以包含一個(gè)引用變量(就是 Entry 構(gòu)造器的的最后一個(gè)參數(shù))用于指向下一個(gè) Entry,因此可能出現(xiàn)的情況是:HashMap 的 bucket 中只有一個(gè) Entry,但這個(gè) Entry 指向另一個(gè) Entry ——這就形成了一個(gè) Entry 鏈。如圖 1 所示:

圖 1. HashMap 的存儲示意

圖 1. HashMap 的存儲示意

HashMap 的讀取實(shí)現(xiàn)

當(dāng) HashMap 的每個(gè) bucket 里存儲的 Entry 只是單個(gè) Entry ——也就是沒有通過指針產(chǎn)生 Entry 鏈時(shí),此時(shí)的 HashMap 具有最好的性能:當(dāng)程序通過 key 取出對應(yīng) value 時(shí),系統(tǒng)只要先計(jì)算出該 key 的 hashCode() 返回值,在根據(jù)該 hashCode 返回值找出該 key 在 table 數(shù)組中的索引,然后取出該索引處的 Entry,最后返回該 key 對應(yīng)的 value 即可???HashMap 類的 get(K key) 方法代碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public V get(Object key)
{
    // 如果 key 是 null,調(diào)用 getForNullKey 取出對應(yīng)的 value
    if (key == null)
        return getForNullKey();
    // 根據(jù)該 key 的 hashCode 值計(jì)算它的 hash 碼
    int hash = hash(key.hashCode());
    // 直接取出 table 數(shù)組中指定索引處的值,
    for (Entry<K,V> e = table[indexFor(hash, table.length)];
        e != null;
        // 搜索該 Entry 鏈的下一個(gè) Entr
        e = e.next)         // ①
    {
        Object k;
        // 如果該 Entry 的 key 與被搜索 key 相同
        if (e.hash == hash && ((k = e.key) == key
            || key.equals(k)))
            return e.value;
    }
    return null;
}

從上面代碼中可以看出,如果 HashMap 的每個(gè) bucket 里只有一個(gè) Entry 時(shí),HashMap 可以根據(jù)索引、快速地取出該 bucket 里的 Entry;在發(fā)生“Hash 沖突”的情況下,單個(gè) bucket 里存儲的不是一個(gè) Entry,而是一個(gè) Entry 鏈,系統(tǒng)只能必須按順序遍歷每個(gè) Entry,直到找到想搜索的 Entry 為止——如果恰好要搜索的 Entry 位于該 Entry 鏈的最末端(該 Entry 是最早放入該 bucket 中),那系統(tǒng)必須循環(huán)到最后才能找到該元素。

歸納起來簡單地說,HashMap 在底層將 key-value 當(dāng)成一個(gè)整體進(jìn)行處理,這個(gè)整體就是一個(gè) Entry 對象。HashMap 底層采用一個(gè) Entry[] 數(shù)組來保存所有的 key-value 對,當(dāng)需要存儲一個(gè) Entry 對象時(shí),會根據(jù) Hash 算法來決定其存儲位置;當(dāng)需要取出一個(gè) Entry 時(shí),也會根據(jù) Hash 算法找到其存儲位置,直接取出該 Entry。由此可見:HashMap 之所以能快速存、取它所包含的 Entry,完全類似于現(xiàn)實(shí)生活中母親從小教我們的:不同的東西要放在不同的位置,需要時(shí)才能快速找到它。

當(dāng)創(chuàng)建 HashMap 時(shí),有一個(gè)默認(rèn)的負(fù)載因子(load factor),其默認(rèn)值為 0.75,這是時(shí)間和空間成本上一種折衷:增大負(fù)載因子可以減少 Hash 表(就是那個(gè) Entry 數(shù)組)所占用的內(nèi)存空間,但會增加查詢數(shù)據(jù)的時(shí)間開銷,而查詢是最頻繁的的操作(HashMap 的 get() 與 put() 方法都要用到查詢);減小負(fù)載因子會提高數(shù)據(jù)查詢的性能,但會增加 Hash 表所占用的內(nèi)存空間。

掌握了上面知識之后,我們可以在創(chuàng)建 HashMap 時(shí)根據(jù)實(shí)際需要適當(dāng)?shù)卣{(diào)整 load factor 的值;如果程序比較關(guān)心空間開銷、內(nèi)存比較緊張,可以適當(dāng)?shù)卦黾迂?fù)載因子;如果程序比較關(guān)心時(shí)間開銷,內(nèi)存比較寬裕則可以適當(dāng)?shù)臏p少負(fù)載因子。通常情況下,程序員無需改變負(fù)載因子的值。

如果開始就知道 HashMap 會保存多個(gè) key-value 對,可以在創(chuàng)建時(shí)就使用較大的初始化容量,如果 HashMap 中 Entry 的數(shù)量一直不會超過極限容量(capacity * load factor),HashMap 就無需調(diào)用 resize() 方法重新分配 table 數(shù)組,從而保證較好的性能。當(dāng)然,開始就將初始容量設(shè)置太高可能會浪費(fèi)空間(系統(tǒng)需要?jiǎng)?chuàng)建一個(gè)長度為 capacity 的 Entry 數(shù)組),因此創(chuàng)建 HashMap 時(shí)初始化容量設(shè)置也需要小心對待。

HashSet 的實(shí)現(xiàn)

對于 HashSet 而言,它是基于 HashMap 實(shí)現(xiàn)的,HashSet 底層采用 HashMap 來保存所有元素,因此 HashSet 的實(shí)現(xiàn)比較簡單,查看 HashSet 的源代碼,可以看到如下代碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable
{
    // 使用 HashMap 的 key 保存 HashSet 中所有元素
    PRivate transient HashMap<E,Object> map;
    // 定義一個(gè)虛擬的 Object 對象作為 HashMap 的 value
    private static final Object PRESENT = new Object();
    ...
    // 初始化 HashSet,底層會初始化一個(gè) HashMap
    public HashSet()
    {
        map = new HashMap<E,Object>();
    }
    // 以指定的 initialCapacity、loadFactor 創(chuàng)建 HashSet
    // 其實(shí)就是以相應(yīng)的參數(shù)創(chuàng)建 HashMap
    public HashSet(int initialCapacity, float loadFactor)
    {
        map = new HashMap<E,Object>(initialCapacity, loadFactor);
    }
    public HashSet(int initialCapacity)
    {
        map = new HashMap<E,Object>(initialCapacity);
    }
    HashSet(int initialCapacity, float loadFactor, boolean dummy)
    {
        map = new LinkedHashMap<E,Object>(initialCapacity
            , loadFactor);
    }
    // 調(diào)用 map 的 keySet 來返回所有的 key
    public Iterator<E> iterator()
    {
        return map.keySet().iterator();
    }
    // 調(diào)用 HashMap 的 size() 方法返回 Entry 的數(shù)量,就得到該 Set 里元素的個(gè)數(shù)
    public int size()
    {
        return map.size();
    }
    // 調(diào)用 HashMap 的 isEmpty() 判斷該 HashSet 是否為空,
    // 當(dāng) HashMap 為空時(shí),對應(yīng)的 HashSet 也為空
    public boolean isEmpty()
    {
        return map.isEmpty();
    }
    // 調(diào)用 HashMap 的 containsKey 判斷是否包含指定 key
    //HashSet 的所有元素就是通過 HashMap 的 key 來保存的
    public boolean contains(Object o)
    {
        return map.containsKey(o);
    }
    // 將指定元素放入 HashSet 中,也就是將該元素作為 key 放入 HashMap
    public boolean add(E e)
    {
        return map.put(e, PRESENT) == null;
    }
    // 調(diào)用 HashMap 的 remove 方法刪除指定 Entry,也就刪除了 HashSet 中對應(yīng)的元素
    public boolean remove(Object o)
    {
        return map.remove(o)==PRESENT;
    }
    // 調(diào)用 Map 的 clear 方法清空所有 Entry,也就清空了 HashSet 中所有元素
    public void clear()
    {
        map.clear();
    }
    ...
}

由上面源程序可以看出,HashSet 的實(shí)現(xiàn)其實(shí)非常簡單,它只是封裝了一個(gè) HashMap 對象來存儲所有的集合元素,所有放入 HashSet 中的集合元素實(shí)際上由 HashMap 的 key 來保存,而 HashMap 的 value 則存儲了一個(gè) PRESENT,它是一個(gè)靜態(tài)的 Object 對象。

HashSet 的絕大部分方法都是通過調(diào)用 HashMap 的方法來實(shí)現(xiàn)的,因此 HashSet 和 HashMap 兩個(gè)集合在實(shí)現(xiàn)本質(zhì)上是相同的。

HashMap 的 put 與 HashSet 的 add

由于 HashSet 的 add() 方法添加集合元素時(shí)實(shí)際上轉(zhuǎn)變?yōu)檎{(diào)用 HashMap 的 put() 方法來添加 key-value 對,當(dāng)新放入 HashMap 的 Entry 中 key 與集合中原有 Entry 的 key 相同(hashCode() 返回值相等,通過 equals 比較也返回 true),新添加的 Entry 的 value 將覆蓋原來 Entry 的 value,但 key 不會有任何改變,因此如果向 HashSet 中添加一個(gè)已經(jīng)存在的元素,新添加的集合元素(底層由 HashMap 的 key 保存)不會覆蓋已有的集合元素。

掌握上面理論知識之后,接下來看一個(gè)示例程序,測試一下自己是否真正掌握了 HashMap 和 HashSet 集合的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class Name
{
    private String first;
    private String last;
 
    public Name(String first, String last)
    {
        this.first = first;
        this.last = last;
    }
 
    public boolean equals(Object o)
    {
        if (this == o)
        {
            return true;
        }
 
    if (o.getClass() == Name.class)
        {
            Name n = (Name)o;
            return n.first.equals(first)
                && n.last.equals(last);
        }
        return false;
    }
}
 
public class HashSetTest
{
    public static void main(String[] args)
    {
        Set<Name> s = new HashSet<Name>();
        s.add(new Name("abc", "123"));
        System.out.println(
            s.contains(new Name("abc", "123")));
    }
}

上面程序中向 HashSet 里添加了一個(gè) new Name(“abc”, “123″) 對象之后,立即通過程序判斷該 HashSet 是否包含一個(gè) new Name(“abc”, “123″) 對象。粗看上去,很容易以為該程序會輸出 true。

實(shí)際運(yùn)行上面程序?qū)⒖吹匠绦蜉敵?false,這是因?yàn)?HashSet 判斷兩個(gè)對象相等的標(biāo)準(zhǔn)除了要求通過 equals() 方法比較返回 true 之外,還要求兩個(gè)對象的 hashCode() 返回值相等。而上面程序沒有重寫 Name 類的 hashCode() 方法,兩個(gè) Name 對象的 hashCode() 返回值并不相同,因此 HashSet 會把它們當(dāng)成 2 個(gè)對象處理,因此程序返回 false。

由此可見,當(dāng)我們試圖把某個(gè)類的對象當(dāng)成 HashMap 的 key,或試圖將這個(gè)類的對象放入 HashSet 中保存時(shí),重寫該類的 equals(Object obj) 方法和 hashCode() 方法很重要,而且這兩個(gè)方法的返回值必須保持一致:當(dāng)該類的兩個(gè)的 hashCode() 返回值相同時(shí),它們通過 equals() 方法比較也應(yīng)該返回 true。通常來說,所有參與計(jì)算 hashCode() 返回值的關(guān)鍵屬性,都應(yīng)該用于作為 equals() 比較的標(biāo)準(zhǔn)。

如下程序就正確重寫了 Name 類的 hashCode() 和 equals() 方法,程序如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
class Name
{
    private String first;
    private String last;
    public Name(String first, String last)
    {
        this.first = first;
        this.last = last;
    }
    // 根據(jù) first 判斷兩個(gè) Name 是否相等
    public boolean equals(Object o)
    {
        if (this == o)
        {
            return true;
        }
        if (o.getClass() == Name.class)
        {
            Name n = (Name)o;
            return n.first.equals(first);
        }
        return false;
    }
 
    // 根據(jù) first 計(jì)算 Name 對象的 hashCode() 返回值
    public int hashCode()
    {
        return first.hashCode();
    }
 
    public String toString()
    {
        return "Name[first=" + first + ", last=" + last + "]";
    }
 }
 
 public class HashSetTest2
 {
    public static void main(String[] args)
    {
        HashSet<Name> set = new HashSet<Name>();
        set.add(new Name("abc" , "123"));
        set.add(new Name("abc" , "456"));
        System.out.println(set);
    }
}

上面程序中提供了一個(gè) Name 類,該 Name 類重寫了 equals() 和 toString() 兩個(gè)方法,這兩個(gè)方法都是根據(jù) Name 類的 first 實(shí)例變量來判斷的,當(dāng)兩個(gè) Name 對象的 first 實(shí)例變量相等時(shí),這兩個(gè) Name 對象的 hashCode() 返回值也相同,通過 equals() 比較也會返回 true。

程序主方法先將第一個(gè) Name 對象添加到 HashSet 中,該 Name 對象的 first 實(shí)例變量值為”abc”,接著程序再次試圖將一個(gè) first 為”abc”的 Name 對象添加到 HashSet 中,很明顯,此時(shí)沒法將新的 Name 對象添加到該 HashSet 中,因?yàn)榇颂幵噲D添加的 Name 對象的 first 也是” abc”,HashSet 會判斷此處新增的 Name 對象與原有的 Name 對象相同,因此無法添加進(jìn)入,程序在①號代碼處輸出 set 集合時(shí)將看到該集合里只包含一個(gè) Name 對象,就是第一個(gè)、last 為”123″的 Name 對象。

全能程序員交流QQ群290551701,群內(nèi)程序員都是來自,百度、阿里、京東、小米、去哪兒、餓了嗎、藍(lán)港等高級程序員 ,擁有豐富的經(jīng)驗(yàn)。加入我們,直線溝通技術(shù)大牛,最佳的學(xué)習(xí)環(huán)境,了解業(yè)內(nèi)的一手的資訊。如果你想結(jié)實(shí)大牛,那 就加入進(jìn)來,讓大牛帶你超神!


發(fā)表評論 共有條評論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 穆棱市| 桦南县| 彰化市| 藁城市| 荆州市| 沈阳市| 全椒县| 乌海市| 黄石市| 恩平市| 东至县| 红安县| 千阳县| 游戏| 榆林市| 灵寿县| 广宁县| 康定县| 黔西县| 十堰市| 天气| 宿州市| 永胜县| 高尔夫| 茶陵县| 松江区| 自贡市| 长兴县| 玉山县| 旺苍县| 铜川市| 商洛市| 海兴县| 北票市| 山阴县| 扎赉特旗| 平顺县| 宜良县| 安福县| 山阴县| 荣成市|