嘮嘮SE的集合-07——HashSet

7. HashSet

底層數據結構是哈希表(元素是鏈表的數組)java

關於這個結構,我以爲有必要用一張圖來解釋:數組

哈希表是一個數組,當一個數據(對象)要添加時,先計算對象的hashCode,來肯定它應該對應數組的哪一個位置,若是那個位置沒有數據,則該元素放到對應位置上;若是有數據,則繼續調用該對象的equals方法,與該位置的已有數據對比,若是返回true,則HashSet認爲這兩個數據是同樣的,就不容許添加;若是返回false,則在原位置以鏈表的形式繼續向下鏈接該要添加的數據。數據結構

 

因此說,哈希表依賴於哈希值存儲,也就是對象的hashCode方法ide

HashSet的特性:

HashSet不保證迭代的插入順序性,特別是不保證每次迭代的順序一致。this

比方說下面這段代碼,咱們作一個String的Set集合,看看每次輸出的結果:spa

import java.util.HashSet;

public class Test {
    public static void main(String[] args) {
        HashSet<String> set = new HashSet<>();
        set.add("嘿嘿");
        set.add("呵呵");
        set.add("嗯吶");
        set.add("哈哈");
        set.add("好的");
        set.add("呼呼");
        System.out.println(set);
    }
}
//運行結果
//[嘿嘿, 呵呵, 哈哈, 呼呼, 嗯吶, 好的]

跟add的順序果真不同。。。code

但所謂的迭代順序不肯定,並非幾回試驗就能夠試驗出來的,這取決於對象的hashCode和equals,還有哈希表的內部結構。更深刻的瞭解,戳:「不保證有序」和「保證無序」對象

 

既然基本數據的包裝類能夠,咱們來試試本身寫的自定義實體類吧:排序

import java.util.HashSet;

public class Test {
    public static void main(String[] args) {
        HashSet<Person> set = new HashSet<>();
        set.add(new Person(1, "辣條"));
        set.add(new Person(3, "冰棍"));
        set.add(new Person(4, "麪包"));
        set.add(new Person(2, "薯片"));
        set.add(new Person(2, "薯片"));
        set.add(new Person(2, "薯片"));
        for (Person person : set) {
            System.out.println(person);
        }
    }
}

class Person {
    private int id;
    private String name;
    public Person(int id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person [id=" + id + ", name=" + name + "]";
    }

    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

運行以後出現了下圖的異常狀況:接口

出現異常狀況的緣由:

添加功能底層依賴兩個方法:

    int hashCode()    

    boolean equals(Object obj)    

那咱們給Person類重寫hashCode方法,存儲Person:

import java.util.HashSet;

public class Test {
    public static void main(String[] args) {
        HashSet<Person> set = new HashSet<>();
        set.add(new Person(1, "辣條"));
        set.add(new Person(3, "冰棍"));
        set.add(new Person(4, "麪包"));
        set.add(new Person(2, "薯片"));
        set.add(new Person(2, "薯片"));
        set.add(new Person(2, "薯片"));
        for (Person person : set) {
            System.out.println(person);
        }
    }
}

class Person {
    private int id;
    private String name;
    public Person(int id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person [id=" + id + ", name=" + name + "]";
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + id;
        return result;
    }

    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

運行以後:

發現仍然異常,但彷佛咱們有了一個意外收穫:居然以id爲排序規則,升序排序了?

注意:這或許是巧合!但咱們能夠感受到另一個重點:3個id=2在一塊兒了,這也就偏偏跟剛纔的數組+鏈表結構呼應上了!

 

最後咱們給Person類重寫equals方法,存儲Person:

import java.util.HashSet;

public class Test {
    public static void main(String[] args) {
        HashSet<Person> set = new HashSet<>();
        set.add(new Person(1, "辣條"));
        set.add(new Person(3, "冰棍"));
        set.add(new Person(4, "麪包"));
        set.add(new Person(2, "薯片"));
        set.add(new Person(2, "薯片"));
        set.add(new Person(2, "薯片"));
        for (Person person : set) {
            System.out.println(person);
        }
    }
}

class Person {
    private int id;
    private String name;
    public Person(int id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person [id=" + id + ", name=" + name + "]";
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + id;
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Person other = (Person) obj;
        if (id != other.id)
            return false;
        return true;
    }

    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

運行結果:

徹底正常!這也就說明了爲何自定義的實體類必需要重寫hashCode和equals方法!

 

HashSet的插入原理:

當你試圖把對象加入HashSet時,HashSet會使用對象的hashCode來判斷對象加入的位置。

同時也會與其餘已經加入的對象的hashCode進行比較,若是沒有相等的hashCode,HashSet就會假設對象沒有重複出現。

若是元素(對象)的hashCode值相同,並不會當即存入,而是會繼續使用equals進行比較。

若是equals爲true,那麼HashSet認爲新加入的對象重複了,因此加入失敗。

若是equals爲false,那麼HashSet認爲新加入的對象沒有重複,新元素能夠存入。

HashSet與List都有contains方法,List接口的實現所有使用equals,而HashSet先使用hashCode,再使用equals

 

---------------------------多嘮幾句--------------------------------

若是有興趣去扒源碼,會發現HashSet實際上是利用了HashMap的鍵來作的。

public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable
{
    static final long serialVersionUID = -5024744406713321676L;

    private transient HashMap<E,Object> map;

    // Dummy value to associate with an Object in the backing Map
    private static final Object PRESENT = new Object();

    /**
     * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
     * default initial capacity (16) and load factor (0.75).
     */
    public HashSet() {
        map = new HashMap<>(); // 這裏new了一個HashMap
    }

另外一個能夠解釋的位置在iterator方法裏:

/**
     * Returns an iterator over the elements in this set.  The elements
     * are returned in no particular order.
     *
     * @return an Iterator over the elements in this set
     * @see ConcurrentModificationException
     */
    public Iterator<E> iterator() {
        return map.keySet().iterator();
    }

返回的居然是map.keySet()的iterator????(黑人問號.jpg)

相關文章
相關標籤/搜索