Java集合之—Set

前言

上一篇讲完了List,那么这一篇就不得不说说它的好朋友—Set了,Set和List的最大区别就是Set中的元素是不可以重复的,虽然Set 接口并没有对 Collection 接口进行扩充,基本上还是与 Collection 接口保持一致。

此接口没有 List 接口中定义的 get(int index)方法,所以无法使用循环进行输出。所以只能使用toArray()方法或者迭代器进行循环遍历输出。

但是该接口中有着两个集合中不可或缺的子类:HashSet、TreeSet,本文将重点介绍HashSet以及TreeSet

HashSet

HashSet属于散列的存放类集,里面的东内容是无序存放的。通过源码可以发现,Has和Set在构造的时候实际上是创建了一个HashMap,即HashSet的底层数据结构是一个HashMap,如果对HashMap不太理解的朋友,可以看我的这篇博客Java中的Map接口

老规矩,先看继承树图:

image-20220415095920170

由于HashSet继承了Set,而set在继承Collection接口后并没有怎么进行方法的扩展,所以set的常用方法和Collection接口一样,大家可参考我的这篇博客Java集合之—Collection下面来简单叙述一下HashSet的几个特点

1、HashSet存储的元素是无序且不可重复
2、可以存储null值
3、是线程不安全的,即如果多个线程同时操作同一个HashSet,那么结果是不可知的

HashSet内部存储机制

HashSet底层使用HashMap来进行存储,当使用HashSet进行存储元素的时候,底层是使用了map类的put方法,实际上就是将要存储的元素存储到了HashMap的key部分,其value部分是使用了一个默认值(new Object() )来存储

image-20220415100703192

注意,当向HashSet集合中存入一个元素时,HashSet会调用该对象的hashCode方法来得到该对象的哈希值,然后根据该哈希值决定该对象在HashSet中的存储位置。

但是,如果有两个元素通过equals方法比较返回值为true,可它们的hashCode方法返回的值不相等,HashSet将会把它们存储在不同位置,依然可以添加成功。

即HashSet集合判断两个元素是否相同的标准是两个对象通过equals方法比较相等,且哈希值也相等

所以,在使用HashSet存储自定义类型数据的时候,一定要尽量确保两个对象通过equals方法比较的结果和Hashcode()的方法返回值一致,即equals比较值为true,其哈希值也应该相等,这样可以更好的保证数据的唯一性,下面举个例子来帮助进一步的认知

例子
package com.blog.set;

import java.util.HashSet;

/**
 * @Author jinhuan
 * @Date 2022/4/15 9:56
 * Description:
 * 未对Book类重写equals以及hashcode方法,检测hashSet是否可以存重复值
 */
public class Test01 {
    public static void main(String[] args) {
        //实例化Book对象
        Book book01 = new Book(1, "方与圆");
        Book book02 = new Book(2, "傲慢与偏见");
        Book book03 = new Book(1, "方与圆");

        //创建HashSet集合
        HashSet<Book> books = new HashSet<>();

        //添加元素,注意,book01与book03是同一本书
        books.add(book01);
        books.add(book02);
        books.add(book03);

        //遍历该集合
        System.out.println("未重写equals方法以及hashcode方法:");
        for (Book book : books) {
            System.out.println(book);
        }
        
    }
}
class Book{

    /**
     * id
     */
    private Integer id;
    /**
     * name
     */
    private String name;

    public Book(Integer id, String name) {
        this.id = id;
        this.name = name;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "书籍信息{" +
                "书号=" + id +
                ", 书名='" + name + '\'' +
                '}';
    }
}

运行截图

image-20220415102529842

package com.blog.set;

import java.util.HashSet;
import java.util.Objects;

/**
 * @Author jinhuan
 * @Date 2022/4/15 9:56
 * Description:
 * 未对Book类重写equals以及hashcode方法,检测hashSet是否可以存重复值
 */
public class Test01 {
    public static void main(String[] args) {
        //实例化Book对象
        Book book01 = new Book(1, "方与圆");
        Book book02 = new Book(2, "傲慢与偏见");
        Book book03 = new Book(1, "方与圆");

        //创建HashSet集合
        HashSet<Book> books = new HashSet<>();

        //添加元素,注意,book01与book03是同一本书
        books.add(book01);
        books.add(book02);
        books.add(book03);

        //遍历该集合
        System.out.println("已经重写equals方法以及hashcode方法:");
        for (Book book : books) {
            System.out.println(book);
        }

    }
}
class Book{

    /**
     * id
     */
    private Integer id;
    /**
     * name
     */
    private String name;

    public Book(Integer id, String name) {
        this.id = id;
        this.name = name;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "书籍信息{" +
                "书号=" + id +
                ", 书名='" + name + '\'' +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o){
            return true;
        }
        if (!(o instanceof Book)) {
            return false;
        }
        Book book = (Book) o;
        return Objects.equals(getId(), book.getId()) &&
                Objects.equals(getName(), book.getName());
    }

    @Override
    public int hashCode() {
        return Objects.hash(getId(), getName());
    }
}

