引入
HashMap的值是唯一的嗎?
import java.util.HashMap;
import java.util.Map;
public class MyTest {
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
map.put("zhangsan", "123-456-7890");
map.put("lisi", "234-567-8901");
map.put("wangwu", "345-678-9012");
// Map的特點是 【鍵】必須是唯一的
// HashMap的特點是 【鍵】必須是唯一的 + 無序
// 值是唯一的嗎?
for (Map.Entry<String, String> entry : map.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
System.out.println("姓名: " + key + ", 電話: " + value);
}
}
}
嘗試將值改為同一個,例如字串123
,甚至是null
都可以正常執行。因此,值並不是唯一的。
但HashMap
的鍵,同時滿足唯一和無序。
實際上把HashMap
中的鍵單獨拎出來就是HashSet
:
- Set的特點是【元素】必須唯一
- HashSet的特點是:唯一 + 無序
HashSet
簡介
摘自官方文件:
This class implements the Set interface, backed by a hash table (actually a HashMap instance). It makes no guarantees as to the iteration order of the set; in particular, it does not guarantee that the order will remain constant over time.
該類實現了 Set
介面,底層使用雜湊表(實際上是一個 HashMap
例項)。它不保證集合的迭代順序,特別是,它並不保證順序會隨時間保持不變。
也就是說,HashSet
就是基於HashMap
實現的,也就是我們剛才說的,單獨把“鍵”拎出來。(這就是為何先學HashMap
然後再學HashSet
)
如果檢視原始碼的話,會發現HashSet
實際上維護了一個HashMap
。
主要方法
見書P256
應用例子
那這樣一個只有鍵的雜湊表,換句話說只能存名字、不能存電話的電話本,有什麼用處呢?
如何建立一個HashSet
?基於上面程式碼簡單修改,實際上就是把鍵給拎出來,把值去掉,換個類名、方法名:
import java.util.HashSet;
import java.util.Set;
public class MyTest {
public static void main(String[] args) {
// Map<String, String> map = new HashMap<>();
Set<String> set = new HashSet<>(); // 同理,set是一個介面,必須依靠於實現類HashSet才能用
set.add("zhangsan");
set.add("lisi");
set.add("wangwu");
for (String s : set) {
System.out.println(s);
}
}
}
最典型的應用是檢測重複的使用者輸入,假設你在開發一個系統,使用者需要填寫使用者名稱。為確保使用者名稱是唯一的,就可以使用HashSet
來檢測是否存在重複輸入:
import java.util.HashSet;
import java.util.Scanner;
public class UniqueUsernameChecker {
public static void main(String[] args) {
HashSet<String> usernames = new HashSet<>();
Scanner scanner = new Scanner(System.in);
System.out.println("請輸入使用者名稱(輸入 'exit' 退出):");
while (true) {
System.out.print("使用者名稱: ");
String username = scanner.nextLine();
if (username.equalsIgnoreCase("exit")) {
System.out.println("退出系統。");
break;
}
// 檢查是否重複
if (usernames.contains(username)) {
System.out.println("使用者名稱已存在,請輸入其他使用者名稱!");
} else {
usernames.add(username);
System.out.println("使用者名稱 " + username + " 已成功新增!");
}
}
System.out.println("\n所有使用者名稱:");
System.out.println(usernames);
}
}
HashSet
基於HashMap
實現
透過檢視原始碼,發現HashSet
實際上維護了一個HashMap
:
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
{
@java.io.Serial
static final long serialVersionUID = -5024744406713321676L;
transient HashMap<E,Object> map;
// Dummy value to associate with an Object in the backing Map
static final Object PRESENT = new Object();
當建立一個HashSet
時,也會初始化一個HashMap
,見HashSet
的構造器:
public HashSet() {
map = new HashMap<>();
}
當呼叫add
方法時,實際上是呼叫了一個map.put
方法:
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
當呼叫contains
時實際上是呼叫了containsKey
:
public boolean contains(Object o) {
return map.containsKey(o);
}
練習
練習1
編寫一個程式,完成以下操作:
- 建立一個
HashSet
來儲存整數。 - 向集合中新增以下數字:10, 20, 30, 20, 40, 10
- 輸出集合的內容。
- 判斷集合是否包含數字 30。如果包含輸出“包含30”,否則輸出“不包含30”。
- 刪除數字 20
- 再次輸出集合內容。
參考程式碼:
import java.util.HashSet;
import java.util.Set;
public class MyTest {
public static void main(String[] args) {
Set<Integer> set = new HashSet<>();
// 尖括號中的型別引數不能是基本型別,也就是不能為int
// 需要使用int的升級版本 或者說類版本, 也就是所謂的包裝器
set.add(10); // 當將一個int值賦給 Integer型別變數時會進行自動裝箱
// 上面相當於 set.add(Integer.valueOf(10));
// 這個工作是編譯器做的
set.add(20);
set.add(30);
set.add(20);
set.add(40);
set.add(10);
System.out.println(set);
if (set.contains(30)) {
System.out.println("包含30");
} else {
System.out.println("不包含30");
}
set.remove(20);
System.out.println(set);
}
}
結合程式碼進一步:
- 理解 HashSet 的無序性(輸出順序可能與插入順序不同)。
- 理解 Set 中元素的唯一性(相同元素不會重複儲存)。
練習2
需要澄清的是無序性是HashSet
的特性,並不是Set
的特性。Set
的特性只有元素的唯一性。
有一種Set
可以儲存元素的順序。它是HashSet
的子類,叫LinkedHashSet
。
可透過如下方式建立一個LinkedHashSet
,其用法和HashSet
一致,只是它可以儲存元素的順序:
Set<String> set = new LinkedHashSet<>();
可用如下方式將一個list
轉換為LinkedHashSet
:
Set<String> set = new LinkedHashSet<>(list);
此時,list
中的元素順序將維持不變,但重複新增的元素將被去除。如果想把set
中元素重新倒回list
中去,可以採用如下的程式碼:
list.clear(); // 清空原有的元素
list.addAll(set); // 把 LinkedHashSet 中的元素重新 按順序 新增進list中
基於上述資訊,請按以下步驟完成練習:
- 建立一個
list
,實現類選LinkedList
- 在
list
中依次新增字串元素:zhangsan, lisi, lisi, wangwu, lisi, wangwu
- 直接透過
System.out.println
輸出list
中的元素 - 透過
LinkedHashSet
清除list
中的重複元素 - 再次透過
System.out.println
輸出list
中的元素
參考輸出:
[zhangsan, lisi, lisi, wangwu, lisi, wangwu]
[zhangsan, lisi, wangwu]
練習3(★★★★★)
已知陣列存放一批QQ號碼,QQ號碼最長為11位,最短為5位String[] strs = {"12345","67891","12347809933","98765432102","67891","12347809933"}
。 將該陣列裡面的所有qq號都存放在LinkedList
中,將list
中重複的元素刪除,將list
中所有元素分別用迭代器
和增強for迴圈
列印出來。
PS. 迭代器版本暫時不做。
可供直接複製的程式碼:
String[] strings = {"12345","67891","12347809933","98765432102","67891","12347809933"};