Java Set类的常见实现类 TreeSet和HashSet

Java 进阶篇

笔记首页

序号内容链接地址
1Java核心Apihttps://blog.csdn.net/weixin_44141495/article/details/108277753
2Java集合框架接口https://blog.csdn.net/weixin_44141495/article/details/108146574
3List实现类https://blog.csdn.net/weixin_44141495/article/details/108146613
4Set实现类https://blog.csdn.net/weixin_44141495/article/details/108146744
5Map实现类https://blog.csdn.net/weixin_44141495/article/details/108146656
6常见接口https://blog.csdn.net/weixin_44141495/article/details/108269208

Set集合的常用实现类 TreeSet和HashSet

HashSet

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

  • HashSet 按 Hash 算法来存储集合中的元素,因此具有很好的存取、查找、删除性能。

特点

  • 不能保证元素的排列顺序

  • HashSet 不是线程安全的

  • 集合元素可以是 null

  • HashSet 集合判断两个元素相等的标准

    • 两个对象通过 hashCode() 方法比较相等,并且两个对象的 equals() 方法返回值也相等。

    • 对于存放在Set容器中的对象,**对应的类一定要重写equals()**和hashCode(Object obj)方法,以实现对象相等规则。即:“相等的对象必须具有相等的散列码”。

向HashSet中添加元素的过程

  • 当向 HashSet 集合中存入一个元素时,HashSet 会调用该对象的 hashCode() 方法来得到该对象的 hashCode 值,然后根据 hashCode 值,通过某种散列函数决定该对象在 HashSet 底层数组中的存储位置。(这个散列函数会与底层数组的长度相计算得到在数组中的下标,并且这种散列函数计算还尽可能保证能均匀存储元素,越是散列分布,该散列函数设计的越好)

  • 如果两个元素的hashCode()值相等,会再继续调用equals方法,如果equals方法结果为true,添加失败;如果为false,那么会保存该元素,但是该数组的位置已经有元素了,那么会通过链表的方式继续链接。

  • 如果两个元素的 equals() 方法返回 true,但它们的 hashCode() 返回值不相等,hashSet 将会把它们存储在不同的位置,但依然可以添加成功。

重写 hashCode() 方法的基本原则

  • 在程序运行时,同一个对象多次调用 hashCode() 方法应该返回相同的值。

  • 当两个对象的 equals() 方法比较返回 true 时,这两个对象的 hashCode()

方法的返回值也应相等。

  • 对象中用作 equals() 方法比较的 Field,都应该用来计算 hashCode 值。

重写 equals() 方法的基本原则

以自定义的Customer类为例,何时需要重写equals()?

  • 当一个类有自己特有的“逻辑相等”概念,当改写equals()的时候,总是

要改写hashCode(),根据一个类的equals方法(改写后),两个截然不

同的实例有可能在逻辑上是相等的,但是,根据Object.hashCode()方法,

它们仅仅是两个对象。

  • 因此,违反了“相等的对象必须具有相等的散列码”。

  • 结论:复写equals方法的时候一般都需要同时复写hashCode方法,hashCode的对象的属性也应该参与到equals()中进行计算。

示例

例:观察HashSet

import java.util.HashSet;
import java.util.Set;
public class HashSetDemo01 {
    public static void main(String[] args) {
        Set<String> all = new HashSet<String>(); // 实例化Set接口对象
        all.add("A");
        all.add("B");
        all.add("C");
        all.add("D");
        all.add("E");
        System.out.println(all);
    }
}

​ 使用HashSet 实例化的Set 接口实例,本身属于无序的存放。

​ 在Collection 接口中定义了将集合变为对象数组,可以用来进行输出。

import java.util.HashSet;
import java.util.Set;
public class HashSetDemo02 {
    public static void main(String[] args) {
        Set<String> all = new HashSet<String>(); // 实例化Set接口对象
        all.add("A");
        all.add("B");
        all.add("C");
        all.add("D");
        all.add("E");
        Object obj[] = all.toArray(); // 将集合变为对象数组
        for (int x = 0; x < obj.length; x++) {
            System.out.print(obj[x] + "、");
        }
    }
}

