關於java中set集合的除重原理

Set<E>是單列集合,裏面元素不可重複,元素存取無序,元素無索引的根節點
 創建對象:Set<E> 集合名 = new HashSet<E>();

Set集合的除重,是發生在調用add()方法時。下面,簡單的看一下add()方法的底層代碼

在HashSet裏的add()方法源碼如下

public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

我們可以看到,返回的是 map裏put方法返回值跟null的比較。若添加成功,則返回True,失敗是Fale。

看一下map裏put方法,源碼如下:

public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key);
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            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;
    }

這裏只粗略的講一下Set集合是如何除重的,所以很多跟返回值無關的代碼不給予解釋。

整個代碼的功能分爲

一、非空校驗,Set集合只允許一個null。

二、遍歷查找相同元素

三、返回null ,add()方法返回  null == null  即true。

以下將代碼分塊解釋:

public V put(K key, V value) {
    第一部分:
     

  if (table == EMPTY_TABLE) {
        inflateTable(threshold);
        }


        
    第二部分:
       

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

       key == null 非空校驗 -----> key的非空校驗
       判斷傳入的參數是否爲空

若你傳入的元素爲空的話 --> return putForNullKey(value); -->結束put方法並把方法的結果返回給調用者(add)
                    putForNullKey(value)-->見名知意 專門爲放null值的方法
            第二板塊的作用:
                判斷你傳入的參數是不是null:
                    若不是null,繼續下面的判斷
                    若是null,把這個null值存到集合中,當是當你第二次傳入null值的時候,不允許再添加null
            -->Set集合可以有null值的元素,但是只能有一個!!!



    第三部分:
       

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


       hash(Object k)方法如下

 final int hash(Object k) {
            int h = hashSeed;
            if (0 != h && k instanceof String) {
                return sun.misc.Hashing.stringHash32((String) k);
            }

            h ^= k.hashCode();

            // This function ensures that hashCodes that differ only by
            // constant multiples at each bit position have a bounded
            // number of collisions (approximately 8 at default load factor).
            h ^= (h >>> 20) ^ (h >>> 12);
            return h ^ (h >>> 7) ^ (h >>> 4);
    }
        我們可以看到,hash(Object k)的返回值跟hashCode()有關.我們在自己定義一個類時,對hashCode()進行了重寫,
        hashCode()返回了運算後屬性值的地址,通過比較hashCode()的值來判斷是否是同一個對象.


    第四部分:
       

for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
for (Entry<K,V> e = table[i]; e != null; e = e.next)  

這個for循環是在做遍歷,遍歷老的Set集合,找有沒有重複的元素,e接收了老的Set集合
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
 
 左邊:e.hash == hash
        通過比較屬性值的地址,判斷新傳入的Set集合是否跟老Set集合的某個是同一個對象

 右邊:((k = e.key) == key || key.equals(k)))
       左邊:(k = e.key) == key
        這是將老Set集合中的key的地址值賦給k,然後和新傳入的key的地址值比較.
        對於引用數據類型的對象,每次創建對象的地址值是不同的,所以k!=key
        對於集合包裝類的對象,拿對象名比較時,會拆箱比較基本數據類型的值,所以存在k == key.
        右邊:key.equals(k)
         這裏重寫了equals方法,比較的是屬性值是否相同.
           
 //整理邏輯
        若e.hash == hash,則說明對象的運算後屬性地址值相同                
            情況一:集合包裝類,判斷(k = e.key) == key,若相同,則執行if的語句,
返回跟新傳入Set相同的老Set集合,即return oldValue
            情況二:引用數據類型,判斷key.equals(k)),若相同,則屬性值相同,執行if的語句,
返回跟新傳入Set相同的老Set集合,即return oldValue
      
        若e.hash != hash,則說明對象的運算後屬性地址值不相同,不執行if的語句   一定不是相同的對象
        

Integer類型的hashCode怎麼說? 
查看Integer源碼可知,Integer的hashCode方法return 是value,也就是Integer的值。        
        
    第五部分:    

 modCount++;
 addEntry(hash, key, value, i);
 return null;

    //新傳入的Set集合,非空且與老集合沒有相同的元素,則返回null.
    }    
  
    也就是說,創建泛型是自己定義對象的Set集合時,要重寫hashCode和equals (Set集合的去重依賴重寫這兩個方法)

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