【Java入门提高篇】Day29 Java容器类详解(十一)LinkedHashSet详解
当当当当当当当,本来打算出去浪来着,想想还是把这个先一起写完吧,毕竟这篇的主角跟我一样是一个超级偷懒的角色——LinkedHashSet,有多偷懒?看完你就知道了。
本篇将从以下几个方面对LinkedHashSet进行介绍:
1、LinkedHashSet中的特性
2、LinkedHashSet源码分析
3、LinkedHashSet应用场景
本篇预计需要食用10分钟,快的话五分钟也够了,完全取决于各位看官心情。
LinkedHashSet中的特性
前面已经介绍过了HashSet,本篇要介绍的LinkedHashSet正是它的儿子,作为HashSet的唯一法定继承人,可以说是继承了HashSet的全部优点——懒,并且将其发挥到了极致,这一点在之后的源码分析里可以看到。
LinkedHashSet继承了HashSet的全部特性,元素不重复,快速查找,快速插入,并且新增了一个重要特性,那就是有序,可以保持元素的插入顺序,所以可以应用在对元素顺序有要求的场景中。
先来看一个小栗子:
- public class LinkedHashSetTest {
- public static void main(String[] args){
- LinkedHashSet<String> linkedHashSet = new LinkedHashSet<>();
- HashSet<String> hashSet = new HashSet<>();
- for (int i = 0; i < 10; i++) {
- linkedHashSet.add("I" + i);
- hashSet.add("I" + i);
- }
- System.out.println("linkedHashSet遍历:");
- for (String string : linkedHashSet){
- System.out.print(string + " ");
- }
- System.out.println();
- System.out.println("hashSet遍历:");
- for (String string : hashSet){
- System.out.print(string + " ");
- }
- }
- }
- linkedHashSet遍历:
- I0 I1 I2 I3 I4 I5 I6 I7 I8 I9
- hashSet遍历:
- I9 I0 I1 I2 I3 I4 I5 I6 I7 I8
可以看到,在HashSet中存储的元素遍历是无序的,而在LinkedHashSet中存储的元素遍历是有序的。嗯,它和HashSet就这唯一的区别了。
LinkedHashSet源码分析
那么问题来了,LinkedHashSet中的元素为什么会是有序的呢?难道也跟LinkedHashMap一样用了链表把元素都拴起来了?别着急,让我们一起来看看源码。
- public class LinkedHashSet<E>
- extends HashSet<E>
- implements Set<E>, Cloneable, java.io.Serializable {
- private static final long serialVersionUID = -2851667679971038690L;
- /**
- * 使用指定初始容量和装载因子构造一个空的LinkedHashSet实例
- */
- public LinkedHashSet(int initialCapacity, float loadFactor) {
- super(initialCapacity, loadFactor, true);
- }
- /**
- * 使用指定的初始容量和默认的装载因子构造一个空的LinkedHashSet实例
- */
- public LinkedHashSet(int initialCapacity) {
- super(initialCapacity, .75f, true);
- }
- /**
- * 使用默认的初始容量和默认的装载因子构造一个空的LinkedHashSet实例
- */
- public LinkedHashSet() {
- super(16, .75f, true);
- }
- /**
- * 构造一个与指定集合有相同元素的空LinkedHashSet实例,使用默认的装载因子和能够容纳下指定集合所有元素的合适的容量。
- */
- public LinkedHashSet(Collection<? extends E> c) {
- super(Math.max(2*c.size(), 11), .75f, true);
- addAll(c);
- }
- /**
- * 可分割式迭代器
- */
- @Override
- public Spliterator<E> spliterator() {
- return Spliterators.spliterator(this, Spliterator.DISTINCT | Spliterator.ORDERED);
- }
- }
你没看错,这应该是所有容器类中最短小精悍的了,这也就是开头为什么说这家伙懒到家的原因了。
可是,LinkedHashSet中并没有覆盖add方法,只是加了几个构造函数和一个迭代器,其他全部和HashSet一毛一样,为什么它就能有序呢??
玄机就藏在这个构造函数中,这几个构造函数其实都是调用了它父类(HashSet)的一个构造函数:
- HashSet(int initialCapacity, float loadFactor, boolean dummy) {
- map = new LinkedHashMap<>(initialCapacity, loadFactor);
- }
嗯,这个构造函数跟其他构造函数唯一的区别就在于,它创建的是一个LinkedHashMap对象,所以元素之所以有序,完全是LinkedHashMap的功劳。该构造函数是默认访问权限的,所以在HashSet中是不能直接调用的,留给子类去调用或覆盖(讲道理使用protected权限不是更合理吗)。
LinkedHashSet应用场景
现在假设这样的场景,现在我有一堆商品,商品有名称和价格,但是里面有重复商品,我希望把重复的商品(名称和价格都一样的)过滤掉,只保留一个,并且希望输出后的顺序跟原来的顺序一致。嗯,这时候LinkedHashSet就派上用场了。(废话,那是你特意给主角加的戏)
商品的结构是这样的:
- public class Commodity {
- private String name;
- private Double price;
- public Commodity(String name, Double price) {
- this.name = name;
- this.price = price;
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public Double getPrice() {
- return price;
- }
- public void setPrice(Double price) {
- this.price = price;
- }
- @Override
- public String toString() {
- return "Commodity{" +
- "name='" + name + '\'' +
- ", price=" + price +
- '}';
- }
- }
来用LinkedHashSet解决一下这个需求:
- public class CommodityTest {
- public static void main(String[] args){
- //有7个商品,A和E、D和G信息完全一样,希望能过滤掉,只保留一个,C和F虽然名称一样,但是价格不同,希望保留
- Commodity commodityA = new Commodity("Iphone6S", 6666.66);
- Commodity commodityB = new Commodity("Iphone7", 7777.77);
- Commodity commodityC = new Commodity("Iphone8", 8888.88);
- Commodity commodityD = new Commodity("IphoneX", 9999.99);
- Commodity commodityE = new Commodity("Iphone6S", 6666.66);
- Commodity commodityF = new Commodity("Iphone8", 6666.66);
- Commodity commodityG = new Commodity("IphoneX", 9999.99);
- LinkedHashSet<Commodity> commodities = new LinkedHashSet<>();
- commodities.add(commodityA);
- commodities.add(commodityB);
- commodities.add(commodityC);
- commodities.add(commodityD);
- commodities.add(commodityE);
- commodities.add(commodityF);
- commodities.add(commodityG);
- for (Commodity commodity : commodities){
- System.out.println(commodity);
- }
- }
- }
输出如下:
- Commodity{name='Iphone6S', price=6666.66}
- Commodity{name='Iphone7', price=7777.77}
- Commodity{name='Iphone8', price=8888.88}
- Commodity{name='IphoneX', price=9999.99}
- Commodity{name='Iphone6S', price=6666.66}
- Commodity{name='Iphone8', price=6666.66}
- Commodity{name='IphoneX', price=9999.99}
翻...翻...翻车了?虽然输出的顺序与插入的顺序是一致的最后一个IphoneX和Iphone6S并没有被去掉,怎么回事呢?说好的可以去重呢?
嗯,别慌,我既然可以让车翻过来,那就有办法让它再翻回去。
想要利用LinkedHashSet自动去重性质,那么我们就要先理解它是怎样去重的,其实和HashSet是一样的,往里面添加元素的时候,其实是这样的:
- public boolean add(E e) {
- return map.put(e, PRESENT)==null;
- }
所以当该元素在map中存在的时候,map.put方法就会返回旧值,此时add方法会返回false,在查找map中put元素的时候,会先调用hashCode方法得到该元素的hashCode值,然后查看table中是否存在该hashCode值,如果存在则调用equals方法重新确定是否存在该元素,如果存在,则更新value值,否则将新的元素添加到HashMap中,如果没有覆盖过hashcode方法,那么就会使用对象默认的hashcode,这个值跟对象成员变量的具体值就没有直接关联了,所以我们需要覆盖hashcode方法和equals方法。
- public class Commodity {
- private String name;
- private Double price;
- public Commodity(String name, Double price) {
- this.name = name;
- this.price = price;
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public Double getPrice() {
- return price;
- }
- public void setPrice(Double price) {
- this.price = price;
- }
- @Override
- public String toString() {
- return "Commodity{" +
- "name='" + name + '\'' +
- ", price=" + price +
- '}';
- }
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- Commodity commodity = (Commodity) o;
- return Objects.equals(name, commodity.name) &&
- Objects.equals(price, commodity.price);
- }
- @Override
- public int hashCode() {
- return Objects.hash(name, price);
- }
- }
这里用的equals方法和hashCode方法是很通用的,在其他地方也可以使用类似的写法,现在再来重新跑一下程序看下:
- Commodity{name='Iphone6S', price=6666.66}
- Commodity{name='Iphone7', price=7777.77}
- Commodity{name='Iphone8', price=8888.88}
- Commodity{name='IphoneX', price=9999.99}
- Commodity{name='Iphone8', price=6666.66}
好的,现在已经达到我们想要的效果了。任务完成,午饭加个蛋。
【Java入门提高篇】Day29 Java容器类详解(十一)LinkedHashSet详解的更多相关文章
- 【Java入门提高篇】Java集合类详解(一)
今天来看看Java里的一个大家伙,那就是集合. 集合嘛,就跟它的名字那样,是一群人多势众的家伙,如果你学过高数,没错,就跟里面说的集合是一个概念,就是一堆对象的集合体.集合就是用来存放和管理其他类对象 ...
- 【Java入门提高篇】Day32 Java容器类详解(十四)ArrayDeque详解
今天来介绍一个不太常见也不太常用的类——ArrayDeque,这是一个很不错的容器类,如果对它还不了解的话,那么就好好看看这篇文章吧. 看完本篇,你将会了解到: 1.ArrayDeque是什么? 2. ...
- 【Java入门提高篇】Day28 Java容器类详解(十)LinkedHashMap详解
今天来介绍一下容器类中的另一个哈希表———>LinkedHashMap.这是HashMap的关门弟子,直接继承了HashMap的衣钵,所以拥有HashMap的全部特性,并青出于蓝而胜于蓝,有着一 ...
- 【Java入门提高篇】Day26 Java容器类详解(八)HashSet源码分析
前面花了好几篇的篇幅把HashMap里里外外说了个遍,大家可能对于源码分析篇已经讳莫如深了.别慌别慌,这一篇来说说集合框架里最偷懒的一个家伙——HashSet,为什么说它是最偷懒的呢,先留个悬念,看完 ...
- 【Java入门提高篇】Day21 Java容器类详解(四)ArrayList源码分析
今天要介绍的是List接口中最常用的实现类——ArrayList,本篇的源码分析基于JDK8,如果有不一致的地方,可先切换到JDK8后再进行操作. 本篇的内容主要包括这几块: 1.源码结构介绍 2.源 ...
- 【Java入门提高篇】Day20 Java容器类详解(三)List接口
今天要说的是Collection族长下的三名大将之一,List,Set,Queue中的List,它们都继承自Collection接口,所以Collection接口的所有操作,它们自然也是有的. Lis ...
- 【Java入门提高篇】Day31 Java容器类详解(十三)TreeSet详解
上一篇很水的介绍完了TreeMap,这一篇来看看更水的TreeSet. 本文将从以下几个角度进行展开: 1.TreeSet简介和使用栗子 2.TreeSet源码分析 本篇大约需食用10分钟,各位看官请 ...
- 【Java入门提高篇】Day27 Java容器类详解(九)LinkedList详解
这次介绍一下List接口的另一个践行者——LinkedList,这是一位集诸多技能于一身的List接口践行者,可谓十八般武艺,样样精通,栈.队列.双端队列.链表.双向链表都可以用它来模拟,话不多说,赶 ...
- 【Java入门提高篇】Day19 Java容器类详解(二)Map接口
上一篇里介绍了容器家族里的大族长——Collection接口,今天来看看容器家族里的二族长——Map接口. Map也是容器家族的一个大分支,但里面的元素都是以键值对(key-value)的形式存放的, ...
- 【Java入门提高篇】Day1 抽象类
基础部分内容差不多讲解完了,今天开始进入Java提高篇部分,这部分内容会比之前的内容复杂很多,希望大家做好心理准备,看不懂的部分可以多看两遍,仍不理解的部分那一定是我讲的不够生动,记得留言提醒我. 好 ...
随机推荐
- idea : shorten command line
[官方文档]:IntelliJ IDEA 2017.3 EAP: Configurable command line shortener and more 如果类路径太长,或者有许多VM参数,程序就无 ...
- DevOps - CI - 持续集成(Continuous Integration)
初见 持续集成是什么? 持续集成基础概念介绍 持续集成服务器与工具集 了解 敏捷开发中的持续集成 使用Jenkins进行持续集成 案例 gitlab+gerrit+jenkins持续集成框架 使用Ge ...
- Java运行环境(win10)
系统安装Java后,配置运行环境,我的系统是win10,之前随便装了,没想到最近执行javac命令报错,(网上找了一堆都没用)处理方式如下: 环境变量-新建:变量名:%JAVA_HOME% 变量值: ...
- oracle10g和oracle11g导入导出数据区别
其中flxuser为用户名,flxuser为密码,file值为导入到数据库中的备份文件. oracle10g和oracle11g导入导出数据的命令方式大有不同: oracle10g导入数据: imp ...
- JAVA获取运行环境的信息
System.getProperties().list(System.out); 得到运行环境的信息
- 我的Java秋招面经大合集(包含BAT头条网易等公司)
微信公众号[程序员江湖] 作者黄小斜,斜杠青年,某985硕士,阿里 Java 研发工程师,于 2018 年秋招拿到 BAT 头条.网易.滴滴等 8 个大厂 offer,目前致力于分享这几年的学习经 ...
- SSE图像算法优化系列五:超高速指数模糊算法的实现和优化(10000*10000在100ms左右实现)。
今天我们来花点时间再次谈谈一个模糊算法,一个超级简单但是又超级牛逼的算法,无论在效果上还是速度上都可以和Boxblur, stackblur或者是Gaussblur想媲美,效果上,比Boxblur来的 ...
- 分布式锁之redisson
redisson是redis官网推荐的java语言实现分布式锁的项目.当然,redisson远不止分布式锁,还包括其他一些分布式结构.详情请移步:https://github.com/mrniko/r ...
- 内核开发知识第一讲.内核中的数据类型.重要数据结构.常用内核API函数.
一丶内核中的数据类型 在内核中.程序的编写不能简单的用基本数据类型了. 因为操作系统不同.很有可能造成数据类型的长度不一.而产生重大问题.所以在内核中. 数据类型都一定重定义了. 数据类型 重定义数据 ...
- CS231n官方笔记授权翻译总集篇发布
CS231n简介 CS231n的全称是CS231n: Convolutional Neural Networks for Visual Recognition,即面向视觉识别的卷积神经网络.该课程是斯 ...