本篇博客主要講解Set接口的三個實現類HashSet、LinkedHashSet、TreeSet的使用方法以及三者之間的區別。html
注意:本文中代碼使用的JDK版本爲1.8.0_191java
HashSet是Set接口最經常使用的實現類,底層數據結構是哈希表,HashSet不保證元素的順序但保證元素必須惟一。程序員
private transient HashMap<E,Object> map;
HashSet類的代碼聲明以下所示:面試
public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable { ...... }
使用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數據結構
truedom
trueide
false性能
false
調試代碼也會發現platformSet只有3個元素:
值得注意的是,platformSet.add(3, "我的博客");
這句代碼會出現編譯錯誤,由於Set集合添加元素只有1個方法,並不像上篇博客中講解的List接口同樣提供了2個重載。
和List接口不同的是,Set類接口並無獲取元素的方法。
獲取HashSet元素個數的使用方法以下所示:
System.out.println("platformSet的元素個數爲:" + platformSet.size());
值得注意的是,使用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("微信公衆號"));
和List接口不同的是,Set類接口並無修改元素的方法。
判斷HashSet是否爲空的使用方法以下所示:
System.out.println("isEmpty:" + platformSet.isEmpty());
遍歷HashSet的元素主要有如下2種方式:
使用方法以下所示:
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); }
清空HashSet中全部元素的使用方法以下所示:
platformSet.clear();
上面講解的幾點,完整代碼以下所示:
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
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<>();
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<>();
HashSet、LinkedHashSet、TreeSet是實現Set接口的3個實現類,其中:
HashSet只是通用的存儲數據的集合,
LinkedHashSet的主要功能用於保證FIFO(先進先出)即有序的集合,
TreeSet的主要功能用於排序(天然排序或者比較器排序)
1)HashSet、LinkedHashSet、TreeSet都實現了Set接口
2)三者都保證了元素的惟一性,即不容許元素重複
3)三者都不是線程安全的
可使用Collections.synchronizedSet()方法來保證線程安全
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]
HashSet,LinkedHashSet容許添加null值,TreeSet不容許添加null值,添加null時會拋出java.lang.NullPointerException
異常。
Set<String> platformSet = new TreeSet<>(); platformSet.add(null);
運行上面的代碼,報錯信息以下所示:
理論狀況下,添加相同數量的元素, 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哪一個更快?
你們怎麼看待這個問題,歡迎留言。
先回顧下上面使用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說我也不知道咋排序,仍是甩鍋拋出異常吧,哈哈。
怎麼解決呢?有如下兩種方式:
天然排序的實現方式是讓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
比較器排序的實現方式是新建一個比較器類,繼承接口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());
輸出結果和使用天然排序的輸出結果徹底同樣。
Java集合中List,Set以及Map等集合體系詳解(史上最全)
原創不易,若是以爲文章能學到東西的話,歡迎點個贊、評個論、關個注,這是我堅持寫做的最大動力。
若是有興趣,歡迎添加個人微信:zwwhnly,等你來聊技術、職場、工做等話題(PS:我是一名奮鬥在上海的程序員)。
原文出處:https://www.cnblogs.com/zwwhnly/p/11282050.html