java ee是什么_死磕 java集合之HashSet源码分析

问题

(1)集合(Collection)和集合(Set)有什么区别?

(2)HashSet怎么保证添加元素不重复?

(3)HashSet是否允许null元素?

(4)HashSet是有序的吗?

(5)HashSet是同步的吗?

(6)什么是fail-fast?

简介

集合,这个概念有点模糊。

广义上来讲,java中的集合是指java.util包下面的容器类,包括和Collection及Map相关的所有类。

中义上来讲,我们一般说集合特指java集合中的Collection相关的类,不包含Map相关的类。

狭义上来讲,数学上的集合是指不包含重复元素的容器,即集合中不存在两个相同的元素,在java里面对应Set。

具体怎么来理解还是要看上下文环境。

比如,面试别人让你说下java中的集合,这时候肯定是广义上的。

再比如,下面我们讲的把另一个集合中的元素全部添加到Set中,这时候就是中义上的。

HashSet是Set的一种实现方式,底层主要使用HashMap来确保元素不重复。

源码分析

属性

// 内部使用HashMapprivate transient HashMap<E,Object> map;// 虚拟对象,用来作为value放到map中private static final Object PRESENT = new Object();

构造方法

public HashSet() {map = new HashMap<>();
}public HashSet(Collection<? extends E> c) {map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));addAll(c);
}public HashSet(int initialCapacity, float loadFactor) {map = new HashMap<>(initialCapacity, loadFactor);
}public HashSet(int initialCapacity) {map = new HashMap<>(initialCapacity);
}// 非public,主要是给LinkedHashSet使用的
HashSet(int initialCapacity, float loadFactor, boolean dummy) {map = new LinkedHashMap<>(initialCapacity, loadFactor);
}

构造方法都是调用HashMap对应的构造方法。

最后一个构造方法有点特殊,它不是public的,意味着它只能被同一个包或者子类调用,这是LinkedHashSet专属的方法。

添加元素

直接调用HashMap的put()方法,把元素本身作为key,把PRESENT作为value,也就是这个map中所有的value都是一样的。

public boolean add(E e) {return map.put(e, PRESENT)==null;
}

删除元素

直接调用HashMap的remove()方法,注意map的remove返回是删除元素的value,而Set的remov返回的是boolean类型。

这里要检查一下,如果是null的话说明没有该元素,如果不是null肯定等于PRESENT。

public boolean remove(Object o) {return map.remove(o)==PRESENT;
}

查询元素

Set没有get()方法哦,因为get似乎没有意义,不像List那样可以按index获取元素。

这里只要一个检查元素是否存在的方法contains(),直接调用map的containsKey()方法。

public boolean contains(Object o) {return map.containsKey(o);
}

遍历元素

直接调用map的keySet的迭代器。

public Iterator<E> iterator() {return map.keySet().iterator();
}

全部源码

