编程自学指南:java程序设计开发,Java Set 集合详解,Set 集合的概念、特点和用途,HashSet、TreeSet 和 LinkedHashSet 的使用, Set 集合的内部实现原理

编程自学指南:java程序设计开发,Java Set 集合详解

一、课程信息

学习目标

  1. 理解 Set 集合的概念、特点和用途。
  2. 掌握 HashSet、TreeSet 和 LinkedHashSet 的使用方法和区别。
  3. 学会使用 Set 集合解决实际问题,如去重操作。
  4. 了解 Set 集合的内部实现原理。

课程重点

  1. Set 集合的特点和常用方法。
  2. HashSet、TreeSet 和 LinkedHashSet 的使用场景。
  3. 元素的唯一性判断机制。

课程难点

  1. TreeSet 的排序规则和自定义排序。
  2. HashSet 的哈希冲突处理机制。

二、课程导入

生活中的去重场景

在生活中,我们经常会遇到需要去重的情况。例如,在一个班级的学生名单中,可能会有重复的名字,我们需要将这些重复的名字去除,只保留唯一的名字。再比如,在统计一个网站的访问 IP 地址时,也需要去除重复的 IP 地址,以准确统计访问的用户数量。

编程中的去重需求

在 Java 编程中,我们也会经常遇到需要去重的场景。例如,从一个数组中去除重复的元素,或者从一个列表中去除重复的对象。使用普通的数组或列表来实现去重操作比较麻烦,而 Java 提供的 Set 集合可以很好地解决这个问题。

示例代码引入

import java.util.ArrayList;
import java.util.List;

public class DuplicateRemovalExample {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>();
        names.add("Alice");
        names.add("Bob");
        names.add("Alice");

        // 手动去重比较麻烦
        List<String> uniqueNames = new ArrayList<>();
        for (String name : names) {
            if (!uniqueNames.contains(name)) {
                uniqueNames.add(name);
            }
        }
        System.out.println(uniqueNames);
    }
}

使用 Set 集合可以更简洁地实现去重:

import java.util.HashSet;
import java.util.Set;

public class SetDuplicateRemovalExample {
    public static void main(String[] args) {
        Set<String> names = new HashSet<>();
        names.add("Alice");
        names.add("Bob");
        names.add("Alice");

        System.out.println(names);
    }
}

三、Set 集合概述

定义

Set 是 Java 集合框架中的一个接口,它继承自 Collection 接口。Set 集合中的元素是唯一的,不允许有重复的元素。

特点

  1. 元素唯一性:Set 集合中不允许存储重复的元素,如果尝试添加重复的元素,添加操作将失败。
  2. 无序性(部分实现):HashSet 和 LinkedHashSet 不保证元素的插入顺序,而 TreeSet 会对元素进行排序。
  3. 不支持索引访问:Set 集合没有像 List 集合那样的索引,不能通过索引来访问元素。

常用实现类

  1. HashSet:基于哈希表实现,不保证元素的顺序,元素的存储位置由元素的哈希值决定。
  2. TreeSet:基于红黑树实现,会对元素进行排序,默认按照元素的自然顺序排序,也可以通过自定义比较器进行排序。
  3. LinkedHashSet:基于哈希表和链表实现,既保证了元素的唯一性,又能保持元素的插入顺序。

四、HashSet 详解

内部实现原理

HashSet 内部使用哈希表(实际上是一个 HashMap)来存储元素。当向 HashSet 中添加元素时,首先会计算元素的哈希值,然后根据哈希值找到对应的桶(bucket),如果桶中没有元素,则直接将元素放入桶中;如果桶中已经有元素,则会调用元素的 equals() 方法来判断元素是否相等,如果相等则不添加,否则将元素添加到桶中。

示例代码

import java.util.HashSet;
import java.util.Set;

