Set集合类_演练

引入

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可译成散列集

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吗?)

应用例子

那这样一个只有键的哈希表,换句话说只能存名字、不能存电话的电话本,有什么用处呢?

先看下如何将一个HashMap“改”成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);
        while (true) {
            System.out.print("用户名: ");
            String username = scanner.nextLine();

            if (username.equalsIgnoreCase("exit")) {
                // 如果输入exit就退出循环,这个exit不区分大小写
                break;
            }

            // 可以用contains检查是否重复
            if (usernames.contains(username)) {
                System.out.println("用户名已存在,请输入其他用户名!");
            } else {
                usernames.add(username);
                System.out.println("用户名 " + username + " 已成功添加!");
            }
        }

        System.out.println("\n所有用户名: ");
        System.out.println(usernames); // HashSet 覆盖了 toString 方法
    }
}

实际中,这个工作通常是数据库层面去做的,但是基本原理一致。

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的构造器(当new一个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的特点:唯一性 + 无序性
  2. 主要方法:add方法、contains方法、remove方法(后续练习体现
  3. HashSet的底层实际上是一个HashMap实例

练习

练习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"};
posted @ 2024-12-03 16:54  xkfx  阅读(84)  评论(0编辑  收藏  举报