天天看點

java linkedhashset用法_Java集合系列(三):HashSet、LinkedHashSet、TreeSet的使用方法

點關注,不迷路;持續更新Java相關技術及資訊!!!

本篇文章主要講解Set接口的三個實作類HashSet、LinkedHashSet、TreeSet的使用方法以及三者之間的差別。注意:本文中代碼使用的JDK版本為1.8.0_191

1. HashSet使用

HashSet是Set接口最常用的實作類,底層資料結構是哈希表,HashSet不保證元素的順序但保證元素必須唯一。

private transient HashMap map;

HashSet類的代碼聲明如下所示:

public class HashSet

extends AbstractSet

implements Set, Cloneable, java.io.Serializable

{

......

}

1.1 添加元素

使用HashSet添加元素的使用方法如下所示:

HashSet 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個元素:

java linkedhashset用法_Java集合系列(三):HashSet、LinkedHashSet、TreeSet的使用方法

值得注意的是,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種方式:疊代器周遊

foreach循環

使用方法如下所示:

System.out.println("使用Iterator周遊:");

Iterator 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 清空集合

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

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 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 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

extends HashSet

implements Set, Cloneable, java.io.Serializable {

{

}

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

LinkedHashSet類的使用方法和HashSet基本一樣,隻需修改下聲明處的代碼即可:

Set platformSet = new LinkedHashSet<>();

3. TreeSet使用

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

TreeSet類的代碼聲明如下所示:

public class TreeSet extends AbstractSet

implements NavigableSet, Cloneable, java.io.Serializable

{

}

TreeSet類的使用方法和HashSet基本一樣,隻需修改下聲明處的代碼即可:

Set 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 hashSet = new HashSet<>();

LinkedHashSet linkedHashSet = new LinkedHashSet<>();

TreeSet 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 platformSet = new TreeSet<>();

platformSet.add(null);

運作上面的代碼,報錯資訊如下所示:

java linkedhashset用法_Java集合系列(三):HashSet、LinkedHashSet、TreeSet的使用方法

4.2.3 性能

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

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

package collection;

public class Employee implements Comparable {

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 hashSet = new HashSet<>();

LinkedHashSet linkedHashSet = new LinkedHashSet<>();

TreeSet 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 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 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());

}

滿心歡喜的運作代碼想看下效果,結果卻發現報如下錯誤:

java linkedhashset用法_Java集合系列(三):HashSet、LinkedHashSet、TreeSet的使用方法

為什麼會這樣呢?

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

怎麼解決呢?有以下兩種方式:自然排序

比較器排序

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 {

@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 studentTreeSet = new TreeSet<>(new StudentComparator());

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

記得點個贊再走哦~