package java.util;import java.io.InvalidObjectException;
import sun.misc.SharedSecrets;public class HashSet<E>extends AbstractSet<E>implements Set<E>, Cloneable, java.io.Serializable
{static final long serialVersionUID = -5024744406713321676L;// 内部元素存储在HashMap中private transient HashMap<E,Object> map;// 虚拟元素,用来存到map元素的value中的,没有实际意义private static final Object PRESENT = new Object();// 空构造方法public HashSet() {map = new HashMap<>();}// 把另一个集合的元素全都添加到当前Set中// 注意,这里初始化map的时候是计算了它的初始容量的public HashSet(Collection<? extends E> c) {map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));addAll(c);}// 指定初始容量和装载因子public HashSet(int initialCapacity, float loadFactor) {map = new HashMap<>(initialCapacity, loadFactor);}// 只指定初始容量public HashSet(int initialCapacity) {map = new HashMap<>(initialCapacity);}// LinkedHashSet专用的方法// dummy是没有实际意义的, 只是为了跟上上面那个操持方法签名不同而已HashSet(int initialCapacity, float loadFactor, boolean dummy) {map = new LinkedHashMap<>(initialCapacity, loadFactor);}// 迭代器public Iterator<E> iterator() {return map.keySet().iterator();}// 元素个数public int size() {return map.size();}// 检查是否为空public boolean isEmpty() {return map.isEmpty();}// 检查是否包含某个元素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;}// 清空所有元素public void clear() {map.clear();}// 克隆方法@SuppressWarnings("unchecked")public Object clone() {try {HashSet<E> newSet = (HashSet<E>) super.clone();newSet.map = (HashMap<E, Object>) map.clone();return newSet;} catch (CloneNotSupportedException e) {throw new InternalError(e);}}// 序列化写出方法private void writeObject(java.io.ObjectOutputStream s)throws java.io.IOException {// 写出非static非transient属性s.defaultWriteObject();// 写出map的容量和装载因子s.writeInt(map.capacity());s.writeFloat(map.loadFactor());// 写出元素个数s.writeInt(map.size());// 遍历写出所有元素for (E e : map.keySet())s.writeObject(e);}// 序列化读入方法private void readObject(java.io.ObjectInputStream s)throws java.io.IOException, ClassNotFoundException {// 读入非static非transient属性s.defaultReadObject();// 读入容量, 并检查不能小于0int capacity = s.readInt();if (capacity < 0) {throw new InvalidObjectException("Illegal capacity: " +capacity);}// 读入装载因子, 并检查不能小于等于0或者是NaN(Not a Number)// java.lang.Float.NaN = 0.0f / 0.0f;float loadFactor = s.readFloat();if (loadFactor <= 0 || Float.isNaN(loadFactor)) {throw new InvalidObjectException("Illegal load factor: " +loadFactor);}// 读入元素个数并检查不能小于0int size = s.readInt();if (size < 0) {throw new InvalidObjectException("Illegal size: " +size);}// 根据元素个数重新设置容量// 这是为了保证map有足够的容量容纳所有元素, 防止无意义的扩容capacity = (int) Math.min(size * Math.min(1 / loadFactor, 4.0f),HashMap.MAXIMUM_CAPACITY);// 再次检查某些东西, 不重要的代码忽视掉SharedSecrets.getJavaOISAccess().checkArray(s, Map.Entry[].class, HashMap.tableSizeFor(capacity));// 创建map, 检查是不是LinkedHashSet类型map = (((HashSet<?>)this) instanceof LinkedHashSet ?new LinkedHashMap<E,Object>(capacity, loadFactor) :new HashMap<E,Object>(capacity, loadFactor));// 读入所有元素, 并放入map中for (int i=0; i<size; i++) {@SuppressWarnings("unchecked")E e = (E) s.readObject();map.put(e, PRESENT);}}// 可分割的迭代器, 主要用于多线程并行迭代处理时使用public Spliterator<E> spliterator() {return new HashMap.KeySpliterator<E,Object>(map, 0, -1, 0, 0);}
}

总结

(1)HashSet内部使用HashMap的key存储元素,以此来保证元素不重复;

(2)HashSet是无序的,因为HashMap的key是无序的;

(3)HashSet中允许有一个null元素,因为HashMap允许key为null;

(4)HashSet是非线程安全的;

(5)HashSet是没有get()方法的;

彩蛋

(1)阿里手册上有说,使用java中的集合时要自己指定集合的大小,通过这篇源码的分析,你知道初始化HashMap的时候初始容量怎么传吗?

我们发现有下面这个构造方法,很清楚明白地告诉了我们怎么指定容量。

假如,我们预估HashMap要存储n个元素,那么,它的容量就应该指定为((n/0.75f) + 1),如果这个值小于16,那就直接使用16得了。

初始化时指定容量是为了减少扩容的次数,提高效率。

public HashSet(Collection<? extends E> c) {map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));addAll(c);
}

(2)什么是fail-fast?

fail-fast机制是java集合中的一种错误机制。

当使用迭代器迭代时,如果发现集合有修改,则快速失败做出响应,抛出ConcurrentModificationException异常。

这种修改有可能是其它线程的修改,也有可能是当前线程自己的修改导致的,比如迭代的过程中直接调用remove()删除元素等。

另外,并不是java中所有的集合都有fail-fast的机制。比如,像最终一致性的ConcurrentHashMap、CopyOnWriterArrayList等都是没有fast-fail的。

那么,fail-fast是怎么实现的呢?

细心的同学可能会发现,像ArrayList、HashMap中都有一个属性叫modCount,每次对集合的修改这个值都会加1,在遍历前记录这个值到expectedModCount中,遍历中检查两者是否一致,如果出现不一致就说明有修改,则抛出ConcurrentModificationException异常。


欢迎关注我的公众号“彤哥读源码”,查看更多源码系列文章, 与彤哥一起畅游源码的海洋。

9a6e9ece908515bd5564509ea32e56db.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/501978.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ubuntu wifi固定ip_自制wifi遥控小车!ESP8266实践指南(二)

