java中的Set集合详解

原文之部分某处有争论,读者谨慎阅读,后期本人再进行本文内容修正

1.简介

  • Set集合中的元素是无序的且不可重复, 如果试图把两个相同元素加入同一个Set集合中,则添加操作失败,add()方法返回false,且新元素不会被加入。

2.HasheSet底层数据结构

  • HashSet底层数据结构是哈希表,因此具有很好的存取和查找性能。
  • 哈希表:一个元素为链表的数组,综合了链表(存储速度快)和数组(查询速度快)的优点。

3.哈希表的存取原理

3.1 调用对象的hashCode()方法,获得要存储元素的哈希值。

3.2 将哈希值与表的长度(即数组的长度)进行求余运算得到一个整数值,该值就是新元素要存放的位置(即是索引值)。

  • 如果索引值对应的位置上没有存储任何元素,则直接将元素存储到该位置上。
  • 如果索引值对应的位置上已经存储了元素,则执行第3.3步。

3.3 遍历该位置上的所有旧元素,依次比较每个旧元素的哈希值和新元素的哈希值是否相同。

  • 如果有哈希值相同的旧元素,则执行第3.4步。
  • 如果没有哈希值相同的旧元素,则执行第3.5步。

3.4 比较新元素和旧元素的地址是否相同。如果地址值相同则用新的元素替换老的元素,停止比较。如果地址值不同,则新元素调用equals方法与旧元素比较内容是否相同。

  • 如果返回true,用新的元素替换老的元素,停止比较。
  • 如果返回false,则回到第3步继续遍历下一个旧元素。

3.5 说明没有重复,则将新元素存放到该位置上并让新元素记住之前该位置的元素。

4.HashSet特点

  • 无序
  • 集合中的元素值可以是null
  • hashSet不是同步的,如果多个线程同时访问一个Set,只要有一个线程修改了Set中的值,就必须进行同步处理,通常通过同步封装这个Set对象来完成同步,如果不存在这样的对象,可以使用Collections.synchronizedSet()方法完成。

5.HashSet保证唯一性:实体类中重写hashCode和equals方法

  • 实体类
public class Person {
    String name;
    int age;
    public Person(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    @Override
    public String toString() {
        return "Person [name=" + name + ", age=" + age + "]";
    }
}
  • 测试代码
@Test
public void testHashSet(){
    Person p1 = new Person("钟梅", 25);
    Person p2 = new Person("王兴", 34);
    Person p3 = new Person("张三", 18);
    Person p4 = new Person("李四", 21);
    Person p5 = new Person("李四", 21);
    HashSet hashSet = new HashSet<>();
    hashSet.add(p1);
    hashSet.add(p2);
    hashSet.add(p3);
    hashSet.add(p4);
    hashSet.add(p5);
    for (Person person : hashSet) {
        System.out.println(person.getName()+ "----------" + person.getAge());
    }
}
  • 输出结果
    java中的Set集合详解_第1张图片
  • 由上可以看到,结果中出现重复元素。在实体类Person中重写hashCode和equals方法:
//判断判断两个对象是否相等,对象是否存在,对象的name和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 &&
            Objects.equals(name, person.name);
}

//返回对象的name和age的hash值
@Override
public int hashCode() {
    return Objects.hash(name, age);
}
  • 重写之后不是判断两个对象hashCode是否相等,而是判断对象的name和age是否同时相等,如果同时相等则判断为同一对象,不能重复出现在集合中。
    再次遍历集合,运行结果:
    java中的Set集合详解_第2张图片
  • 可以看到重复的元素已经被覆盖,保证了集合中元素的唯一性。

经验:为什么不直接用数组,而用HashSet?

  • 因为数组的索引是连续的而且数组的长度是固定的,无法自由增加数组的长度。而HashSet就不一样了,HashCode表用每个元素的hashCode值来计算其存储位置,从而可以自由增加HashCode的长度,并根据元素的hashCode值来访问元素。而不用一个个遍历索引去访问,这就是它比数组快的原因。

6.LinkedHashSet类

元素顺序是添加元素的顺序

  • LinkedHashSet集合也是根据元素的hashCode值来决定元素的存储位置,但它同时使用链表维护元素的次序,这样使得元素看起来是以插入的顺序保存的,也就是说当遍历集合LinkedHashSet集合里的元素时,集合将会按元素的添加顺序来访问集合里的元素。
  • 输出集合里的元素时,元素顺序总是与添加顺序一致。但是LinkedHashSet依然是HashSet,因此它不允许集合重复。

7.ThreeSet类

7.1:ThreeSet类特点

  • TreeSet可以确保集合元素处于排序状态。
  • 内部存储机制:TreeSet内部实现的是红黑树,默认整形排序为从小到大。
    java中的Set集合详解_第3张图片

7.2:ThreeSet的方法

  • 与HashSet集合相比,TreeSet还提供了几个额外方法:
