1. 程式人生 > >Java集合系列(三):HashSet、LinkedHashSet、TreeSet的使用方法及區別

Java集合系列(三):HashSet、LinkedHashSet、TreeSet的使用方法及區別

本篇部落格主要講解Set介面的三個實現類HashSet、LinkedHashSet、TreeSet的使用方法以及三者之間的區別。

注意:本文中程式碼使用的JDK版本為1.8.0_191

1. HashSet使用

HashSet是Set介面最常用的實現類,底層資料結構是雜湊表,HashSet不保證元素的順序但保證元素必須唯一。

private transient HashMap<E,Object> map;

HashSet類的程式碼宣告如下所示:

public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable
{
    ......
}

1.1 新增元素

使用HashSet新增元素的使用方法如下所示:

HashSet<String> platformSet = new HashSet<>();

// 新增元素
System.out.println(platformSet.add("部落格園"));
System.out.println(platformSet.add("掘金"));
System.out.println(platformSet.add("微信公眾號"));

// 新增重複元素,不會新增成功,因為Set不允許重複元素
// 不過程式碼不會報錯,而是返回false,即新增失敗
System.out.println(platformSet.add("部落格園"));
System.out.println(platformSet.add("掘金"));

以上程式碼執行的輸出結果是:

true

true

true

false

false

除錯程式碼也會發現platformSet只有3個元素:

值得注意的是,platformSet.add(3, "個人部落格");這句程式碼會出現編譯錯誤,因為Set集合新增元素只有1個方法,並不像上篇部落格中講解的List介面一樣提供了2個過載。

1.2 獲取元素

和List介面不一樣的是,Set類介面並沒有獲取元素的方法。

1.3 獲取集合元素個數

獲取HashSet元素個數的使用方法如下所示:

System.out.println("platformSet的元素個數為:" + platformSet.size());

1.4 刪除元素

值得注意的是,使用HashSet刪除元素也只有1個方法,並不像使用ArrayList刪除元素有2個過載:

public boolean remove(Object o) {
    return map.remove(o)==PRESENT;
}

使用方法如下所示:

// 刪除不存在的元素"個人部落格",返回false
System.out.println(platformSet.remove("個人部落格"));
// 刪除存在的元素 "微信公眾號",返回true
System.out.println(platformSet.remove("微信公眾號"));

1.5 修改元素

和List介面不一樣的是,Set類介面並沒有修改元素的方法。

1.6 判斷集合是否為空

判斷HashSet是否為空的使用方法如下所示:

System.out.println("isEmpty:" + platformSet.isEmpty());

1.7 遍歷元素(面試常問)

遍歷HashSet的元素主要有以下2種方式:

  1. 迭代器遍歷
  2. foreach迴圈

使用方法如下所示:

System.out.println("使用Iterator遍歷:");
Iterator<String> platformIterator = platformSet.iterator();
while (platformIterator.hasNext()) {
    System.out.println(platformIterator.next());
}

System.out.println();
System.out.println("使用foreach遍歷:");
for (String platform : platformSet) {
    System.out.println(platform);
}

1.8 清空集合

清空ArrayList中所有元素的使用方法如下所示:

platformSet.clear();

1.9 完整示例程式碼

上面講解的幾點,完整程式碼如下所示:

package collection;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