上次带大家利用ESP8266自制了wifi控制的LED点阵屏幕&#xff0c;大家觉得怎么样呢&#xff1f; 手把手教你用wifi控制显示屏&#xff01;ESP8266实践指南(一)今天我们来做点更有意思的~ wifi遥控小车&#xff01;一、所需材料ESP8266 NodeMCU开发板&#xff1a;这次我们使用CP2…

micopython 18b20_[MicroPython]stm32f407控制DS18B20检测温度

1.实验目的 1. 学习在PC机系统中扩展简单I/O 接口的方法。 2. 进一步学习编制数据输出程序的设计方法。 3. 学习DS18B20的接线方法&#xff0c;并利用DS18B20检测当前温度。 2.所需元器件 F407Micropython开发板1块 数据线1条 DS18b20温度传感器1个 DS18B20测温模块(不含DS18B2…

float php 运算_写给 PHP 程序员的 Python 学习指南

文 | 汤青松SegmentFault编辑 | EarlGrey推荐 | 编程派公众号(ID&#xff1a;codingpy)一、背景人工智能这几年一直都比较火&#xff0c;笔者一直想去学习一番&#xff1b;因为一直是从事PHP开发工作&#xff0c;对于Python接触并不算多&#xff0c;总是在关键时候面临着 基础不…

类似ftp文件服务器有哪些,FTP的替代品有哪些,你知道吗?

在某些情况下&#xff0c;需要从服务器上传(或下载)文件。多年来&#xff0c;最流行的文件传输方法是文件传输协议(FTP)。FTP的一大优点是它支持断点续传。FTP收获了方便性&#xff0c;却在安全性上有所欠缺。FTP未加密&#xff0c;这意味着格式是开放的&#xff0c;文件可能在…

case里面两个条件_Go语言条件语句之 switch 语句

点击上方蓝色 “铁匠学编程” 关注我&#xff0c;让我们一起学习&#xff01;switch语句会根据传入条件不同&#xff0c;选择不同的分支执代码进行执行&#xff0c;他可以代替多个 if else 子句&#xff0c;Go 语言的分支语句和 PHP 的类似&#xff0c;Go 不需要显示的通过brea…

api 二次 开发 禅道_浅谈-软件开发流程

先直接放出我对软件开发的相关人员职责和流程&#xff1a;图一&#xff1a;软件开发的相关人员职责以下是截屏的开发流程泳道图&#xff1a;横轴是相关开发人员的工作模块&#xff1b;纵轴是从上至下开发时序周期。图二&#xff1a;软件开发的流程图从职责图和流程图对应到我们…

光遇安卓服务器维修,《光遇》渠道服更换手机解决办法

《光遇》游戏中渠道服更换手机怎么办&#xff1f;很多小伙伴更换手机后登陆游戏发现自己还需要重头开始&#xff0c;很多小伙伴表示不知道怎么找回之前的渠道服账号和数据&#xff1f;当然IOS玩家不用担心&#xff0c;如果更换IOS可以继续使用账号&#xff0c;但是IOS更换安卓是…

线程中task取消_Rust Async: async-task源码分析

async-std是rust异步生态中的基础运行时库之一&#xff0c;核心理念是合理的性能 用户友好的api体验。经过几个月密集的开发&#xff0c;前些天已经发布1.0稳定版本。因此是时候来一次深入的底层源码分析。async-std的核心是一个带工作窃取的多线程Executor&#xff0c;而其本…

extjs 方法执行顺序_TestNG之注解变压器amp;方法拦截器

一.注解变压器TestNG允许在执行期间修改所有注解的内容。当源代码中的注解大部分是正确的&#xff0c;但是有一些时刻你想要重写他们的值时&#xff0c;这个是非常有用的。可以使用注解变压器实现。注解变压器是一个实现了接口的类&#xff1a;public interface IAnnotationTra…

服务器升级中暂不可修改怎么回事,抖音服务器升级中,暂不支持本地区开播抖音怎么在法国直播?...

抖音服务器升级中&#xff0c;暂不支持本地区开播抖音怎么在法国直播&#xff1f;除了餐饮&#xff0c;腾讯自主研发的各种跨界开发节目也无人问津。至于年初腾讯app项目和资源&#xff0c;还没有整体发布&#xff0c;不过&#xff0c;神似的行业信息也有公布。这其中对爆红的博…

keil 查看 stm32 io波形_你知道 KEIL 自带示波器吗?

导读&#xff1a;很多时候我们并不满足于查看变量的值&#xff0c;可能还想看这个变量的历史变化&#xff0c;同时以波形的方式显示出来&#xff0c;这就需要了解 KEIL 另一个有趣的东西&#xff1a;逻辑分析仪。效果图&#xff1a;以 STM32F103RET6&#xff0c;外接 8M 晶振&a…

ai怎么取消颗粒效果_AI教程3个超实用设计技巧教程

1-AI教程-矢量颗粒墨点字体教程第一步选择自己需要图形第二步画一个一样大的黑白渐变&#xff0c;放上最上层。第三步效果-像素化-铜板雕刻第四步选择&#xff0c;粒状点&#xff0c;其他的也可以自己尝试。第五步对象-扩散-外观第六步图像描摹同时点击 扩展第七步选择图形&…

企业门户网站服务器,企业或个人门户网站对服务器前的重要准备 - 酷番云

互联网被称为继报纸、广播、杂志和电视之后的第五大数字媒体。因为传统媒体的高成本。而且还受到时间和地域的限制,除非你付出高昂代价,否则效果不太好。互联网是展示世界的窗口&#xff0c;也是信息交流的双向交流工具。成本低&#xff0c;回报丰厚。互联网已经成为越来越多企…

机器人踩滑板_不死神草、飞行滑板…超2000种创新发明在这里展出

便捷式单人飞行滑板、空海光电搜跟设备、“踩不死”的草坪…第二十四届全国发明展览会一带一路暨金砖国家技能发展与技术创新大赛今天(11月19日)上午在佛山市潭洲国际会展中心开幕戳视频看看有啥好玩↓ ↓ ↓视频来源&#xff1a;醒目视频智能中医艾灸床便捷式单人飞行滑板…

区分错误类型_数仓|几种SQL隐藏的错误,你遇到过吗?

本文分享主要描述了几种书写SQL时常见的一些隐藏错误&#xff0c;主要包括&#xff1a;在运算符中使用null值、在聚合数据时使用null值、求平均值时使用判断条件、滤条件中使用and和or、查询的列字段之间缺少逗号分隔、inner join与left join。都是一些比较细节的点&#xff0c…

-9 逆序输出一个整数的各位数字_【Java编程基本功】(八)逆序输出、是否为回文数,判断星期几,升序排列...

第二十四题给一个不多于5位的正整数&#xff0c;要求&#xff1a;一、求它是几位数&#xff0c;二、逆序打印出各位数字。代码&#xff1a;public 第二十五题一个5位数&#xff0c;判断它是不是回文数。代码&#xff1a;public 代码2&#xff1a;public 第二十六题请输入星期几…

动词变名词的变化规则_动词第三人称单数的变化规则及练习(含语音)

点击上面蓝字关注我“点击即可听录音”1. 大部分单词可以直接在动词后:s例&#xff1a;like -- likes play -- playsShe likes cola.发音规则&#xff1a;所加的“s”在清辅音后&#xff0c;发【s】&#xff0c;works在浊辅音及元音后&#xff0c;发【z】&#xff0c; plays在…

c语言sleep函数_做游戏,学C语言,小球碰撞游戏,菜鸡者从黑窗口到图形化编程...

CMD黑窗口小球运动这次教程&#xff0c;我们实现一个弹跳小球。需要学习完基础的变量、运算符、表达式&#xff0c;printf、scanf输入输出函数的用法&#xff0c;if-else、while、for语句的用法。第1步&#xff0c;显示静止的小球。效果为&#xff1a;第2步让小球斜着弹跳。主要…

语言五子棋无ai程序框图_2020输入法报告 如何选择更好的AI语音输入法?

不少人认为&#xff0c;现在输入法均已内置语音输入方式&#xff0c;只要是语音输入就没有什么差别。其实不然。近日&#xff0c;Mob研究院发布《2020中国第三方输入法行业洞察》报告&#xff0c;深入挖掘分析语音输入法行业现状及变化趋势。《报告》统计显示&#xff0c;讯飞输…

对应的ctrl_取消单元格合并,对空白单元格填充数据,学会Ctrl+Enter五秒搞定

Excel统计数据过程中&#xff0c;相信大家最不想碰到的就是合并单元格&#xff0c;因为合并区域会对我们的数据统计造成各种麻烦。取消单元格合并后&#xff0c;除第一个单元格有内容外其他的单元格都成了空值。案例说明&#xff1a;如上图所示&#xff0c;我们需要将左边合并单…