public class HashSetExample {
    public static void main(String[] args) {
        // 创建 HashSet 对象
        Set<String> fruits = new HashSet<>();

        // 添加元素
        fruits.add("Apple");
        fruits.add("Banana");
        fruits.add("Orange");
        fruits.add("Apple"); // 重复元素,添加失败

        // 遍历元素
        for (String fruit : fruits) {
            System.out.println(fruit);
        }

        // 判断元素是否存在
        boolean containsApple = fruits.contains("Apple");
        System.out.println("Contains Apple: " + containsApple);

        // 删除元素
        fruits.remove("Banana");
        System.out.println("After removing Banana: " + fruits);
    }
}

哈希冲突处理

当不同的元素计算出相同的哈希值时,就会发生哈希冲突。HashSet 使用链地址法来处理哈希冲突,即每个桶中存储一个链表,当发生哈希冲突时,将冲突的元素添加到链表中。

元素唯一性判断

HashSet 判断元素是否相等的规则是:首先比较元素的哈希值,如果哈希值不同,则认为元素不相等;如果哈希值相同,则调用元素的 equals() 方法进一步比较。因此,为了保证元素的唯一性,重写 equals() 方法时必须同时重写 hashCode() 方法。

import java.util.HashSet;
import java.util.Set;

class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age && name.equals(person.name);
    }

    @Override
    public int hashCode() {
        int result = name.hashCode();
        result = 31 * result + age;
        return result;
    }

    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + "}";
    }
}

public class HashSetCustomObjectExample {
    public static void main(String[] args) {
        Set<Person> people = new HashSet<>();
        people.add(new Person("Alice", 20));
        people.add(new Person("Bob", 25));
        people.add(new Person("Alice", 20)); // 重复元素,添加失败

        for (Person person : people) {
            System.out.println(person);
        }
    }
}

五、TreeSet 详解

内部实现原理

TreeSet 内部使用红黑树(一种自平衡的二叉搜索树)来存储元素。红黑树可以保证元素的有序性,并且插入、删除和查找操作的时间复杂度都是 O (log n)。

示例代码

import java.util.TreeSet;
import java.util.Set;

public class TreeSetExample {
    public static void main(String[] args) {
        // 创建 TreeSet 对象
        Set<Integer> numbers = new TreeSet<>();

        // 添加元素
        numbers.add(3);
        numbers.add(1);
        numbers.add(2);

        // 遍历元素
        for (Integer number : numbers) {
            System.out.println(number); // 输出:1 2 3
        }

        // 获取第一个和最后一个元素
        Integer first = ((TreeSet<Integer>) numbers).first();
        Integer last = ((TreeSet<Integer>) numbers).last();
        System.out.println("First: " + first + ", Last: " + last);
    }
}

自然排序和自定义排序

  • 自然排序:如果元素实现了 Comparable 接口,TreeSet 会按照元素的自然顺序进行排序。例如,IntegerString 等类都实现了 Comparable 接口。
import java.util.TreeSet;
import java.util.Set;

class Student implements Comparable<Student> {
    private String name;
    private int score;

    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }

    @Override
    public int compareTo(Student other) {
        return Integer.compare(this.score, other.score);
    }

    @Override
    public String toString() {
        return "Student{name='" + name + "', score=" + score + "}";
    }
}

public class TreeSetNaturalOrderExample {
    public static void main(String[] args) {
        Set<Student> students = new TreeSet<>();
        students.add(new Student("Alice", 80));
        students.add(new Student("Bob", 90));
        students.add(new Student("Charlie", 70));

        for (Student student : students) {
            System.out.println(student);
        }
    }
}
  • 自定义排序:如果元素没有实现 Comparable 接口,或者需要按照自定义的规则进行排序,可以在创建 TreeSet 时传入一个 Comparator 对象。
import java.util.Comparator;
import java.util.TreeSet;
import java.util.Set;

class Book {
    private String title;
    private double price;

    public Book(String title, double price) {
        this.title = title;
        this.price = price;
    }

    public String getTitle() {
        return title;
    }

    public double getPrice() {
        return price;
    }