public class SetTest {
    public static void main(String[] args) {
        Set<String> platformSet = new HashSet<>();

        // 新增元素
        System.out.println(platformSet.add("部落格園"));
        System.out.println(platformSet.add("掘金"));
        System.out.println(platformSet.add("微信公眾號"));

        // 新增重複元素,不會新增成功,因為Set不允許重複元素
        // 不過程式碼不會報錯,而是返回false,即新增失敗
        System.out.println(platformSet.add("部落格園"));
        System.out.println(platformSet.add("掘金"));


        System.out.println("platformSet的元素個數為:" + platformSet.size());

        // 刪除不存在的元素"個人部落格",返回false
        System.out.println(platformSet.remove("個人部落格"));
        // 刪除存在的元素 "微信公眾號",返回true
        System.out.println(platformSet.remove("微信公眾號"));

        System.out.println("platformSet的元素個數為:" + platformSet.size());

        System.out.println("isEmpty:" + platformSet.isEmpty());

        System.out.println("使用Iterator遍歷:");
        Iterator<String> platformIterator = platformSet.iterator();
        while (platformIterator.hasNext()) {
            System.out.println(platformIterator.next());
        }

        System.out.println();
        System.out.println("使用foreach遍歷:");
        for (String platform : platformSet) {
            System.out.println(platform);
        }

        System.out.println();

        platformSet.clear();
        System.out.println("isEmpty:" + platformSet.isEmpty());
    }
}

輸出結果為:

true

true

true

false

false

platformSet的元素個數為:3

false

true

platformSet的元素個數為:2

isEmpty:false

使用Iterator遍歷:

部落格園

掘金

使用foreach遍歷:

部落格園

掘金

isEmpty:true

2. LinkedHashSet使用

LinkedHashSet也是Set介面的實現類,底層資料結構是連結串列和雜湊表,雜湊表用來保證元素唯一,連結串列用來保證元素的插入順序,即FIFO(First Input First Output 先進先出)。

LinkedHashSet類的程式碼宣告如下所示:

public class LinkedHashSet<E>
    extends HashSet<E>
    implements Set<E>, Cloneable, java.io.Serializable {
{
}

從以上程式碼也能看出,LinkedHashSet類繼承了HashSet類。

LinkedHashSet類的使用方法和HashSet基本一樣,只需修改下宣告處的程式碼即可:

Set<String> platformSet = new LinkedHashSet<>();

3. TreeSet使用

TreeSet也是Set介面的實現類,底層資料結構是紅黑樹,TreeSet不僅保證元素的唯一性,也保證元素的順序。

TreeSet類的程式碼宣告如下所示:

public class TreeSet<E> extends AbstractSet<E>
    implements NavigableSet<E>, Cloneable, java.io.Serializable
{
}

TreeSet類的使用方法和HashSet基本一樣,只需修改下宣告處的程式碼即可:

Set<String> platformSet = new TreeSet<>();

4. HashSet、LinkedHashSet、TreeSet的區別(面試常問)

HashSet、LinkedHashSet、TreeSet是實現Set介面的3個實現類,其中:

HashSet只是通用的儲存資料的集合,

LinkedHashSet的主要功能用於保證FIFO(先進先出)即有序的集合,

TreeSet的主要功能用於排序(自然排序或者比較器排序)

4.1 相同點

1)HashSet、LinkedHashSet、TreeSet都實現了Set介面

2)三者都保證了元素的唯一性,即不允許元素重複

3)三者都不是執行緒安全的

可以使用Collections.synchronizedSet()方法來保證執行緒安全

4.2 不同點

4.2.1 排序

HashSet不保證元素的順序

LinkHashSet保證FIFO即按插入順序排序

TreeSet保證元素的順序,支援自定義排序規則

空口無憑,上程式碼看效果:

HashSet<String> hashSet = new HashSet<>();
LinkedHashSet<String> linkedHashSet = new LinkedHashSet<>();
TreeSet<String> treeSet = new TreeSet<>();

String[] letterArray = new String[]{"B", "A", "D", "C", "E"};
for (String letter : letterArray) {
    hashSet.add(letter);
    linkedHashSet.add(letter);
    treeSet.add(letter);
}

System.out.println("HashSet(我不保證順序):" + hashSet);
System.out.println("LinkedHashSet(我保證元素插入時的順序):" + linkedHashSet);
System.out.println("TreeSet(我按排序規則保證元素的順序):" + treeSet);

上面程式碼的輸出結果為:

HashSet(我不保證順序):[A, B, C, D, E]

LinkedHashSet(我保證元素插入時的順序):[B, A, D, C, E]

