J2EE集合框架之Set
1.概念:
Set(集合)是一个数学和计算机科学中的概念。在数学中,集合是一组唯一的对象的集合,这些对象被称为集合的元素,而在计算机科学中,集合是一种特殊的数据结构,用于存储一组唯一的元素,而且这些元素没有特定的顺序。集合不允许重复的元素,因此具有唯一性。
图解
2.Set集合的基本方法
方法作用 | 方法名 |
add() | 向Set中添加元素x。如果该元素已经存在,则Set不会发生任何变化。 |
remove() | 从Set中删除元素x。如果该元素不存在,则Set不会发生任何变化。 |
contains() | 检查Set中是否包含元素x。如果包含,则返回true,否则返回false。 |
size() | 返回Set中元素的数量,即表示的是集合的长度。 |
clear() | 删除Set中的所有元素,即清空集合。 |
union(set) | 返回一个新的Set,包含原Set与参数set中所有元素。 |
intersection(set) | 返回一个新的Set,包含原Set与参数set中的共同元素。 |
difference(set) | 返回一个新的Set,包含原Set中不在参数set中的元素。 |
subset(set) | 检查原Set是否为参数set的子集。如果是则返回true,否则返回false。 |
3.特点:
- 唯一性:Set中不存在重复的元素。当插入重复元素时,Set不会发生变化。
- 无序性:Set中的元素没有特定的顺序。因此,不能用索引来访问Set中的元素,只能通过迭代器或集合操作来处理Set中的元素。
- 可用性:Set可以用于存储各种数据类型,包括数字、字符串、对象等。
- 微型存储:Set通常比数组和其他数据结构更紧凑,因为它不需要预先分配内存和存储无效的值。
- 高效性:Set通常提供快速的查找和删除操作,这是因为Set哈希表、红黑树等高效的数据结构来实现。
总的来说,Set具有简单、高效、可用、安全等优点,因此在计算机科学领域中得到了广泛的应用。
4. Set集合的遍历方法
4.1增强for遍历
package com.YX; import java.util.HashSet; import java.util.Set; public class Demo1 { public static void main(String[] args) { Set set = new HashSet<>(); //创建一个Set,存储字符串 set.add("YX"); set.add("JY"); set.add("MY"); //使用for-each遍历Set for (Object obj : set) { System.out.println(obj); } } }
遍历结果:
4.2 Iterator迭代器遍历
package com.YX; import java.util.HashSet; import java.util.Iterator; import java.util.Set; public class Demo1 { public static void main(String[] args) { Set set = new HashSet<>(); //创建一个Set,存储字符串 set.add("YX"); set.add("JY"); set.add("MY"); // Iterator迭代器遍历 Iterator it=set.iterator(); while(it.hasNext()) { System.out.println(it.next()); } } }
遍历结果:
4.3 利用toArray()→Object[]
package com.YX; import java.util.HashSet; import java.util.Iterator; import java.util.Set; public class Demo1 { public static void main(String[] args) { Set set = new HashSet<>(); //创建一个Set,存储字符串 set.add("YX"); set.add("JY"); set.add("MY"); Object [] obj=set.toArray(); for (int i = 0; i < obj.length; i++) { System.out.println(obj[i]); } } }
遍历结果:
5. Set集合的常见实现类
5.1 HashSet
5.1.1 概述:
HashSet是Java中的一种集合类型,它是基于哈希表实现的,可以用来存储一组无序的、不重复的元素。哈希表是一种利用哈希函数进行快速查找的数据结构,它将元素的值通过哈希函数映射成唯一的索引,然后将元素存储在对应索引的位置上。HashSet内部维护一个HashMap实例,把HashSet中的元素作为HashMap的键,值则使用一个固定的Object对象。HashSet的实现方式非常高效,可以在常量时间内进行添加、删除和查找操作,因此在需要快速查询元素的场景中经常使用。
5.1.2 特点:
- 唯一性:HashSet中不存在重复的元素。当插入重复元素时,HashSet不会发生变化。
- 无序性:HashSet中的元素没有特定的顺序。因此,不能用索引来访问HashSet中的元素,只能通过迭代器或集合操作来处理HashSet中的元素。
- 可用性:HashSet可以用于存储各种数据类型,包括数字、字符串、对象等。
- 对空间的利用率高:HashSet仅在需要时才增加其大小,因此可以有效地利用空间。
- 高效性:当HashSet的哈希表大小足够时,插入、删除和查找元素的操作具有常数时间复杂度O(1)。
5.2 LinkedHashSet
5.2.1 概述:
LinkedHashSet是Java中的一种集合类型,它是HashSet的变体,也是基于哈希表实现的,但同时也维护了一个双向链表用于维护插入次序。与HashSet不同的是,它可以按照元素插入的顺序遍历集合中的元素。LinkedHashSet中的元素也是无序且不可重复的。它在内部维护了一个哈希表,这个哈希表的元素和哈希表定位的方法与HashSet是一样的,只是每个元素还同时可以维护一个前驱和后继指针,这样就可以按照元素插入的先后顺序对其进行遍历。
5.2.2 与HashSet的区别:
LinkedHashSet的实现方式比HashSet略微慢一些,因为在维护链表指针的同时还要进行哈希表操作。但它的迭代效率非常高,因为它在遍历元素时只需要按照链表的顺序遍历即可。所以,在需要按照元素插入次序进行遍历的场景中,使用LinkedHashSet是非常合适的选择。
5.2.3 特点:
- 保持元素插入顺序:LinkedHashSet内部维护了一个双向链表,它可以按照插入顺序来遍历集合中的元素。当元素被添加到LinkedHashSet中时,它会被加入到双向链表的尾部。
- 元素唯一性:LinkedHashSet与其他Set一样,它不允许集合中有重复的元素。当尝试添加已经存在的元素时,LinkedHashSet会自动忽略该元素,并且不会引发异常。
- 基于哈希表实现:在往LinkedHashSet中添加元素时,元素的哈希值被用来计算出它应该在哈希表中的位置。这使得向集合中添加、删除和查找元素的速度都很快。
- 有序性:除了维护元素插入顺序,LinkedHashSet还支持按照元素的哈希码顺序或者重载了 equals() 方法实现的自然顺序进行遍历。
- 线程不安全:LinkedHashSet并不是线程安全的,它不保证多线程下的正确性。
5.3 TreeSet
5.3.1 概述:
TreeSet是一种基于红黑树(Red-Black Tree)的Set(集合)类型,它实现了SortedSet接口,可以对集合元素按照一定的比较规则进行排序,是一种有序的Set。
5.3.2 与HashSet的区别:
TreeSet是有序的Set,因此当需要进行大量的插入、删除等操作时,使用HashSet可能会比TreeSet更高效。
5.3.3 特点:
- 有序性:TreeSet中的元素按照一定的比较规则进行排序。默认情况下,它将元素按照自然排序(元素本身可比较)进行排序。
- 唯一性:TreeSet中不存在重复的元素。当插入重复元素时,TreeSet不会发生变化。
- 可用性:TreeSet可以用于存储各种数据类型,包括数字、字符、对象等。
- 高效性:由于TreeSet底层使用红黑树来实现,因此它的插入、删除和查找元素的操作都具有O(log n)的时间复杂度。
6. Set集合的去重原理
6.1原理:
利用了Java中的哈希表(Hash Table)来实现。当向Set集合中添加一个元素时,会先通过元素的哈希码(Hash Code)来确定元素在哈希表中的位置,然后将该元素插入到哈希表中。如果哈希表中已经存在该元素,则Set将不会将该元素插入到哈希表中,从而实现了去重。
6.2 代码演示:
package com.YX; import java.util.HashSet; import java.util.Iterator; import java.util.Set; public class Demo2 { public static void main(String[] args) { Set set = new HashSet<>(); // 创建一个Set,存储字符串 set.add(new Book(1, "君易")); set.add(new Book(2, "木易")); set.add(new Book(3, "栀龍")); set.add(new Book(1, "木易")); } } class Book { private int id; private String bname; public Book(int id, String bname) { super(); this.id = id; this.bname = bname; } public Book() { // TODO Auto-generated constructor stub } @Override public String toString() { return "Book [id=" + id + ", bname=" + bname + "]"; } @Override // 重写hashCode方法 public int hashCode() { System.out.println("调用hashCode方法"); final int prime = 31; int result = 1; result = prime * result + ((bname == null) ? 0 : bname.hashCode()); result = prime * result + id; return result; } // @Override // public boolean equals(Object obj) { // if (this == obj) // return true; // if (obj == null) // return false; // if (getClass() != obj.getClass()) // return false; // Book other = (Book) obj; // if (bname == null) { // if (other.bname != null) // return false; // } else if (!bname.equals(other.bname)) // return false; // if (id != other.id) // return false; // return true; // } }
如以上代码所示,我们先将equals()方法注释,然后重新添加一个重复的数据,通过查看控制台显示如图下所示:
如图所示,当我们重新添加新一个元素时,Set集合会通过该集合的哈希码值来确定该元素数据在Set集合中的数据数据中有对应数据的哈希码值与该数据的哈希码值一致的话则无法添加该数据,若哈希码值不一致则添加成功。但如果Set集合只通过元素数据之间的哈希码值来判断该元素数据是否重复的话则Set集合去重复是有缺陷的。
如下面代码所示,我若将元素的哈希码值修改的不一致所展示的效果又不一样。
package com.YX; import java.util.HashSet; import java.util.Iterator; import java.util.Set; public class Demo2 { public static void main(String[] args) { Set set = new HashSet<>(); // 创建一个Set,存储字符串 set.add(new Book(1, "君易")); set.add(new Book(2, "木易")); set.add(new Book(3, "栀龍")); // 添加重复数据测试 set.add(new Book(2, "木易")); } } class Book { private int id; private String bname; public Book(int id, String bname) { super(); this.id = id; this.bname = bname; } public Book() { // TODO Auto-generated constructor stub } @Override public String toString() { return "Book [id=" + id + ", bname=" + bname + "]"; } @Override // 重写hashCode方法 public int hashCode() { System.out.println("调用hashCode方法"); // final int prime = 31; // int result = 1; // result = prime * result + ((bname == null) ? 0 : bname.hashCode()); // result = prime * result + id; // 将上述代码注释,将每个数据值的哈希码值随机成不同的值,几乎无法重复。 return (int) (Math.random() * 1000000000); } }
此时我们再次的去重复添加一个数据在Set集合中,结果如下图所示:
虽然调用了hasCode()方法,但是应为每一个元素数据的哈希码值不一致则,即使元素内容一致的数据也会每天添加进Set集合中。
6.3 完美的Set集合去重:
完美去重原理:
首先先将要添加的元素数据的哈希码值与Set集合中的每一个哈希码值做比较,如果有一致的则会进入到下面equals()方法中进行一系列的比较,直到equals()返回的是false表示该数据不是重复数据则添加至Set集合中。
方式:
重写hasCode()方法和equals()方法。
代码演示
package com.YX; import java.util.HashSet; import java.util.Iterator; import java.util.Set; public class Demo2 { public static void main(String[] args) { Set set = new HashSet<>(); // 创建一个Set,存储字符串 set.add(new Book(1, "君易")); set.add(new Book(2, "木易")); set.add(new Book(3, "栀龍")); // 添加重复数据测试 set.add(new Book(2, "木易")); for (Object object : set) { System.out.println(object); } } } class Book { private int id; private String bname; public Book(int id, String bname) { super(); this.id = id; this.bname = bname; } public Book() { // TODO Auto-generated constructor stub } @Override public String toString() { return "Book [id=" + id + ", bname=" + bname + "]"; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((bname == null) ? 0 : bname.hashCode()); result = prime * result + id; return result; } @Override public boolean equals(Object obj) { System.out.println("equals()方法被调用了"); if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Book other = (Book) obj; if (bname == null) { if (other.bname != null) return false; } else if (!bname.equals(other.bname)) return false; if (id != other.id) return false; return true; } }
控制台输出结果:
7.Set集合排序
7.1Set集合实现类的排序方式:
- HashSet:HashSet是一种无序的Set,在默认情况下,它不支持排序。可以将其转换为List,再进行排序后,再将排序后的List转换为HashSet。
- LinkedHashSet:LinkedHashSet是一种有序的Set,它可以按照元素被添加的顺序进行遍历,因此可以通过先将元素插入LinkedHashSet,再将其转换为List,再进行排序,最后再将已排序的List转换为LinkedHashSet,从而得到有序的Set集合。
- TreeSet:TreeSet是一种基于红黑树的有序集合,它可以按照自然顺序或指定的比较器对元素进行排序。当需要对自定义对象排序时,需要重写Comparable接口或提供Comparator对象。
**注:**通常使用TreeSet集合的排序方式比较多,主要市场上大多数都是自定义对象数据类型较多。
7.2 TreeSet排序方式(为例)
package com.YX; import java.util.Comparator; import java.util.Objects; import java.util.Set; import java.util.TreeSet; class Person implements Comparable<Person> { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public int getAge() { return age; } // 实现Comparable接口的compareTo方法 @Override public int compareTo(Person other) { return Integer.compare(this.age, other.age); } // 重写equals方法,方便比较 @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null || getClass() != obj.getClass()) { return false; } Person person = (Person) obj; return Objects.equals(name, person.name) && Objects.equals(age, person.age); } // 重写hashCode方法,保证好的比较结果 @Override public int hashCode() { return Objects.hash(name, age); } // 重写toString方法方便输出 @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } } public class Demo3 { public static void main(String[] args) { Set<Person> treeSet = new TreeSet<>(); treeSet.add(new Person("木易", 20)); treeSet.add(new Person("君易", 18)); treeSet.add(new Person("栀龍", 22)); System.out.println("按照年龄排序的TreeSet:" + treeSet); Set<Person> treeSetByName = new TreeSet<>(Comparator.comparing(Person::getName)); treeSetByName.addAll(treeSet); System.out.println("按照姓名排序的TreeSet:" + treeSetByName); } }
排序后输出结果:
总结:
TreeSet是基于红黑树实现的,因此其排序原理也是基于红黑树的算法。
在TreeSet中,每个节点都是以某种规则排列好的,通常是按元素的大小进行排序,这就是红黑树的基本性质:左子树小于父节点,右子树大于父节点。在插入和删除元素时,TreeSet会根据元素的大小将元素插入到对应的位置。插入或删除元素会导致红黑树结构的调整,以维护红黑树的性质,从而保证集合元素按照排序规则排列。
TreeSet的默认排序方式是元素的自然排序,即元素实现了Comparable接口,并实现了compareTo方法。User对象必须实现Comparable接口,并重写compareTo方法,根据需要排序的属性进行比较,然后TreeSet就会根据compareTo方法的返回值对集合进行排序。
当需要使用元素的其他属性进行排序时,可以提供一个定制的Comparator实现来定义排序规则,Comparator实现compareTo方法来进行比较,然后TreeSet就会根据该Comparator实现对集合进行排序。
总的来说,TreeSet的排序原理是基于红黑树实现的,在插入或删除元素时,会根据元素的大小将元素插入到对应的位置,从而保证集合元素按照指定的排序规则排列。
树的算法。
在TreeSet中,每个节点都是以某种规则排列好的,通常是按元素的大小进行排序,这就是红黑树的基本性质:左子树小于父节点,右子树大于父节点。在插入和删除元素时,TreeSet会根据元素的大小将元素插入到对应的位置。插入或删除元素会导致红黑树结构的调整,以维护红黑树的性质,从而保证集合元素按照排序规则排列。
TreeSet的默认排序方式是元素的自然排序,即元素实现了Comparable接口,并实现了compareTo方法。User对象必须实现Comparable接口,并重写compareTo方法,根据需要排序的属性进行比较,然后TreeSet就会根据compareTo方法的返回值对集合进行排序。
当需要使用元素的其他属性进行排序时,可以提供一个定制的Comparator实现来定义排序规则,Comparator实现compareTo方法来进行比较,然后TreeSet就会根据该Comparator实现对集合进行排序。
总的来说,TreeSet的排序原理是基于红黑树实现的,在插入或删除元素时,会根据元素的大小将元素插入到对应的位置,从而保证集合元素按照指定的排序规则排列。