    @Override
    public String toString() {
        return "Book{title='" + title + "', price=" + price + "}";
    }
}

public class TreeSetCustomOrderExample {
    public static void main(String[] args) {
        // 自定义比较器,按价格排序
        Comparator<Book> priceComparator = Comparator.comparingDouble(Book::getPrice);

        Set<Book> books = new TreeSet<>(priceComparator);
        books.add(new Book("Java Programming", 50.0));
        books.add(new Book("Python Basics", 30.0));
        books.add(new Book("Data Structures", 70.0));

        for (Book book : books) {
            System.out.println(book);
        }
    }
}

六、LinkedHashSet 详解

内部实现原理

LinkedHashSet 是 HashSet 的子类,它在 HashSet 的基础上维护了一个双向链表,用于记录元素的插入顺序。因此,LinkedHashSet 既保证了元素的唯一性,又能保持元素的插入顺序。

示例代码

import java.util.LinkedHashSet;
import java.util.Set;

public class LinkedHashSetExample {
    public static void main(String[] args) {
        // 创建 LinkedHashSet 对象
        Set<String> colors = new LinkedHashSet<>();

        // 添加元素
        colors.add("Red");
        colors.add("Green");
        colors.add("Blue");
        colors.add("Red"); // 重复元素,添加失败

        // 遍历元素
        for (String color : colors) {
            System.out.println(color); // 输出:Red Green Blue
        }
    }
}

七、HashSet、TreeSet 和 LinkedHashSet 的比较

比较项HashSetTreeSetLinkedHashSet
内部实现哈希表红黑树哈希表 + 双向链表
元素顺序无序有序(自然排序或自定义排序)保持插入顺序
插入、删除、查找效率O(1)O(log n)O(1)
适用场景不需要元素顺序,注重插入、删除和查找效率需要元素排序需要保持元素插入顺序

八、课堂练习

练习 1

编写一个程序,使用 HashSet 去除数组中的重复元素,并输出去重后的元素。

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

public class Exercise1 {
    public static void main(String[] args) {
        Integer[] numbers = {1, 2, 3, 2, 4, 3, 5};
        // 请完成代码
    }
}

练习 2

编写一个程序,使用 TreeSet 对一组字符串进行排序,并输出排序后的结果。

import java.util.TreeSet;
import java.util.Set;

public class Exercise2 {
    public static void main(String[] args) {
        String[] words = {"apple", "banana", "cherry", "date"};
        // 请完成代码
    }
}

练习 3

编写一个程序,使用 LinkedHashSet 存储一组学生的姓名,并保持插入顺序,最后输出这些姓名。

import java.util.LinkedHashSet;
import java.util.Set;

public class Exercise3 {
    public static void main(String[] args) {
        String[] names = {"Alice", "Bob", "Charlie", "Alice"};
        // 请完成代码
    }
}

九、课程总结

重点回顾

  1. Set 集合的概念、特点和常用实现类。
  2. HashSet、TreeSet 和 LinkedHashSet 的内部实现原理、使用方法和区别。
  3. 元素的唯一性判断机制和排序规则。

注意事项

  1. 在使用 HashSet 时,要确保元素的 equals() 方法和 hashCode() 方法正确重写。
  2. 在使用 TreeSet 时,元素必须实现 Comparable 接口或传入自定义的 Comparator 对象。

十、课后作业

作业 1

编写一个程序,使用 HashSet 存储一组员工对象(包含员工编号、姓名和工资),并去除重复的员工对象。要求重写员工类的 equals() 方法和 hashCode() 方法。

作业 2

编写一个程序,使用 TreeSet 对一组学生对象(包含学生姓名和成绩)按照成绩从高到低进行排序,并输出排序后的结果。可以通过自定义比较器来实现。

作业 3

编写一个程序,使用 LinkedHashSet 存储一组商品对象(包含商品名称和价格),并保持商品的插入顺序,最后输出这些商品的信息。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zl515035644

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值