TreeSet(我按排序規則保證元素的順序):[A, B, C, D, E]

4.2.2 null值

HashSet,LinkedHashSet允許新增null值,TreeSet不允許新增null值,新增null時會丟擲java.lang.NullPointerException異常。

Set<String> platformSet = new TreeSet<>();
platformSet.add(null);

執行上面的程式碼,報錯資訊如下所示:

4.2.3 效能

理論情況下,新增相同數量的元素, HashSet最快,其次是LinkedHashSet,TreeSet最慢(因為內部要排序)。

然後我們通過一個示例來驗證下,首先新建Employee類,自定義排序規則:

package collection;

public class Employee implements Comparable<Employee> {
    private Integer employeeNo;

    public Employee(Integer employeeNo) {
        this.employeeNo = employeeNo;
    }

    public Integer getEmployeeNo() {
        return employeeNo;
    }

    public void setEmployeeNo(Integer employeeNo) {
        this.employeeNo = employeeNo;
    }

    @Override
    public int compareTo(Employee o) {
        return this.employeeNo - o.employeeNo;
    }
}

然後新增如下驗證程式碼,分別往HashSet,LinkedHashSet,TreeSet中新增10000個元素:

Random random = new Random();
HashSet<Employee> hashSet = new HashSet<>();
LinkedHashSet<Employee> linkedHashSet = new LinkedHashSet<>();
TreeSet<Employee> treeSet = new TreeSet<>();

int maxNo = 10000;

long startTime = System.nanoTime();
for (int i = 0; i < maxNo; i++) {
    int randomNo = random.nextInt(maxNo - 10) + 10;
    hashSet.add(new Employee(randomNo));
}

long endTime = System.nanoTime();
long duration = endTime - startTime;
System.out.println("HashSet耗時: " + duration);

startTime = System.nanoTime();
for (int i = 0; i < maxNo; i++) {
    int randomNo = random.nextInt(maxNo - 10) + 10;
    linkedHashSet.add(new Employee(randomNo));
}

endTime = System.nanoTime();
duration = endTime - startTime;
System.out.println("LinkedHashSet:耗時 " + duration);

startTime = System.nanoTime();
for (int i = 0; i < maxNo; i++) {
    int randomNo = random.nextInt(maxNo - 10) + 10;
    treeSet.add(new Employee(randomNo));
}

endTime = System.nanoTime();
duration = endTime - startTime;
System.out.println("TreeSet耗時: " + duration);

第1次執行,輸出結果:

HashSet耗時: 6203357

LinkedHashSet:耗時 5246129

TreeSet耗時: 7813460

第2次執行,輸出結果:

HashSet耗時: 9726115

LinkedHashSet:耗時 5521640

TreeSet耗時: 6884474

第3次執行,輸出結果:

HashSet耗時: 7263940

LinkedHashSet:耗時 6156487

TreeSet耗時: 8554666

第4次執行,輸出結果:

HashSet耗時: 6140263

LinkedHashSet:耗時 4643429

TreeSet耗時: 7804146

第5次執行,輸出結果:

HashSet耗時: 7913810

LinkedHashSet:耗時 5847025

TreeSet耗時: 8511402

從5次執行的耗時可以看出,TreeSet是最耗時的,不過LinkedHashSet的耗時每次都比HashSet少,

這就和上面說的HashSet最快矛盾了,所以這裡留個疑問:HashSet和LinkedHashSet哪個更快?

大家怎麼看待這個問題,歡迎留言。

5. TreeSet的兩種排序方式(面試常問)

先回顧下上面使用TreeSet排序的程式碼:

TreeSet<String> treeSet = new TreeSet<>();

String[] letterArray = new String[]{"B", "A", "D", "C", "E"};
for (String letter : letterArray) {
    treeSet.add(letter);
}

System.out.println("TreeSet(我按排序規則保證元素的順序):" + treeSet);

我們插入元素的順序是"B", "A", "D", "C", "E",但是輸出元素的順序是"A", "B", "C", "D", "E",證明TreeSet已經按照內部規則排過序了。

