Java 容器和泛型(3)HashSet,TreeSet 和 LinkedHashSet比較

泥沙磚瓦漿木匠-Jeff_Li發表於2015-04-08

一、Set回顧

一個不包括重複元素(包括可變物件)的Collection,是一種無序的集合。Set不包含滿 a.equals(b) 的元素對a和b,並且最多有一個null。

泥瓦匠的記憶宮殿:

1、不允許包含相同元素

2、判斷物件是否相同,根據equals方法

Java 容器 & 泛型:三、HashSet,TreeSet 和 LinkedHashSet比較

二、HashSet

一個按著Hash演算法來儲存集合中的元素,其元素值可以是NULL。它不能保證元素的排列順序。同樣,HashSet是不同步的,如果需要多執行緒訪問它的話,可以用 Collections.synchronizedSet 方法來包裝它:

Set s = Collections.synchronizedSet(new HashSet(...));
同上一節一樣,用迭代器的時候,也要注意 併發修改異常ConcurrentModificationException

要注意的地方是,HashSet集合判斷兩個元素相等不單單是equals方法,並且必須hashCode()方法返回值也要相等。看下面的例子:

import java.util.HashSet;

class EuqalsObj
{
    public boolean equals(Object obj)
    {
        return true;
    }
}

class HashCodeObj
{
    public int hashCode()
    {
        return 1;
    }
}

class HashSetObj
{
    public int hashCode()
    {
        return 2;
    }

    public boolean equals(Object obj)
    {
        return true;
    }
}

public class HashSetTest
{
    public static void main(String[] args)
    {
        HashSet objs = new HashSet();
        objs.add(new EuqalsObj());
        objs.add(new EuqalsObj());
        objs.add(new HashCodeObj());
        objs.add(new HashCodeObj());
        objs.add(new HashSetObj());
        objs.add(new HashSetObj());

        System.out.println("HashSet Elements:");
        System.out.print("/t" + objs + "/n");
    }
}

Run 一下,控制檯如下輸出:

HashSet Elements:
    [HashCodeObj@1, HashCodeObj@1, HashSetObj@2, EuqalsObj@1471cb25, EuqalsObj@3acff49f]

泥瓦匠根據結果,一一到來。首先,排列順序不定。

HashSetObj 類滿足我們剛剛的要求,所以集合中只有一個且它的HashCode值為2。

HashCodeObj 類雖然它們HashCode值為1,但是他們不相等。(其實當HashCode值一樣,這個儲存位置會採用鏈式結構儲存兩個HashCodeObj物件。)

同樣,EqualsObj 類他們相等,但是他們HashCode值不等,分別為1471cb25、3acff49f。

因此,用HashSet新增可變物件,要注意當物件有可能修改後和其他物件矛盾,這樣我們無法從HashSet找到準確我們需要的物件。

三、LinkedHashList

HashSet的子類,也同樣有HashCode值來決定元素位置。但是它使用連結串列維護元素的次序。記住兩個字:有序

有序的妙用,複製。比如泥瓦匠實現一個HashSet無序新增,然後複製一個一樣次序的HashSet來。程式碼如下:

package com.sedion.bysocket.collection;

import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;

public class LinkedHashListTest
{
    public static void main(String[] args)
    {
        /* 複製HashSet */
        Set h1 = new HashSet<String>();
        h1.add("List");
        h1.add("Queue");
        h1.add("Set");
        h1.add("Map");

        System.out.println("HashSet Elements:");
        System.out.print("/t" + h1 + "/n");

        Set h2 = copy(h1);
        System.out.println("HashSet Elements After Copy:");
        System.out.print("/t" + h2 + "/n");
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    public static Set copy(Set set)
    {
        Set setCopy = new LinkedHashSet(set);
        return setCopy;
    }

}

Run 一下,控制檯輸出:

HashSet Elements:
    [Map, Queue, Set, List]
HashSet Elements After Copy:
    [Map, Queue, Set, List]

可見,每個資料結構都有它存在的理由。

四、TreeSet

TreeSet使用樹結構實現(紅黑樹),集合中的元素進行排序,但是新增、刪除和包含的演算法複雜度為O(log(n))。

舉個例子吧,首先我們定義一個Bird類。(鳥是泥瓦匠最喜歡的動物)

class Bird
{
    int size;

