Collection中的Set

it2025-01-10  5

Set接口是Collection的子接口,set接口没有提供额外的方法。但是比Collection接口更加严格了。

Set 集合不允许包含相同的元素,如果试把两个相同的元素加入同一个 Set 集合中,则添加操作失败。

Set集合支持的遍历方式和Collection集合一样:foreach和Iterator。

Set的常用实现类有:HashSet、TreeSet、LinkedHashSet。

HashSet

HashSet 是 Set 接口的典型实现,大多数时候使用 Set 集合时都使用这个实现类。

java.util.HashSet底层的实现其实是一个java.util.HashMap支持,然后HashMap的底层物理实现是一个Hash表。(什么是哈希表,下一节在HashMap小节在细讲,这里先不展开)

HashSet 按 Hash 算法来存储集合中的元素,因此具有很好的存取和查找性能。HashSet 集合判断两个元素相等的标准:两个对象通过 hashCode() 方法比较相等,并且两个对象的 equals() 方法返回值也相等。因此,存储到HashSet的元素要重写hashCode和equals方法。

示例代码:定义一个Employee类,该类包含属性:name, birthday,其中 birthday 为 MyDate类的对象;MyDate为自定义类型,包含年、月、日属性。要求 name和birthday一样的视为同一个员工。

public class Employee { private String name; private MyDate birthday; public Employee(String name, MyDate birthday) { super(); this.name = name; this.birthday = birthday; } public Employee() { super(); } public String getName() { return name; } public void setName(String name) { this.name = name; } public MyDate getBirthday() { return birthday; } public void setBirthday(MyDate birthday) { this.birthday = birthday; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((birthday == null) ? 0 : birthday.hashCode()); result = prime * result + ((name == null) ? 0 : name.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Employee other = (Employee) obj; if (birthday == null) { if (other.birthday != null) return false; } else if (!birthday.equals(other.birthday)) return false; if (name == null) { if (other.name != null) return false; } else if (!name.equals(other.name)) return false; return true; } @Override public String toString() { return "姓名:" + name + ", 生日:" + birthday; } } public class MyDate { private int year; private int month; private int day; public MyDate(int year, int month, int day) { super(); this.year = year; this.month = month; this.day = day; } public MyDate() { super(); } public int getYear() { return year; } public void setYear(int year) { this.year = year; } public int getMonth() { return month; } public void setMonth(int month) { this.month = month; } public int getDay() { return day; } public void setDay(int day) { this.day = day; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + day; result = prime * result + month; result = prime * result + year; return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; MyDate other = (MyDate) obj; if (day != other.day) return false; if (month != other.month) return false; if (year != other.year) return false; return true; } @Override public String toString() { return year + "-" + month + "-" + day; } } import java.util.HashSet; public class TestHashSet { @SuppressWarnings("all") public static void main(String[] args) { HashSet<Employee> set = new HashSet<>(); set.add(new Employee("张三", new MyDate(1990,1,1))); //重复元素无法添加,因为MyDate和Employee重写了hashCode和equals方法 set.add(new Employee("张三", new MyDate(1990,1,1))); set.add(new Employee("李四", new MyDate(1992,2,2))); for (Employee object : set) { System.out.println(object); } } }

LinkedHashSet

LinkedHashSet是HashSet的子类,它在HashSet的基础上,在结点中增加两个属性before和after维护了结点的前后添加顺序。java.util.LinkedHashSet,它是链表和哈希表组合的一个数据存储结构。LinkedHashSet插入性能略低于 HashSet,但在迭代访问 Set 里的全部元素时有很好的性能。

LinkedHashSet<String> set = new LinkedHashSet<>(); set.add("张三"); set.add("李四"); set.add("王五"); set.add("张三"); System.out.println("元素个数:" + set.size()); for (String name : set) { System.out.println(name); } 运行结果: 元素个数:3 张三 李四 王五

TreeSet

底层结构:里面维护了一个TreeMap,都是基于红黑树实现的!

特点: 1、不允许重复 2、实现排序 自然排序或定制排序

如何实现去重的?

如果使用的是自然排序,则通过调用实现的compareTo方法 如果使用的是定制排序,则通过调用比较器的compare方法

如何排序?

方式一:自然排序 让待添加的元素类型实现Comparable接口,并重写compareTo方法 方式二:定制排序 创建Set对象时,指定Comparator比较器接口,并实现compare方法

自然顺序

如果试图把一个对象添加到 TreeSet 时,则该对象的类必须实现 Comparable 接口。实现 Comparable 的类必须实现 compareTo(Object obj) 方法,两个对象即通过 compareTo(Object obj) 方法的返回值来比较大小。对于 TreeSet 集合而言,它判断两个对象是否相等的唯一标准是:两个对象通过 compareTo(Object obj) 方法比较返回值为0。

代码示例一:按照字符串Unicode编码值排序

@Test public void test1(){ TreeSet<String> set = new TreeSet<>(); set.add("zhangsan"); //String它实现了java.lang.Comparable接口 set.add("lisi"); set.add("wangwu"); set.add("zhangsan"); System.out.println("元素个数:" + set.size()); for (String str : set) { System.out.println(str); } }

定制排序

如果放到TreeSet中的元素的自然排序(Comparable)规则不符合当前排序需求时,或者元素的类型没有实现Comparable接口。那么在创建TreeSet时,可以单独指定一个Comparator的对象。使用定制排序判断两个元素相等的标准是:通过Comparator比较两个元素返回了0。

代码示例:学生类型未实现Comparable接口,单独指定Comparator比较器,按照学生的学号排序

public class Student{ private int id; private String name; public Student(int id, String name) { super(); this.id = id; this.name = name; } public int getId() { return id; } public void setId(int id) { this.id = id; } //......这里省略了name属性的get/set @Override public String toString() { return "Student [id=" + id + ", name=" + name + "]"; } } @Test public void test3(){ TreeSet<Student> set = new TreeSet(new Comparator<Student>(){ @Override public int compare(Student o1, Student o2) { return o1.getId() - o2.getId(); } }); set.add(new Student(3,"张三")); set.add(new Student(1,"李四")); set.add(new Student(2,"王五")); set.add(new Student(3,"张三风")); System.out.println("元素个数:" + set.size()); for (Student stu : set) { System.out.println(stu); } }

重要实现类:

HashSet:使用hashCode()方法和equals()方法配合去重。当一个元素存入HashSet集合时,首先调用该元素的hashCode()方法,得到hash码,利用hash码计算该元素在数组中存放的索引。如果该索引位置无元素则直接存放在该位置。如果该下标处有元素,则调用新元素的equals方法和原元素进行比较,如果equals返回false,则判定两个元素不相同,则将新元素链接到原元素的next属性上。如果equals方法返回true,则判定两个元素相同,不执行新增动作。

LinkedHashSet:底层实现和HashSet一模一样。除了本身有两个属性,first和last。由于这两个属性的存在,LinkedHashSet除了数组还维护一个链表结构。遍历时遍历的是这个链表,而链表可以记录元素新增顺序。

TreeSet:底层使用红黑树保存数据。元素在向红黑树新增的时候和原元素进行比较大小,如果相等,则判定元素相同,不执行新增操作。如果不相等则向红黑树中新增。使用红黑树可以在元素存入的时候即按照升序或者降序排列,同时去重。

最新回复(0)