那如果TreeSet中放入的元素型別是我們自定義的引用型別,它的排序規則是什麼樣的呢?

帶著這個疑問,我們新建個Student類如下:

package collection;

public class Student {
    private String name;
    private int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

然後新增如下驗證程式碼:

TreeSet<Student> studentTreeSet = new TreeSet<>();

Student student1 = new Student("zhangsan", 20);
Student student2 = new Student("lisi", 22);
Student student3 = new Student("wangwu", 24);
Student student4 = new Student("zhaoliu", 26);

Student student5 = new Student("zhangsan", 22);

studentTreeSet.add(student1);
studentTreeSet.add(student2);
studentTreeSet.add(student3);
studentTreeSet.add(student4);
studentTreeSet.add(student5);

for (Student student : studentTreeSet) {
    System.out.println("name:" + student.getName() + ",age:" + student.getAge());
}

滿心歡喜的執行程式碼想看下效果,結果卻發現報如下錯誤:

為什麼會這樣呢?

這是因為我們並沒有給Student類定義任何排序規則,TreeSet說我也不知道咋排序,還是甩鍋丟擲異常吧,哈哈。

怎麼解決呢?有以下兩種方式:

  1. 自然排序
  2. 比較器排序

5.1 自然排序

自然排序的實現方式是讓Student類實現介面Comparable,並重寫該介面的方法compareTo,該方法會定義排序規則。

使用IDEA的快捷鍵生成的compareTo方法預設是這樣的:

@Override
public int compareTo(Student o) {
    return 0;
}

這個方法會在執行add()方法新增元素時執行,以便確定元素的位置。

如果返回0,代表兩個元素相同,只會保留第一個元素

如果返回值大於0,代表這個元素要排在引數中指定元素o的後面

如果返回值小於0,代表這個元素要排在引數中指定元素o的前面

因此如果對compareTo()方法不做任何修改,直接執行之前的驗證程式碼,會發現集合中只有1個元素:

name:zhangsan,age:20

然後修改下compareTo()方法的邏輯為:

@Override
public int compareTo(Student o) {
    // 排序規則描述如下
    // 按照姓名的長度排序,長度短的排在前面,長度長的排在後面
    // 如果姓名的長度相同,按字典順序比較String
    // 如果姓名完全相同,按年齡排序,年齡小的排在前面,年齡大的排在後面

    int orderByNameLength = this.name.length() - o.name.length();
    int orderByName = orderByNameLength == 0 ? this.name.compareTo(o.name) : orderByNameLength;
    int orderByAge = orderByName == 0 ? this.age - o.age : orderByName;

    return orderByAge;
}

再次執行之前的驗證程式碼,輸出結果如下所示:

name:lisi,age:22

name:wangwu,age:24

name:zhaoliu,age:26

name:zhangsan,age:20

name:zhangsan,age:22

5.2 比較器排序

比較器排序的實現方式是新建一個比較器類,繼承介面Comparator,重寫介面中的Compare()方法。

注意:使用此種方式Student類不需要實現介面Comparable,更不需要重寫該介面的方法compareTo。

package collection;

import java.util.Comparator;

public class StudentComparator implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        // 排序規則描述如下
        // 按照姓名的長度排序,長度短的排在前面,長度長的排在後面
        // 如果姓名的長度相同,按字典順序比較String
        // 如果姓名完全相同,按年齡排序,年齡小的排在前面,年齡大的排在後面

        int orderByNameLength = o1.getName().length() - o2.getName().length();
        int orderByName = orderByNameLength == 0 ? o1.getName().compareTo(o2.getName()) : orderByNameLength;
        int orderByAge = orderByName == 0 ? o1.getAge() - o2.getAge() : orderByName;

        return orderByAge;
    }
}

然後修改下驗證程式碼中宣告studentTreeSet的程式碼即可:

TreeSet<Student> studentTreeSet = new TreeSet<>(new StudentComparator());

