Java集合(四)Set的常用实现类

本文围绕Java集合中Set的常用实现类展开。介绍了Set元素无序、不可重复的特点,常用实现有HashSet、LinkedHashSet和TreeSet。详细阐述了各实现类的底层结构、特点及使用方法,如HashSet底层是HashMap,元素无序;LinkedHashSet底层是LinkedHashMap,元素有序;TreeSet底层是TreeMap,支持排序。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  本系列文章:
    Java集合(一)集合框架概述
    Java集合(二)List的常用实现类
    Java集合(三)Map的常用实现类
    Java集合(四)Set的常用实现类
    Java集合(五)BlockingQueue的常用实现类

一、Set

  List是有序集合的根接口,Set是无序集合的根接口,无序也就意味着元素不重复。更严格地说,Set集合不包含一对元素e1和e2 ,使得e1.equals(e2) ,并且最多一个空元素。
  使用Set存储的特点与List相反:元素无序、不可重复。常用的实现方式:HashSet、LinkedHashSet和TreeSet。

具体实现优点缺点
HashSet底层数据结构是哈希表,可以存储null元素,效率高线程不安全,需要重写hashCode()和equals()来保证元素唯一性
LinkedHashSet底层数据结构是链表和哈希表(链表保证了元素的顺序与存储顺序一致,哈希表保证了元素的唯一性),效率高线程不安全
TreeSet底层数据结构是二叉树,元素唯一且已经排好序需要重写hashCode和equals()来保证元素唯一性

  当向HashSet结合中存入一个元素时,HashSet会调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据hashCode值来决定该对象在HashSet中存储位置。简单的说,HashSet集合判断两个元素相等的标准是两个对象通过equals方法比较相等,并且两个对象的hashCode()方法返回值也相等
  LinkedHashSet集合同样是根据元素的hashCode值来决定元素的存储位置,但是它同时使用链表维护元素的次序。这样使得元素看起来像是以插入顺序保存的,也就是说,当遍历该集合时候,LinkedHashSet将会以元素的添加顺序访问集合的元素。

  在使用Set存储数据时,为保障元素唯一性,常常要重写hashCode。重写hashCode方法时,尽量遵循以下原则:

  1. 相同的对象返回相同的hashCode值。
  2. 不同的对象返回不同的hashCode值,否则,就会增加冲突的概率。
  3. 尽量的让hashCode值散列开(用异或运算可使结果的范围更广)。
  • HashSet
      HashSet中没有重复元素,底层由HashMap实现,不保证元素的顺序(此处的没有顺序是指:元素插入的顺序与输出的顺序不一致),HashSet允许使用Null元素,HashSet是非同步的。
  • LinkedHashSet
      LinkedHashSet继承自HashSet,其底层是基于LinkedHashMap来实现的,有序,非同步。
      LinkedHashSet在迭代访问Set中的全部元素时,性能比HashSet好,但是插入时性能稍微逊色于HashSet。
  • TreeSet
      TreeSet底层是基于TreeMap实现的,所以元素有序。TreeSet支持两种排序方式,自然排序和定制排序,其中自然排序为默认的排序方式。当我们构造TreeSet时,若使用不带参数的构造函数,则TreeSet的使用自然比较器;若用户需要使用自定义的比较器,则需要使用带比较器的参数。
      TreeSet是线程不安全的。
      自然排序要求元素必须实现Compareable接口,并重写里面的compareTo()方法,元素通过比较返回的int值来判断排序序列,返回0说明两个对象相同。
      比较器排序需要在TreeSet初始化是时候传入一个实现Comparator接口的比较器对象,或者采用匿名内部类的方式new一个Comparator对象,重写里面的compare()方法。
      TreeSet判断两个对象不相等的方式是两个对象通过equals方法返回false,或者通过CompareTo方法比较没有返回0。
      在使用Set存储元素时,元素虽然无放入顺序,但Set的底层实现其实是Map,元素在Set中的位置是有该元素的HashCode决定的,所以其位置其实是固定的。
      TreeSet是SortedSet接口的唯一实现类,TreeSet可以确保集合元素处于排序状态。TreeSet支持两种排序方式,自然排序和定制排序,其中自然排序为默认的排序方式。

  至于具体使用哪种集合时,参考:

  在List和Set两个分支中,ArrayList和HashSet是对应分支中适应性最广的,两者再比较,ArrayList则适用性更广一些。也就是说如果要确定用List,但不确定用哪种List,就可以使用ArrayList;如果确定用Set,但不确定用哪种Set,就可以使用HashSet。如果只知道用集合,就用ArrayList。

二、HashSet

2.1 HashSet介绍

  HashSet是一个无序集合,其底层结构是HashMap,简单来说,HashSet是value是固定值(Object PRESENT = new Object())的HashMap。

2.1.1 HashSet的特点(底层是HashMap/元素无序且不能重复/线程不安全)

  • 1、HashSet的底层实现是HashMap(HashSet的值存放于HashMap的key上,HashMap的value是一个统一的值)。
  • 2、HashSet中的元素无序且不能重复(从插入HashSet元素的顺序和遍历HashSet的顺序对比可以看出:遍历顺序和存入到Set的顺序并不一致)。
  • 3、HashSet是线程不安全的。如果要保证线程安全,其中一种方法是将其改造成线程安全的类,示例:
	Set set = Collections.synchronizedSet(new HashSet(...));
  • 4、HashSet允许存入null

  • HashSet如何检查重复
      当你把对象加入HashSet时,HashSet会先计算对象的hashcode值来判断对象加入的位置,同时也会与Set中其他元素的hashcode值作比较,如果没有相同的hashcode,HashSet会假设对象没有重复出现。
      如果发现有相同hashcode值的对象,这时会调用equals方法来检查hashcode相等的对象是否真的相同。如果两者相同,HashSet就不再存储该元素。
      hashCode()与 equals()的相关规定:

  1)如果两个对象相等,则hashcode一定也是相同的;
  2)两个对象相等,对两个equals方法返回true;
  3)两个对象有相同的hashcode值,它们也不一定是相等的;
  4)如果equals方法被覆盖过,则hashCode方法也必须被覆盖;
  5)hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该 class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)。