运行截图

image-20220415102656939

可以看出,重写了equals方法以及hashcode方法之后,就不会出现Set中存储‘“相同”元素的情况了,但实际上,并不是真正的相同哈,因为hashcode的返回值不同,所以并不是真正意义上的完全相容

众所周知,集合类就是对于数组的一个扩展,是为了解决数组不能解决或者不便解决的问题,关于hashset的存储原理,也就是hashmap的存储原理,还请大家参考我的这篇博客Java集合之—Map,里面对于存储原理做了进步一的讲解叙述,这里只对hash表做简单叙述

hash表

初始化容量:创建hash表时桶的数量;

尺寸:当前hash表中记录的数量;

容量:hash表中桶的数量;

散列因子:负载因子范围0~1,0表示空的hash表,0.5表示半满的hash表,1表示满(几乎不存在)。小对应着查询效率高,存储容量低;大对应着存储容量高,查询效率低。

负载极限:负载极限是一个0~1之间的数值,决定了hash表的最大填满程度。当hash表的负载因子达到指定负载极限时,hash表会自动成倍地增加容量,并将原有的对象重新分配,放入新的桶中。HashSet、HashMap、Hashtable默认的负载极限都是0.75,这个是官方测试后给出的最佳大小。

注意

当向HashSet中添加自定义对象时,必须十分小心!!!

如果修改HashSet集合中的对象,有可能导致该对象与集合中的其他对象相等,此时其哈希值发生变化,从而导致HashSet无法准确访问该对象。更为详细的介绍,还请大家移步上文,查看我的另一篇文章——Map。

TreeSet

说完了HashSet,下面就来谈谈TreeSet,继承树如下:

image-20220415115013648

Treeset类实现了SortedSet接口,即TreeSet中的元素是有序的,和HashSet雷同,TreeSet底层是一个TreeMap,其内部实现的是红黑树。也因为其排序的特性,TreeSet有几个额外的方法提供使用,如下:

image-20220415115305723

方法:简述:
Comparator comparator()如果TreeSet采用了定制顺序,则该方法返回定制排序所使用的 Comparator,如果TreeSet采用自然排序,则返回null
Object first()返回集合中的第一个元素
Object last()返回集合中的最后一个元素
Object lower(Object e)返回指定元素之前的元素
Object higher(Object e)返回指定元素之后的元素
SortedSet subSet(Object fromElement,Object toElement)返回此Set的子集合,不包含
SortedSet headSet(Object toElement返回此Set的子集,由小于toElement的元素组成
SortedSet tailSet(Object fromElement返回此Set的子集,由大于fromElement的元素组成

到此就不得不说一下TreeSet支持的排序方法:默认排序以及自定义排序

默认排序:

又叫做自然排序,是根据调用集合元素的compare To(Object object)方法来比较该集合中不同元素之间的大小关系,并默认采用升序排列。

关于Compare To(Object object),该方法源于Comarable接口,返回值为整数型,例如:obj1.compareTo(obj2)
如果返回0表示两个对象相等;如果返回正整数则表明obj1大于obj2;如果是负整数则表明obj1小于obj2。

自定义排序:

需要实现定制排序的时候,例如降序排序(TreeSet的自然排序是升序排列),可通过Comparator接口来实现该需求。该接口里包含一个int compare(T o1,T o2)方法,用于比较o1和o2的大小。见实例:

实例
package com.blog.set;

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

/**
 * @Author jinhuan
 * @Date 2022/4/15 12:46
 * Description:
 */
public class TestComparator {
    public static void main(String[] args) {
        //实现降序输出TreeSet中的元素
        TreeSet treeSet = new TreeSet<Integer>(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                //修改为逆序
                return o2-o1;
            }
        });

        //添加元素
        treeSet.add(1);
        treeSet.add(2);
        treeSet.add(3);
        treeSet.add(4);
        treeSet.add(5);

        //输出
        System.out.println(treeSet);
    }
}

image-20220415125241054

注意事项:

1、TreeSet是排序类集合,无法正常排序则会出现异常,所以,所有集合中的元素都必须实现compatable接口,注意,自定义类必须实现该接口,否则会出现ClassCastException

2、使用TreeSet建议加上泛型,以保证集合中的元素一致

3、和HashSet一样,已经存入集合的实例变量,不要进行二次修改,这将导致它与其他对象的大小顺序发生改变,但TreeSet集合不会再次调整它们的顺序

4、不可以添加相同对象,自定义类必须重写equals以及hashcode

5、比较是comparable不是equals,所以不能代替equals来判断是否为同一个元素

以上均为本人个人观点,借此分享,希望能和大家一起进步。如有不慎之处,劳请各位批评指正!鄙人将不胜感激并在第一时间进行修改!

image-20220327095755218

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

尽欢Sir

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值