輸出結果和使用自然排序的輸出結果完全一樣。

6. 原始碼及參考

Java集合中List,Set以及Map等集合體系詳解(史上最全)

7. 最後

打個小廣告,歡迎掃碼關注微信公眾號:「申城異鄉人」,定期分享Java技術乾貨,讓我們一起進步。

相關推薦

Java集合系列()HashSetLinkedHashSetTreeSet的使用方法區別

本篇部落格主要講解Set介面的三個實現類HashSet、LinkedHashSet、TreeSet的使用方法以及三者之間的區別。 注意:本文中程式碼使用的JDK版本為1.8.0_191 1. HashSet使用 HashSet是Set介面最常用的實現類,底層資料結構是雜湊表,HashSet不保證元素的順序

深入理解JAVA集合系列HashMap的死循環解讀

現在 最新 star and 場景 所有 image cap 時也 由於在公司項目中偶爾會遇到HashMap死循環造成CPU100%,重啟後問題消失,隔一段時間又會反復出現。今天在這裏來仔細剖析下多線程情況下HashMap所帶來的問題: 1、多線程put操作後,get操作導

Java集合系列四】HashSetLinkedHashSet解析

inpu skin lam 繼承 depend try put args port 2017-07-29 16:58:13 一、簡介 1、Set概念 Set可以理解為集合,非常類似數據概念中的集合,集合三大特征:1、確定性;2、互異性;3、無序性,因此Set實現類也有類似的

Java集合系列(二)ArrayListLinkedListVector的使用方法區別

本篇部落格主要講解List介面的三個實現類ArrayList、LinkedList、Vector的使用方法以及三者之間的區別。 1. ArrayList使用 ArrayList是List介面最常用的實現類,內部通過陣列來實現,因此它的優點是適合隨機查詢和遍歷,缺點是不適合插入和刪除。 ArrayList類的程

Java集合系列(四)HashMapHashtableLinkedHashMapTreeMap的使用方法區別

本篇部落格主要講解Map介面的4個實現類HashMap、Hashtable、LinkedHashMap、TreeMap的使用方法以及三者之間的區別。 注意:本文中程式碼使用的JDK版本為1.8.0_191 值得注意的是,Map介面是獨立的介面,並沒有繼承Collection介面(這裡是重點,面試常問):

深入理解JAVA集合系列ArrayList源碼解讀

結束 了解 數組下標 size new 數組元素 開始 ini rem 在開始本章內容之前,這裏先簡單介紹下List的相關內容。 List的簡單介紹 有序的collection,用戶可以對列表中每個元素的插入位置進行精確的控制。用戶可以根據元素的整數索引(在列表中的位置)訪

java集合系列——Set之HashSetTreeSet介紹(十)

最大 ... gpo 鏈表 key 同步 中大 nds set接口 一.Set的簡介Set是一個不包含重復元素的 collection。更確切地講,set 不包含滿足 e1.equals(e2) 的元素。對 e1 和 e2,並且最多包含一個為 null 的元素。 Set的類

5.Java集合框架剖析 之 HashsetLinkedHashSet原始碼剖析

1 package java.util; 2 3 import java.io.InvalidObjectException; 4 import sun.misc.SharedSecrets; 5 6 public class HashSet<E> extends

Java 集合系列16之 HashSet詳細介紹(原始碼解析)和使用示例

1 package java.util; 2 3 public class HashSet<E> 4 extends AbstractSet<E> 5 implements Set<E>, Cloneable, java.i

Java爬蟲系列使用Jsoup解析HTML

在上一篇隨筆《Java爬蟲系列二:使用HttpClient抓取頁面HTML》中介紹了怎麼使用HttpClient進行爬蟲的第一步--抓取頁面html,今天接著來看下爬蟲的第二步--解析抓取到的html。 有請第二步的主角:Jsoup粉墨登場。下面我們把舞臺交給Jsoup,讓他完成本文剩下的內容。 ====

