前言
大家好啊,我是汤圆,明天给大家带来的是《Java中的汇合Set - 入门篇》,心愿对大家有帮忙,谢谢
简介
后面介绍了汇合List,映射Map,最初再简略介绍下汇合Set
,相干类如下图所示
注释
Set从里面看像List(都是存储繁多数据的汇合),只不过存储的数据不会有反复;
然而外面却是Map映射(因为它内存存储是基于Map构造实现),这也是为什么把Set放到Map前面来说的起因。
Set和Map有什么关系呢?
因为Map的键不会有反复,所以Set就利用了Map的这个特点,将其作为外部成员变量来应用
比方咱们看下HashSet外部的源码,能够看到,基本上所有操作都是基于其外部的成员变量HashMap进行的
private transient HashMap<E,Object> map; // 这个常量只是为了适配Map的键值对构造,无实际意义 private static final Object PRESENT = new Object(); public HashSet() { map = new HashMap<>(); } public int size() { return map.size(); } public boolean contains(Object o) { return map.containsKey(o); } public boolean add(E e) { return map.put(e, PRESENT)==null; } public boolean remove(Object o) { return map.remove(o)==PRESENT; }
Set的品种
Set的品种相似Map
Set次要有三种类型:HashSet(罕用)、TreeSet(树形构造)、LinkedHashSet(前两者的联合)
咱们先来看一下Set接口次要的几个办法:
boolean add(E e)
:往Set中增加元素boolean contains(Object o)
:查问Set是否蕴含指定对象boolean remove(Object o)
:从Set中删除指定对象int size()
:返回Set的元素数量
上面咱们简略看下三者的区别
HashSet | TreeSet | LinkedHashSet | |
---|---|---|---|
访问速度 | 快 | 慢 | 适中 |
元素是否有序 | 无序 | 有序,默认升序 | 有序,默认按插入的程序 |
实用场景 | 为疾速查问而设计(用的最多) | 须要排序的场景 | 须要保障查问和插入程序统一的场景 |
接下来咱们以HashSet为例,来介绍Set接口
HashSet
HashSet是一个无序汇合
因为它外部是基于HashMap实现
下面的源码咱们有看到,HashSet每插入一个元素,就将该元素作为外部hashMap的key,而后常量Object作为hashMap的value,存储到hashMap中
- 如果元素的hash值没有反复,就依照数组的形式顺次排列;
- 如果hash值有反复的,就增加到已有的值对前面,造成链表构造;
整体构造 如下图所示
上面用代码示范一下
public class SetDemo { public static void main(String[] args) { // 初始化 Set<Integer> set = new HashSet<>(); // 增加元素 set.add(10); // 元素数量 int size = set.size(); System.out.println(size); // 查问元素是否存在 boolean isContain = set.contains(10); System.out.println(set); // 删除 set.remove(10); System.out.println(set); }}
TreeSet
TreeSet在插入的时候,能够依照元素进行排序(默认升序)
它适宜用在排序比拟多的场景,性能会比HashSet差一些
上面用代码示范一下(重点要来了)
public class SetDemo { public static void main(String[] args) { // TreeSet B b = new B(); // 初始化 Set<B> set2 = new TreeSet<>(); // 增加元素 set2.add(b); // 元素数量 int size2 = set2.size(); System.out.println(size2); // 查问元素是否存在 boolean isContain2 = set2.contains(b); System.out.println(set2); // 删除 set2.remove(b); System.out.println(set2); }}class B{}
这段代码看似一切正常,实则暗藏玄机
如果你运行这段代码,会报出上面的谬误提醒:类B不能转换为Comparable。
可是为什么要转换呢?我也没有转换啊
那是因为外部主动转换了
TreeSet啥时候会主动将元素类转为Comparable呢?
是在你插入第一个数据开始,外部就曾经开始在做比拟了(第一次先本人跟本人做比拟,目标就是查看你这个数据有没有实现Comparable接口);
前面每插一个数据,都要从根节点开始挨个比拟排序
这其实也算也是TreeSet外部排序的工作原理
所以下面这段代码须要让B实现Comparable接口,改后如下所示
public class SetDemo { public static void main(String[] args) { // TreeSet B b = new B(); // 初始化 Set<B> set2 = new TreeSet<>(); // 增加元素 // 此时运行没问题 set2.add(b); // 元素数量 int size2 = set2.size(); System.out.println(size2); // 查问元素是否存在 boolean isContain2 = set2.contains(b); System.out.println(set2); // 删除 set2.remove(b); System.out.println(set2); }}// 这里实现了Comparableclass B implements Comparable{ @Override public int compareTo(Object o) { return this.hashCode()>o.hashCode() ? 1 : -1; }}
此时运行就没问题了
那为什么TreeMap没有这个问题呢?
其实TreeMap也有这个问题,只是TreeMap的键key个别都是字符串或者整型,而字符串和整型对象外部曾经实现了Comparable接口,所以问题不大(因为用自定义对象做键key的毕竟还是多数)
LinkedHashSet
LinkedHashSet领有HashSet的大部分长处,且保障了插入的程序,使得在查问的时候,能够依照插入的程序顺次读取(原理是链表)
这里要留神一点:在Java程序语言设计中,所有的链表都是双向链表,即每个节点还寄存着一个指向前节点的援用
大抵的结构图如下所示(之前的LinkedHashMap遗记贴图了,跟这个相似,只是元素内容不同):
三者的排序比拟
参考上一篇映射Map,Set排序比拟体现进去的行为与Map统一
总结
Set个别用到的有HashSet,TreeSet,LinkedHashSet,外部都是无反复元素
- HashSet的插入和拜访都很快,然而外部是无序排列
- TreeSet的插入和拜访都很慢,然而外部是有序排列,默认按升序排列
- LinkedHashSet领有HashSet的大部分长处,而且还能够依照元素插入的程序来拜访元素,然而性能会比HashSet差
后记
最初,感激大家的观看,谢谢