五、Java集合类(二)Set

    xiaoxiao2022-07-12  155

    今天的博客主题

          基础篇 --》常用类 --》Java集合类(二)Set


    在上一篇介绍了Java集合类里的List集合,这篇就说一下Set集合。

     

    Set

    public interface Set<E> extends Collection<E> {}

     

    Set是一个接口继承了Collection接口。

    Set的一个体系结构大概是这样子的

    通过源码里的类注释上得知,Set是一种无序,不可重复的集合。

    Set接口里提供了许多操作Set集合的方法,主要看下具体实现吧。

     

    HashSet

     

    public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable{}

    HashSet继承了AbstractSet,实现了Set、Cloneable和Serializable接口。

    AbstractSet继承了AbstractCollection类,实现了Set接口。

    特点:

    底层是有hash算法实现的,具有很好的存取查找性能。元素不能重复元素无序允许插入null值线程不安全。(若两个线程同时操作,需要通过代码来实现同步)

    HashSet底层数据结构实现是有HashMap来完成的,属于哈希表结构。

    新增元素相当于HashMap的key,而value默认为一个固定的Object。

    是通过hashCode值来确定集合中的位置,由于Set集合中并没有下标的概念,所以并没有像List一样提供一个get()方法,就可以获取元素。

    当获取HashSet中某个元素时,只能通过遍历集合的方式进行equals()比较来实现。

    核心方法(常用API)

     

    public static void main(String[] args) { // 声明一个HashSet集合 Set hashSet = new HashSet(); // 往集合添加元素 hashSet.add("1"); hashSet.add("2"); hashSet.add("2"); hashSet.add("1"); hashSet.add("3"); System.out.println(hashSet); // [1, 2, 3] List list = new ArrayList(); list.add("3"); list.add("4"); list.add("5"); list.add("5"); System.out.println(list); // [3, 4, 5, 5] // 往集合添加指定集合。会去除重复的元素 hashSet.addAll(list); System.out.println(hashSet); // [1, 2, 3, 4, 5] // 移除集合里指定元素 hashSet.remove("2"); System.out.println(hashSet); // [1, 3, 4, 5] // 获取集合长度 int size = hashSet.size(); System.out.println(size); // 4 // 判断集合是不是空的 boolean empty = hashSet.isEmpty(); System.out.println(empty); // false // 判断集合是否包含指定元素 boolean contains = hashSet.contains("3"); System.out.println(contains); // true List list1 = new ArrayList(); list1.add("1"); // 判断集合里是否包含了一个指定的集合 boolean containsAll = hashSet.containsAll(list1); System.out.println(containsAll); // true list1.add("2"); boolean containsAll2 = hashSet.containsAll(list1); System.out.println(containsAll2); // false System.out.println(hashSet); // [1, 3, 4, 5] System.out.println(list1); // [1, 2] // 保留指定集合里的内容,结果取交集 hashSet.retainAll(list1); System.out.println(hashSet); // [1] // 清空集合 hashSet.clear(); // [] }

    LinkedHashSet

     

    public class LinkedHashSet<E> extends HashSet<E> implements Set<E>, Cloneable, java.io.Serializable {}

    LinkedHashSet继承了HashSet,实现了Set、Cloneable和Serializable接口。

    LinkedHashSet使用的是LinkedHashMap。

    LinkedHashSet底层数据结构采用双向链表,可以保证元素的插入顺序,又因为是HashSet的子类,所以插入的元素又不能重复。

    LinkedHashSet需要维护元素的插入顺序,因此性能略低于HashSet的性能,但在迭代访问Set里的元素时将有很好的性能,因为它以链表形式来实现的。

    LinkedHashSet源码里只有一些构造函数,一些具体操作实现的方法都是用父类HashSet的。

     

    TreeSet

     

    public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>, Cloneable, java.io.Serializable{}

    TreeSet继承了AbstractSet,实现了NavigableSet、Cloneable和Serializable接口。

    我们发现TreeSet并没有实现Set接口,而是实现了NavigableSet,NavigableSet继承了SortedSet,SortedSet又继承了Set。

    TreeSet是一个有序的集合,但是最终实现与Set还是需要遵守Set的特性,元素不会重复。

    TreeSet是基于TreeMap实现的,底层数据结构是红黑树(二叉树)

    TreeSet也是SortedSet接口的唯一实现类,通过接口名称,我们能推知出,TreeSet是可以排序的。

    特点:

    支持排序(确保集合里的元素处于排序状态,不是插入的顺序排列的)元素不可重复树形结构不支持随机遍历,只能通过迭代器进行遍历

    用来排序的, 可以指定一个顺序, 对象存入之后会按照指定的顺序排列

    排序方法

    自然排序比较器排序(Comparator)

    核心方法(常用API)

     

    public static void main(String[] args) { // 声明Set集合 TreeSet treeSet = new TreeSet(); // 往集合添加元素 treeSet.add("1"); treeSet.add("2"); treeSet.add("3"); treeSet.add("4"); treeSet.add("5"); System.out.println(treeSet); // [1, 2, 3, 4, 5] // 获取集合长度 int size = treeSet.size(); System.out.println(size); // 5 // 判断集合是不是为空 boolean empty = treeSet.isEmpty(); System.out.println(empty); // false // 判断集合是否包含某个元素 boolean contains = treeSet.contains("2"); System.out.println(contains);// true // 获取集合第一个元素 Object first = treeSet.first(); System.out.println(first); // 1 // 获取集合最后一个元素 Object last = treeSet.last(); System.out.println(last); // 5 // 返回小于给定键的最大键 Object lower = treeSet.lower("3"); System.out.println(lower); // 2 // 返回小于或等于给定键的最大键 Object floor = treeSet.floor("3"); System.out.println(floor); // 3 // 返回大于或等于给定键的最小键 Object ceiling = treeSet.ceiling("3"); System.out.println(ceiling); // 3 // 移除最小元素并返回 Object pollFirst = treeSet.pollFirst(); System.out.println(pollFirst); // 1 System.out.println(treeSet); // [2, 3, 4, 5] // 移除最大元素并返回 Object pollLast = treeSet.pollLast(); System.out.println(pollLast); // 5 System.out.println(treeSet); // [2, 3, 4] // 移除集合内指定元素 treeSet.remove("3"); System.out.println(treeSet); // [1, 2, 4] // 清空集合元素 treeSet.clear(); System.out.println(treeSet); // [] }

    TreeSet有一个比较坑的地方,看一下

    在我们之间讲过到的集合里,对一个集合指定类型,进行`存放数据,是没有问题的,但是在TreeSet这是不行的。

     

    // User对象 @Data public class User implements Comparable<User>{ private String name; private Integer age; public User() { } public User(String name, Integer age) { this.name = name; this.age = age; } } public static void main(String[] args) { // 声明Set集合 TreeSet<User> treeSetUser = new TreeSet(); treeSetUser.add(new User("zhangsan", 22)); treeSetUser.add(new User("lisi", 20)); treeSetUser.add(new User("wangwu", 25)); System.out.println(treeSetUser); }

    当执行上面方法时会抛出如下异常

    Exception in thread "main" java.lang.ClassCastException: com.xxx.crdms.controller.User cannot be cast to java.lang.Comparable at java.util.TreeMap.compare(TreeMap.java:1294) at java.util.TreeMap.put(TreeMap.java:538) at java.util.TreeSet.add(TreeSet.java:255) at com.xxx.crdms.controller.TestDemoController.main(TestDemoController.java:34)

     

    通过异常信息我们得知是出现了类型转换异常。原因在于我们需要告诉TreeSet如何来进行比较元素,如果不指定,就会抛出这个异常。

    这个TreeSet我们认为是用来存储的,为什么要排序呢?

    在上面介绍的时候就说了TreeSet有一个特点就是可以指定排序存储。

    这个异常出现原因就是我们没有指定排序方式。

    那怎么解决呢?

    需要在我们指定TreeSet集合泛型的对象类里实现Comparable接口,并重写接口中的compareTo方法

    就像这样,实现Comparable,重写compareTo方法。

     

    @Data public class User implements Comparable<User>{ private String name; private Integer age; public User() { } public User(String name, Integer age) { this.name = name; this.age = age; } @Override public int compareTo(User o) { if(this.age > o.age){ return 1; }else if(this.age < o.age){ return -1; }else{ return this.name.compareTo(o.name); } } } // 这里返回值有三中情况。对于这个方法里的排序方式根据需求来写。这只是举栗子 return 0; //当compareTo方法返回0的时候集合中只有一个元素 return 1; //当compareTo方法返回正数的时候集合会怎么存就怎么取 return -1; //当compareTo方法返回负数的时候集合会倒序存储

    如果将compareTo()返回值写死为0,元素值每次比较,都认为是相同的元素,这时就不再向TreeSet中插入除第一个外的新元素。所以TreeSet中就只存在插入的第一个元素。

    如果将compareTo()返回值写死为1,元素值每次比较,都认为新插入的元素比上一个元素大,于是二叉树存储时,会存在根的右侧,读取时就是正序排列的。

    如果将compareTo()返回值写死为-1,元素值每次比较,都认为新插入的元素比上一个元素小,于是二叉树存储时,会存在根的左侧,读取时就是倒序序排列的。

    这种排序称为自然排序(Comparable)

    TreeSet自然排序步骤

    需要排序的类实现Comparable接口重写compareTo方法,方法内自定义排序方式。

    实现了自然排序之后在去执行那个main方法就不会出现异常了。

    上边说了两种排序,另一个就是比较器的排序了。看下如何实现

    第一种方式:通过一个匿名的内部类来实现

    public static void main(String[] args) { TreeSet<User> treeSetUser = new TreeSet<User>(new Comparator<User>() { @Override public int compare(User u1, User u2) { // 先判断姓名长度的大小 int num = u1.getName().length() - u2.getName().length(); // 姓名长度一致时,比较内容是否一致 int num2 = num==0 ?u1.getName().compareTo(u2.getName()) :num; // 姓名内容一致时,比较年龄 int num3 = num2==0 ?(u1.getAge() - u2.getAge()) :num2; return num3; } }); treeSetUser.add(new User("zhangsan", 22)); treeSetUser.add(new User("lisi", 20)); treeSetUser.add(new User("wangwu", 25)); System.out.println(treeSetUser); } 输出: [User(name=lisi, age=20), User(name=wangwu, age=25), User(name=zhangsan, age=22)]

     

    第二种方式:自定义一个类,实现Comparator,重写compare方法

    public class ComparatorSort implements Comparator<User> { @Override public int compare(User u1, User u2) { // 先判断姓名长度的大小 int num = u1.getName().length() - u2.getName().length(); // 姓名长度一致时,比较内容是否一致 int num2 = num==0 ?u1.getName().compareTo(u2.getName()) :num; // 姓名内容一致时,比较年龄 int num3 = num2==0 ?(u1.getAge() - u2.getAge()) :num2; return num3; } } public static void main(String[] args) { TreeSet<User> treeSetUser = new TreeSet<>(new ComparatorSort()); treeSetUser.add(new User("zhangsan", 22)); treeSetUser.add(new User("lisi", 20)); treeSetUser.add(new User("wangwu", 25)); System.out.println(treeSetUser); } 输出: [User(name=lisi, age=20), User(name=wangwu, age=25), User(name=zhangsan, age=22)]

    同样两种方式都可实现比较器排序。

     

    总结

    Set是一个接口,继承Collection接口。

    Set是一个无序,元素不可重复的集合。

    HashSet是Set接口的一个实现类,依赖于hashCode()与equals()方法。

    在存储时首先首先比较哈希值:

    如果相同比较地址值或者equals(),相同说明元素重复不添加,不同说明元素不重复,添加到集合中不相同直接添加到集合中

    如果一个类中没有重写hashCode()和equals()则直接继承Object类

    LinkedHashSet是HashSet的一个子类,也是Set集合比较特殊的集合,是一个有序的set集合。底层是通过链表与哈希表来实现的。

    TreeSet也是Set接口的一个实现类,是一个排序集合。能够对元素按照某种规则进行排序存储。

    排序方式有两种:自然排序和比较器排序。

    自然排序是将存储的对象实现Comparable接口,重写compareTo()方法,自定义排序方式。

    比较器排序可以指定内部匿名类或新建类实现Comparator接口,都需要重写compare方法,来自定义排序方式。

    HashSet底层是通过HashMap实现的,查询比较快,元素必须定义hashCode方法。(比较常用)

    LinkedHashSet底层数据结构是链表,具有HashSet的查询速度; 内部使用链表维护元素插入的次序; 元素必须定义hashCode方法。

    TreeSet底层数据结构是二叉树结构,可以从Set中提取有序的序列; 元素必须实现Comparable接口。

     


     

     

     

     

    最新回复(0)