Java集合系列(一)集合的定義分類

1. 集合的定義 什麼是集合呢? 定義:集合是一個存放物件的引用的容器。 在Java中,集合位於java.util包下。 2. 集合和陣列的區別(面試常問) 提到容器,就會想起陣列,那麼集合和陣列的區別是什麼呢?(這裡是重點,面試可能問的比較多) 陣列和集合都是Java中的容器 陣列的長度是固定的,集合的長

PHP中 PublicPrivateProtect 的使用方法區別

public 【公共的】 可以在程式中的任何位置(類內、類外)被其他的類和物件呼叫。子類可以繼承和使用父類中所有的公共成員。 Private 【私有的】 被private修飾的變數和方法,只能在所在的類的內部被呼叫和修改,不可以在類的外部被訪問。在子類中也不可以。 如

JAVA基礎第四章-集合框架Collection篇 JAVA基礎第一章-初識java JAVA基礎第二章-java三大特性封裝繼承多型 JAVA基礎第章-類與物件抽象類介面 記一次list迴圈刪除元素的突發事件!

 業內經常說的一句話是不要重複造輪子,但是有時候,只有自己造一個輪子了,才會深刻明白什麼樣的輪子適合山路,什麼樣的輪子適合平地! 我將會持續更新java基礎知識,歡迎關注。   往期章節: JAVA基礎第一章-初識java JAVA基礎第二章-java三大特性

JAVA基礎第五章-集合框架Map篇 JAVA基礎第一章-初識java JAVA基礎第二章-java三大特性封裝繼承多型 JAVA基礎第章-類與物件抽象類介面 JAVA基礎第四章-集合框架Collection篇

 業內經常說的一句話是不要重複造輪子,但是有時候,只有自己造一個輪子了,才會深刻明白什麼樣的輪子適合山路,什麼樣的輪子適合平地! 我將會持續更新java基礎知識,歡迎關注。   往期章節: JAVA基礎第一章-初識java

Java集合HashSetLinkedHashSetTreeSet

討論集合關注的問題: 底層資料結構 增刪改查方式 初始容量,擴容方式,擴容時機 執行緒安全與否 是否允許空,是否允許重複,是否有序 1. 概述 前篇,我寫了關於Map系列的集合(點選跳轉);本篇重新回顧Coll

java集合(10)——HashSetLinkedHashSetTreeSet辨析

Set介面 是Collection的子介面,Set要點: 不允許包含相同的元素 使用equals方法判斷物件是否相同 一個不包含重複元素的 collection。更確切地講,set 不包含滿足 e1.equals(e2) 的元素對 e1 和 e2,並且最多

Java集合set中HashSetLinkedHashSetTreeSet用法比較

HashSet: 底層實現是通過HashMap儲存元素,HashSet的元素是儲存在HashMap的Key中,而value統一使用一個object物件 使用key保證了唯一性,但不保證順序 執行緒不安

java集合 List (ArrayListlinkedListVectorStack) Set(HashSetLinkedHashSetTreeSet) Queue Map

集合就是高階陣列,可以存放任意型別的物件,同時可以自動擴容, List介面實現下來的類有: 陣列集合叫arrayList、Vector、stack(棧) 連結串列集合叫LinkedList List 有序可重複的集合 Set是無序不可重複集合 H

Java集合之四SetHashSetLinkedHashSetTreeSet

Set Set介面是Collection的子介面,set介面沒有提供額外的方法。 Set 集合不允許包含相同的元素,最多允許一個null值,如果試把兩個相同的元素加入同一個 Set 集合中,則新增操作失敗。 Set 判斷兩個物件是否相同不是使用 == 運算子,而是根據 eq

Java Set 常用集合 HashSetLinkedHashSetTreeSet

> Java 中的 Set 是非常常用的資料型別。Set 是無序的 Collection,Java Set 有三個常用的實現類,分別是:HashSet、LinkedHashSet、TreeSet ![](https://img2020.cnblogs.com/blog/1759254/202009/