Set集合

  • 特点:无序,无下标,元素不可重复
  • 方法:全部继承自Collection中的方法

不包含重复元素的集合。

更正式地,集合不包含一对元素e1e2 ,使得e1.equals(e2) ,并且最多一个空元素。

正如其名称所暗示的那样,这个接口模拟了数学抽象。

Set接口除了继承自Collection接口的所有构造函数的合同以及add,equalshashCode方法的合同外 增加了其他规定。 其他继承方法的声明也包括在这里以方便。 (伴随这些声明的规范已经量身定做Set接口,但它们不包含任何附加的规定。)

构造函数的额外规定并不奇怪,所有构造函数都必须创建一个不包含重复元素的集合(如上所定义)。

注意:如果可变对象用作设置元素,则必须非常小心。 如果对象的值以影响equals比较的方式更改,而对象是集合中的元素, 则不指定集合的行为。 这种禁止的一个特殊情况是,一个集合不允许将其本身作为一个元素。

一些集合实现对它们可能包含的元素有限制。 例如,一些实现禁止空元素,有些实现对元素的类型有限制。 尝试添加不合格元素会引发未经检查的异常,通常为NullPointerExceptionClassCastException 。 尝试查询不合格元素的存在可能会引发异常,或者可能只是返回false; 一些实现将展现出前者的行为,一些实现将展现出后者。 更一般来说,尝试对不符合条件的元素的操作,其完成不会导致不合格元素插入到集合中,可能会导致异常,或者可能会成功执行该选项。 此异常在此接口的规范中标记为“可选”。

Set接口的使用

  • Set的实现类

    • HashSet【重点】:

      1. 基于HashCode实现元素不重复
      2. 当存入元素的哈希码相同时,会调用equals进行确认,如果结果为true,则拒绝后者存入。
    • TreeSet:

      1. 基于排列顺序实现元素不重复。
package com.set.demo;

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

public class Demo1 {
    public static void main(String[] args) {
        Set<String> set = new HashSet();

        /*
         * Set
         * 特点:
         *       1.无序,无下标
         *       2.不可重复
         * */
        set.add("苹果");
        set.add("小米");
        set.add("华为");
        set.add("三星");
        //set.add("三星");//重复不可添加
        System.out.println(set.toString());
        System.out.println(set.size());

//      遍历,因为Set是没有下标的,所以遍历只能通过增强for或者迭代器来实现
        System.out.println("-------------增强for-------------");
        for (String s : set
        ) {
            System.out.println(s);
        }
        System.out.println("-------------迭代器-------------");
        Iterator<String> iterator = set.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
        System.out.println("==============================");
//        判断
        System.out.println(set.contains("小米"));
        System.out.println(set.isEmpty());

//        删除
        set.remove("小米");
        set.clear();

    }
}

Set的实现类

HashSet【重点】:

  • 基于HashCode计算元素存放的位置
  • 当存入元素的哈希码相同时,会调用equals进行确认,如果结果为true,则拒绝后者存入。
  • 存储结构:哈希表(数组+单向链表+红黑树)

哈希表结构:

集合框架——Set集合的使用与数据结构详解_二叉树

package com.set.demo;

import com.sun.org.apache.xalan.internal.xsltc.compiler.util.StringStack;

import java.util.HashSet;
import java.util.Iterator;

public class Demo2 {
    public static void main(String[] args) {

        HashSet<String> strings = new HashSet<>();
        /*
         * HashSet
         * 存储结构:哈希表(数组+链表+红黑树)
         * */

        strings.add("孙悟空");
        strings.add("牛魔王");
        strings.add("猪八戒");
        strings.add("沙和尚");
        System.out.println(strings.size());
        System.out.println(strings.toString());

        /*删除*/
        strings.remove("牛魔王");
        /*遍历*/
        System.out.println("------------增强for--------------");
        for (String string : strings) {
            System.out.println(string);
        }
        System.out.println("------------迭代器--------------");
        Iterator<String> iterator = strings.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }

        /*判断*/
        strings.contains("牛魔王");
        strings.isEmpty();



    }
}
  • 判断是否重复解析
package com.set.demo;

import java.util.HashSet;

public class Demo3 {
    public static void main(String[] args) {

        /*
         * HashSet
         * 1.HashSet在存储元素时,首先会比较Hashcode,如果Hashcode相同则会调用equals方法,则存储在对应的列表中所在位置没有被占据,则会直接存入
         * 2.如果列表所在位置已被占据,则会形成链表。
         *
         * */

        HashSet<Person> people = new HashSet<>();
        Person p1 = new Person("孙悟空", 328);
        Person p2 = new Person("猪八戒", 853);
        Person p3 = new Person("牛魔王", 928);
        Person p4 = new Person("沙和尚", 728);

        people.add(p1);
        people.add(p2);
        people.add(p3);
        people.add(p4);
        people.add(p1);
        System.out.println(people.size());  // 此时我们发现重复的P1并没有被加入集合中
        System.out.println(people.toString());

        people.add(new Person("孙悟空", 328)); // 此时我们发现新的对象可以被加入集合中
        /*因为new 会重新再内存中申请一个空间,后者传入的引用就不再是p1了所以可以添加成功
        * 如果我们不希望它添加成功,我们认为,名字和年龄相同就是同一个对象,这是我们可以重写
        * Person类的 hashCode 和 equals方法来改变其判定。
        * */
        System.out.println(people.size());  // 此时我们发现重复的P1并没有被加入集合中
        System.out.println(people.toString());


    }
}
  • 重写 hashCode 方法和 equals 方法
package com.set.demo;

import java.util.Objects;

public class Person {
    String name;
    int age;

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

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