    public Bird(int s)
    {
        size = s;
    }

    public String toString()
    {
        return size + "";
    }

}

然後用TreeSet新增Bird類。

public class TreeSetTest
{
    public static void main(String[] args)
    {
        TreeSet<Bird> bSet = new TreeSet<Bird>();
        bSet.add(new Bird(1));
        bSet.add(new Bird(3));
        bSet.add(new Bird(2));

        Iterator<Bird> iter = bSet.iterator();

        while (iter.hasNext())
        {
            Bird bird = (Bird) iter.next();
            System.out.println(bird);
        }
    }
}

Run一下,控制檯輸出如下:

Exception in thread "main" java.lang.ClassCastException: Bird cannot be cast to java.lang.Comparable
    at java.util.TreeMap.compare(Unknown Source)
    at java.util.TreeMap.put(Unknown Source)
    at java.util.TreeSet.add(Unknown Source)
    at com.sedion.bysocket.collection.TreeSetTest.main(TreeSetTest.java:29)

答案很明顯,TreeSet是排序的。所以Bird需要實現Comparable此介面。

java.lang.Comparable此介面強行對實現它的每個類的物件進行整體排序。這種排序被稱為類的自然排序,類的 compareTo 方法被稱為它的自然比較方法

修改Bird如下:

class Bird implements Comparable<Bird>
{
    int size;

    public Bird(int s)
    {
        size = s;
    }

    public String toString()
    {
        return size + "號鳥";
    }

    @Override
    public int compareTo(Bird o)
    {
        return size - o.size;
    }

}

再次Run一下:

1號鳥
2號鳥
3號鳥

五、效能測試比較

針對上面三種Set集合,我們對它們的Add方法進行效能測試:

import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Random;
import java.util.TreeSet;

class Bird implements Comparable<Bird>
{
    int size;

    public Bird(int s)
    {
        size = s;
    }

    public String toString()
    {
        return size + "號鳥";
    }

    @Override
    public int compareTo(Bird o)
    {
        return size - o.size;
    }

}
public class Set
{
    public static void main(String[] args)
    {
        Random r = new Random();

        HashSet<Bird> hashSet = new HashSet<Bird>();
        TreeSet<Bird> treeSet = new TreeSet<Bird>();
        LinkedHashSet<Bird> linkedSet = new LinkedHashSet<Bird>();

        // start time
        long startTime = System.nanoTime();

        for (int i = 0; i < 1000; i++) {
            int x = r.nextInt(1000 - 10) + 10;
            hashSet.add(new Bird(x));
        }
        // end time
        long endTime = System.nanoTime();
        long duration = endTime - startTime;
        System.out.println("HashSet: " + duration);

        // start time
        startTime = System.nanoTime();
        for (int i = 0; i < 1000; i++) {
            int x = r.nextInt(1000 - 10) + 10;
            treeSet.add(new Bird(x));
        }
        // end time
        endTime = System.nanoTime();
        duration = endTime - startTime;
        System.out.println("TreeSet: " + duration);

        // start time
        startTime = System.nanoTime();
        for (int i = 0; i < 1000; i++) {
            int x = r.nextInt(1000 - 10) + 10;
            linkedSet.add(new Bird(x));
        }
        // end time
        endTime = System.nanoTime();
        duration = endTime - startTime;
        System.out.println("LinkedHashSet: " + duration);
    }
}

Run一下,可以在控制檯中看出:

HashSet: 2610998
TreeSet: 3195378
LinkedHashSet: 2673782

可見,TreeSet因為需要進行比較,所以效能比較差。

六、總結

HashSet:equlas hashcode

LinkedHashSet:鏈式結構

TreeSet:比較,Comparable介面,效能較差

相關文章