2.1.2 HashSet的使用

  • 1、构造方法
	//默认初始容量为16,负载因子为0.75
	public HashSet()
	//指定初始容量,负载因子为0.75
	public HashSet(int initialCapacity)
	//指定初始容量和负载因子
	public HashSet(int initialCapacity, float loadFactor)
  • 2、添加元素
	public boolean add(E e)
  • 3、删除元素
	public boolean remove(Object o)
  • 4、判断是否包含某元素
	public boolean contains(Object o)
  • 5、其他方法
	//清空集合
	public void clear()
	//判断Set是否为空
	public boolean isEmpty()	
	//获取迭代器
	public Iterator<E> iterator()	
	//返回此集合中的元素数
	public int size()	

三、LinkedHashSet

3.1 LinkedHashSet介绍

  LinkedHashSet是有序集合,其底层是通过LinkedHashMap来实现的,LinkedHashMap其实也就是value是固定值的LinkedHashMap。因此LinkedHashSet中的元素顺序是可以保证的,也就是说遍历序和插入序是一致的。
  LinkedHashSet继承了HashSet。

3.1.1 LinkedHashSet的特点(底层是LinkedHashMap/线程不安全/元素有序)

  1)底层是用LinkedHashMap来实现的。
  2)线程不安全 。
  3)元素有序,是按照插入的顺序排序的。
  4)最多只能存一个null。

  • LinkedHashSet支持按元素访问顺序排序吗
      LinkedHashSet所有的构造方法都是调用HashSet的同一个构造方法:
    HashSet(int initialCapacity, float loadFactor, boolean dummy) {
        map = new LinkedHashMap<>(initialCapacity, loadFactor);
    }

    public LinkedHashMap(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor);
        accessOrder = false;
    }

  可以看出:这里把accessOrder写死为false。所以,LinkedHashSet是不支持按访问顺序对元素排序的,只能按插入顺序排序

四、TreeSet

4.1 TreeSet介绍

  TreeSet是一个有序集合,基于TreeMap实现。

4.1.1 TreeSet特点(支持元素排序/线程不安全/)

  • 1、TreeSet支持元素的自然排序和按照在创建时指定的Comparator比较器(外比较器)进行排序
      TreeSet是使用二叉树的原理对新增的对象按照指定的顺序排序,每增加一个对象都会进行排序,将对象插入的二叉树指定的位置。
      TreeSet中要存储自定义类的对象时, 自己定义的类必须实现Comparable接口,并且覆写相应的compareTo()函数,才可以正常使用。
      Integer和String对象都可以进行默认的TreeSet排序,而自定义类的对象是不可以的。自己定义的类必须实现Comparable接口,并且覆写相应的compareTo()函数,才可以正常使用。
      在重写compare()函数时,要返回相应的值才能使 TreeSet 按照一定的规则来排序。比较此对象与指定对象的顺序。如果该对象小于、等于或大于指定对象,则分别返回负整数、零或正整数。示例:
@Data
public class student  implements Comparable<student>{
    private String name;
    private int age;

    @Override
    public int compareTo(student s) {
        // 主要条件:按照年龄从小到大
        int num = this.age - s.age;
        //次要条件:年龄相同时,按照姓名的字母顺序排序
        int num2 = num == 0 ? this.name.compareTo(s.name) : num;
        return num2;
    }
}

public class Comparabledemo {
    public static void main(String[] args) {
        TreeSet<student> tree = new TreeSet<student>();
        student s1 = new student("wuer",27);
        student s2 = new student("weuers",250);
        tree.add(s1);
        tree.add(s2);
    }   
}
  • 2、TreeSet的基本操作(增删)的时间复杂度是log(n)
  • 3、TreeSet是非线程安全的
  • 4、TreeSet的迭代器是fail-fast策略的
  • 5、TreeSet中元素不允许为null,不允许重复值

4.1.2 TreeSet的使用

  • 1、构造方法
	//创建一个空的 TreeSet,使用自然排序
	public TreeSet()
	//指定比较器,如果比较器是 null 将使用自然排序
	public TreeSet(Comparator<? super E> comparator)
  • 2、添加元素
	//添加一个元素
	public boolean add(E e)
	//添加集合中的元素
	public  boolean addAll(Collection<? extends E> c)
  • 3、删除元素
    public boolean remove(Object o)
  • 4、判断是否包含某元素
	public boolean contains(Object o)
  • 5、获取特殊元素
	//返回此TreeSet中存在的最大元素
    public E last() 
	//返回此TreeSet中存在的最小元素
    public E first()
	//返回在这个集合中小于或者等于给定元素的最大元素
    public E floor(E e)
	//返回在这个集合中大于或者等于给定元素的最小元素
    public E ceiling(E e)
   	//返回此集合中大于某个元素的最小的元素
    public E higher(E e) 
	//返回此集合中小于某个元素的最大的元素
    public E lower(E e)    
  • 6、删除
	//检索和删除最小(第一个)元素
    public E pollFirst()
	//检索和删除最大(最后)元素
    public E pollLast()
  • 7、其他方法
	//获取TreeSet元素个数
    public int size()
	//判断TreeSet是否为空
    public boolean isEmpty()
	//清空TreeSet
    public void clear()
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值