​ 但是,以上的操作不好,因为在操作的时候已经指定了操作的泛型类型,那么现在最好的做法是由泛型所指定的类型变为指定的数组。

<T> T[] toArray(T[] a)
import java.util.HashSet;
import java.util.Set;
public class HashSetDemo03 {
    public static void main(String[] args) {
        Set<String> all = new HashSet<String>(); // 实例化Set接口对象
        all.add("A");
        all.add("B");
        all.add("C");
        all.add("D");
        all.add("E");
        String[] str = all.toArray(new String[] {});// 变为指定的泛型类型数组
        for (int x = 0; x < str.length; x++) {
            System.out.print(str[x] + "、");
        }
    }
}

​ 下面再进一步验证Set 接口中是不能有重复的内容的。

import java.util.HashSet;
import java.util.Set;
public class HashSetDemo04 {
    public static void main(String[] args) {
        Set<String> all = new HashSet<String>(); // 实例化Set接口对象
        all.add("A");
        all.add("A"); // 重复元素
        all.add("A"); // 重复元素
        all.add("A"); // 重复元素
        all.add("A"); // 重复元素
        all.add("B");
        all.add("C");
        all.add("D");
        all.add("E");
        System.out.println(all);
    }
}

​ 以上字符串“A”设置了很多次,因为Set 接口中是不能有任何的重复元素的,所以其最终结果只能有一个“A”。


TreeSet

特点

  • TreeSet 是 SortedSet 接口的实现类,TreeSet 可以确保集合元素处于排序状态。

  • TreeSet底层使用红黑树结构存储数据

  • 有序,查询速度比List快再具体就不说了,可以参看这篇博文对红黑树的讲解写得不错。

  • TreeSet 两种排序方法:自然排序定制排序。默认情况下,TreeSet 采用自然排序。

  • 向 TreeSet 中添加元素时,只有第一个元素无须比较compareTo()方法,后面添加的所有元素都会调用compareTo()方法进行比较。

  • 新增的方法如下: (了解)

    • Comparator comparator()

    • Object first()

    • Object last()

    • Object lower(Object e)

    • Object higher(Object e)

    • SortedSet subSet(fromElement, toElement)

    • SortedSet headSet(toElement)

    • SortedSet tailSet(fromElement)

示例

下面通过代码来观察其是如何进行排序的。

import java.util.Set;
import java.util.TreeSet;
public class TreeSetDemo01 {
    public static void main(String[] args) {
        Set<String> all = new TreeSet<String>(); // 实例化Set接口对象\
        all.add("D");
        all.add("X");
        all.add("A");
        System.out.println(all);
    }
}

​ 虽然在增加元素的时候属于无序的操作,但是增加之后却可以为用户进行排序功能的实现。

3.4.3 排序的说明(重点)

对于自定义类型Person

如果要进行排序的话,则必须在Person 类中实现Comparable 接口。

public class Person implements Comparable<Person> {
    private String name;
    private int age;
    public int compareTo(Person per) {
        if (this.age > per.age) {
            return 1;
        } else if (this.age < per.age) {
            return -1;
        } else {
           return 0;
        }
    } 
    
    public Person() {} 
    
    public Person(String name, int age) { 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;
    }
    public String toString() {
        return "姓名:" + this.name + ",年龄:" + this.age;
    }
}
import java.util.Set;
import java.util.TreeSet;
public class TreeSetPersonDemo01 {
public static void main(String[] args) {
        Set<Person> all = new TreeSet<Person>();
        all.add(new Person("张三", 10));
        all.add(new Person("李四", 10));
        all.add(new Person("王五", 11));
        all.add(new Person("赵六", 12));
        all.add(new Person("孙七", 13));
        System.out.println(all);
    }
}

程序的执行结果如下:

[姓名:张三,年龄:10, 姓名:王五,年龄:11, 姓名:赵六,年龄:12, 姓名:孙七,年龄:13]

​ 从以上的结果中可以发现,李四没有了。因为李四的年龄和张三的年龄是一样的,所以会被认为是同一个对象。则此时必须修改Person 类,如果假设年龄相等的话,按字符串进行排序。

