Set集合類_演練

xkfx發表於2024-12-03

引入

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

編寫一個程式,完成以下操作:

  1. 建立一個 HashSet 來儲存整數。
  2. 向集合中新增以下數字:10, 20, 30, 20, 40, 10
  3. 輸出集合的內容。
  4. 判斷集合是否包含數字 30。如果包含輸出“包含30”,否則輸出“不包含30”。
  5. 刪除數字 20
  6. 再次輸出集合內容。

參考程式碼:

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中

基於上述資訊,請按以下步驟完成練習:

  1. 建立一個list,實現類選LinkedList
  2. list中依次新增字串元素:zhangsan, lisi, lisi, wangwu, lisi, wangwu
  3. 直接透過System.out.println輸出list中的元素
  4. 透過LinkedHashSet清除list中的重複元素
  5. 再次透過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"};

相關文章