    @Override
    public boolean equals(Object o) {

        if (o == null) {
            return false;
        } else {
            if (o instanceof Person && this.name.equals(((Person) o).name) && this.age == ((Person) o).age) {
                return true;
            }
            return false;
        }
    }

    @Override
    public int hashCode() {
        return name.hashCode() + age;
    }
}

那么为什么选用31作为基数呢?先要明白为什么需要HashCode.每个对象根据值计算HashCode,这个code大小虽然不奢求必须唯一(因为这样通常计算会非常慢),但是要尽可能的不要重复,因此基数要尽量的大。另外,31*N可以被编译器优化为
左移5位后减1,有较高的性能。其实选用31还是有争议

public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        char val[] = value;

        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}
  • 结论:
    1. 31是一个质数,它可以在计算时,减少散列的重复可能性
    2. i*31 会被编译器优化成 (i<<5)-i, 可以优化运行效率

TreeSet

  • 基于排列顺序实现不重复。
  • 实现了SortedSet接口,对集合元素自动排序。
  • 元素对象的类型必须实现Comparable接口,指定排序规则。
  • 通过CompareTo方法确定是否为重复元素。
  • 存储结构:红黑树

二分查找树:

集合框架——Set集合的使用与数据结构详解_链表_02

红黑树:

集合框架——Set集合的使用与数据结构详解_java_03

package com.set.demo;

import java.util.Iterator;
import java.util.TreeSet;

public class Demo4 {
    public static void main(String[] args) {
        TreeSet<String> strings = new TreeSet<>();

        /*
         * TreeSet
         * 存储结构:红黑树
         * */
        strings.add("xyz");
        strings.add("hello");
        strings.add("abc");

        System.out.println("元素个数:" + strings.size());
        System.out.println(strings.toString());

//        strings.remove("xyz");
//        strings.clear();
        /*
         * 增强for
         * */
        System.out.println("--------------增强for-------------------");
        for (String s : strings) {
            System.out.println(s);
        }
        System.out.println("--------------迭代器-------------------");
        Iterator<String> iterator = strings.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }

//        判断
        strings.contains("xyz");
        strings.isEmpty();


    }
}
  • ClassCastException---->实现ComParable接口,实现compareTo方法
package com.set.demo;

import java.util.TreeSet;

public class Demo5 {
    public static void main(String[] args) {

        TreeSet<Person> people = new TreeSet<>();
        Person p1 = new Person("孙悟空", 528);
        Person p2 = new Person("猪八戒", 753);
        Person p3 = new Person("牛魔王", 1028);
        Person p4 = new Person("沙和尚", 758);
        people.add(p1);
        people.add(p2);
        people.add(p3);
        people.add(p4);
        people.add(p1);
        System.out.println(people.size());  //ClassCastException 因为内部存储结构是红黑树,程序不知道该如何比较排序
        System.out.println(people.toString());
        /*
        * 要求元素必须要实现ComParable接口,该接口中有一个方法compareTo,如果返回值为0,则认为相同。
        *
        *  @Override
            public int compareTo(Person o) {
            int n1 = this.name.compareTo(o.name);
            int n2 = this.age - o.getAge();
            return n1 == 0 ? n2 : n1;
            }
        * */

    }
}
package com.set.demo;

import java.util.Objects;

public class Person implements Comparable<Person> {
    String name;
    int age;

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

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

    @Override
    public boolean equals(Object o) {

        if (o == null) {
            return false;
        } else {
            if (o instanceof Person && this.name.equals(((Person) o).name) && this.age == ((Person) o).age) {
                return true;
            }
            return false;
        }
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    @Override
    public int hashCode() {
        return name.hashCode() + age;
    }

    @Override
    public int compareTo(Person o) {
        int n1 = this.name.compareTo(o.name);
        int n2 = this.age - o.getAge();
        return n1 == 0 ? n2 : n1;
    }
}
  • 除了通过实现实现ComParable接口的方式来解决这一问题之外,TreeSet还有一个构造器可以通过实现Comparator接口的方式建立匿名内部类的方式来定制比较器来解决比较问题。
package com.set.demo;

import java.util.Comparator;
import java.util.TreeSet;

public class Demo6 {
    public static void main(String[] args) {

        TreeSet<Person> people = new TreeSet<>(new Comparator<Person>() {
            /*
            * TreeSet
            * 我们除了实现ComParable接口之外
            * 还可以在建立集合时,通过实现Comparator接口的方式来建立一个匿名内部类来自定义比较器
            * */
            @Override
            public int compare(Person o1, Person o2) {
                int n1 = o1.name.compareTo(o2.name);
                int n2 = o1.age - o2.getAge();
                return n1 == 0 ? n2 : n1;
            }
        });
        Person p1 = new Person("孙悟空", 528);
        Person p2 = new Person("猪八戒", 753);
        Person p3 = new Person("牛魔王", 1028);
        Person p4 = new Person("沙和尚", 758);
        people.add(p1);
        people.add(p2);
        people.add(p3);
        people.add(p4);
        people.add(p1);
        System.out.println(people.size());
        System.out.println(people.toString());
    }
}

TreeSet 小案例

  • 要求字符串按照字符串长度进行排序
  • 如果长度一样,就按照字母顺序进行排序
package com.set.demo;

import java.util.Comparator;
import java.util.TreeSet;

public class Demo7 {
    public static void main(String[] args) {

        TreeSet<String> strings = new TreeSet<>(new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                int length = o1.length() - o2.length();
                int n2 = o1.compareTo(o2);

                return length == 0 ? n2 : length;
            }
        });

        boolean s1 = strings.add("abc");
        boolean s2 = strings.add("xyzsadjk");
        boolean s3 = strings.add("helijdli");
        boolean s4 = strings.add("xyz");

        System.out.println(strings.toString());


    }
}