public int compareTo(Person per) {
    if (this.age > per.age) {
        return 1;
    } else if (this.age < per.age) {
        return -1;
    } else {
        return this.name.compareTo(per.name);
    }
}

​ 此时,可以发现李四出现了,如果加入了同一个人的信息的话,则会认为是重复元素,所以无法继续加入。

关于重复元素的说明(重点)

​ 之前使用Comparable 完成的对于重复元素的判断,那么Set 接口定义的时候本身就是不允许重复元素的,那么证明如果现在真的是有重复元素的话,使用HashSet 也同样可以进行区分。

import java.util.HashSet;
import java.util.Set;
public class HashSetPersonDemo01 {
    public static void main(String[] args) {
        Set<Person> all = new HashSet<Person>();
        all.add(new Person("张三", 10));
        all.add(new Person("李四", 10));
        all.add(new Person("李四", 10));
        all.add(new Person("王五", 11));
        all.add(new Person("赵六", 12));
        all.add(new Person("孙七", 13));
        System.out.println(all);
    }
}

​ 此时发现,并没有去掉所谓的重复元素,也就是说之前的操作并不是真正的重复元素的判断,而是通过Comparable接口间接完成的(返回值为0时告诉了程序是相同元素)。

​ 如果要想判断两个对象是否相等,则必须使用Object 类中的equals()方法。

​ 从最正规的来讲,如果要想判断两个对象是否相等,则有两种方法可以完成:

  • 第一种判断两个对象的编码是否一致,这个方法需要通过hashCode()完成,即:每个对象有唯一的编码
  • 还需要进一步验证对象中的每个属性是否相等,需要通过equals()完成。
public boolean equals(Object obj) {
    if (this == obj) {
        return true;
    } if (!(obj instanceof Person)) {
        return false;
    }
    Person per = (Person) obj;
    if (per.name.equals(this.name) && per.age == this.age) {
        return true;
    } else {
        return false;
    }
} 

public int hashCode() {
    return this.name.hashCode() * this.age;
}

​ 发现,此时已经不存在重复元素了,所以如果要想去掉重复元素需要依靠hashCode()和equals()方法共同完成。

小结:

​ 关于TreeSet 的排序实现,如果是集合中对象是自定义的或者说其他系统定义的类没有实现

Comparable 接口,则不能实现TreeSet 的排序,会报类型转换(转向Comparable 接口)错误。

换句话说要添加到TreeSet 集合中的对象的类型必须实现了Comparable 接口。

​ 不过TreeSet 的集合因为借用了Comparable 接口,同时可以去除重复值,而HashSet 虽然是

Set 接口子类,但是对于没有复写Object 的equals 和hashCode 方法的对象,加入了HashSet

集合中也是不能去掉重复值的。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
sortSethashSettreeSet都是Java中的集合框架,用于存储一组元素。它们各自具有不同的特点和适用场景。 - HashSet:是基于哈希表实现的,可以快速查找元素。HashSet中的元素是无序的,不允许重复元素。因此,当需要快速查找元素且不关心元素顺序时,可以选择HashSet。 - TreeSet:是基于红黑树实现的,可以自动排序。TreeSet中的元素是有序的,不允许重复元素。因此,当需要自动排序且不允许重复元素时,可以选择TreeSet。 - SortedSet:是一个接口,继承自Set接口,它可以自动排序。SortedSet中的元素是有序的,不允许重复元素。SortedSet中有两个重要的实现,分别是TreeSet和ConcurrentSkipListSet。当需要自动排序且不关心线程安全时,可以选择TreeSet;当需要自动排序且需要线程安全时,可以选择ConcurrentSkipListSet。 - LinkedHashSet:是基于哈希表和双向链表实现的,可以保持元素插入的顺序。LinkedHashSet中的元素是有序的,不允许重复元素。因此,当需要保持元素插入顺序且不允许重复元素时,可以选择LinkedHashSet。 总的来说,选择哪种集合框架,需要根据具体的需求来决定。如果需要快速查找元素且不关心元素顺序,可以选择HashSet;如果需要自动排序且不允许重复元素,可以选择TreeSet;如果需要保持元素插入顺序且不允许重复元素,可以选择LinkedHashSet。SortedSet可以根据需要选择具体实现

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值