//如果TreeSet采用了定制顺序,则该方法返回定制排序所使用的Comparator,如果TreeSet采用自然排序,则返回null;
Comparator comparator();
//返回集合中的第一个元素;
Object first();
//返回集合中的最后一个元素;
Object last();
//返回指定元素之前的元素。
Object lower(Object e);
//返回指定元素之后的元素。
Object higher(Object e);
//返回此Set的子集合,含头不含尾;
SortedSet subSet(Object fromElement,Object toElement);
//返回此Set的子集,由小于toElement的元素组成;
SortedSet headSet(Object toElement);
//返回此Set的子集,由大于fromElement的元素组成;
SortedSet tailSet(Object fromElement);
  • 用法示例:
@Test
public void testTreeSet(){
    TreeSet nums = new TreeSet<>();
    //向集合中添加元素
    nums.add(5);
    nums.add(2);
    nums.add(15);
    nums.add(-4);
    //输出集合,可以看到元素已经处于排序状态
    System.out.println(nums);//[-4, 2, 5, 15]
    System.out.println("集合中的第一个元素:"+nums.first());//集合中的第一个元素:-4
    System.out.println("集合中的最后一个元素:"+nums.last());//集合中的最后一个元素:15
    System.out.println("集合小于4的子集,不包含4:"+nums.headSet(4));//集合小于4的子集,不包含4:[-4, 2]
    System.out.println("集合大于5的子集:"+nums.tailSet(2));//集合大于5的子集:[2, 5, 15]
    System.out.println("集合中大于等于-3,小于4的子集:"+nums.subSet(-3,4));//集合中大于等于-3,小于4的子集:[2]
}
  • 输出结果:
    java中的Set集合详解_第4张图片
  • 从上面的运行结果可以看出输出的集合已经按从小到大排好了,但是问题来了,只能从小到大排序吗?如果是字符对象应按该怎样的顺序排序?如果是一个对象又按怎样的顺序排序呢?遵循怎样的排序规则呢?

7.3 TreeSet的排序实现

  • TreeSet支持两种排序方法:自然排序和定制排序,在默认情况下,采用的是自然排序。

7.3.1 自然排序

  • TreeSet会调用集合元素的compareTo(Objec obj)方法来比较元素之间的大小关系,然后将集合元素按升序排列,这就是自然排序。

  • 拓展:
    Java提供了一个Comparable接口,该接口里定义了一个compareTo(Object obj)方法,该方法返回一个整数值,实现该接口的类必须实现该方法,实现了该接口的类必须实现该方法,实现接口的类就可以比较大小了。当调用一个一个对象调用该方法与另一个对象进行比较时, compareTo(Object obj)如果返回0表示两个对象相等;如果返回正整数则表明obj1大于obj2,如果是负整数则相反。

  • 案例:
    实现存储Person类的集合,排序方式,按年龄大小,如果年龄相等,则按name字符串长度,如果长度相等则比较字符。如果name和age都相等则视为同一对象。

public class Person implements Comparable{
    String name;
    int age;
    public Person(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    @Override
    public String toString() {
        return "Person [name=" + name + ", age=" + age + "]";
    }

    @Override
    public int compareTo(Person o) {
        //比较age
        int num=this.age-o.age;
        //如果age相等则比较name长度
        int num1=num==0?this.name.length()-o.name.length():num;
        //如果前两者都相等则比较name字符串
        int num2=num1==0?this.name.compareTo(o.name):num1;
        return num2;
    }
}
  • 测试
@Test
public void testTreeSet(){
    TreeSet tree = new TreeSet<>();
    //向集合中添加元素
    tree.add(new Person("孙悟空",16));
    tree.add(new Person("孙悟空",17));
    tree.add(new Person("孙悟空",16));
    tree.add(new Person("唐僧",16));
    tree.add(new Person("沙悟净",23));
    tree.add(new Person("唐僧",30));
    //遍历
    System.out.println(tree);
}
  • 输出:
    在这里插入图片描述
  • 从运行结果可以看到满足定义的排序规则。
  • 当把一个对象添加进集合时,集合调用该对象的CompareTo(Object obj)方法与容器中的其他对象比较大小,然后根据红黑树结构中找到它的存储位置。如果两个对象相等则新对象无法加入到集合中。

7.3.3 定制排序

  • TreeSet的自然排序是根据集合元素的大小,TreeSet将它们以升序排列。如果需要实现定制排序,例如降序排序,则可通过Comparator接口的帮助。该接口里包含一个int compare(T o1,T o2)方法,用于比较o1和o2的大小。由于Comparator是一个函数式接口,因此还可以使用Lambda表达式来代替Comparator子类对象。
@Test
public void testTreeSet2(){
    TreeSet nums = new TreeSet<>((a,b)->-(a-b));
    //向集合中添加元素
    nums.add(5);
    nums.add(2);
    nums.add(15);
    nums.add(-4);
    //输出集合,可以看到元素已经处于排序状态
    System.out.println(nums);//[15, 5, 2, -4]
}

原文地址 https://blog.csdn.net/mashaokang1314/article/details/83721792

你可能感兴趣的:(java,数据结构,Set集合)