【Java集合】HashSet源码解析以及HashSet与HashMap的区别
HashSet
前言
HashSet是一个不可重复且元素无序的集合。内部使用HashMap实现。
我们可以从HashSet源码的类注释中获取到如下信息:
- 底层基于HashMap实现,所以迭代过程中不能保证和增加时的顺序相同。
- add,remove,contains,size等方法的耗时性能,是不会随着数据量的增加而增加的。在不考虑Hash冲突的情况下时间复杂度都是O(1)。
- 线程不安全的集合,如果在多线程的场景下建议使用
//Collections#synchronizedSetCollections.synchronizedSet方法
Set s = Collections.synchronizedSet(new HashSet(...));
- 在迭代过程中,如果数据结构发生变化会抛出
ConcurrentModificationException异常。
组合HashMap
先看一下HashSet的类图
从上图中可以看出HashSet继承了AbstractSet并且实现了 Set,Cloneable,Serializable接口。
在Java中基于基类进行创新,有两种方法。
- 继承的方式。继承基类,重写基类的一些方法。
- 组合基础类,通过调用基础类的方法,来复用基础类的能力。
这里的HashSet使用的就是组合HashMap,优点如下:
- 继承表示父类是属于同一事物,而Set和Map本来表示的是两种不同的事物,所以继承关系不适用他,而且Java中的子类只能继承一个父类,后续难以扩展。
- 组合的话,更加灵活,可以任意的组合现有的基础类,并且可以在基础类的方法上进行扩展。且方法名可以自定义,无需和基础类保持一致。
在Java编程思想和 effective java中也建议多用组合少用继承。
以下是HashSet的组合实现:
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
{
static final long serialVersionUID = -5024744406713321676L;
//将HashMap组合起来,key是HashSet的key,value是下面的Object
private transient HashMap<E,Object> map;
// HashMap中的value
private static final Object PRESENT = new Object();
/**
* 构建一个新的,空的HashMap实例对象
* default initial capacity (16) and load factor (0.75).
*/
public HashSet() {
map = new HashMap<>();
}
/**
* 构造一个新集合类,负载因子时0.75,集合的容量由新的Collection决定
*/
public HashSet(Collection<? extends E> c) {
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
//和16比大小,如果给定的集合大小小于16,那初始容量大小就是16,如果大于16,就按照指定集合的容量
//HashMap扩容阀值的计算公式:Map容量*0.75f。一旦达到阀值就会扩容,此处这样写使我们期望的大小比扩容阀值大1,就不会扩容
addAll(c);
}
/**
*构造一个新的空集合HashMap实例,可以指定初始容量和负载因子
* @param initialCapacity the initial capacity of the hash map
* @param loadFactor the load factor of the hash map
* @throws IllegalArgumentException if the initial capacity is less
* than zero, or if the load factor is nonpositive
*/
public HashSet(int initialCapacity, float loadFactor) {
map = new HashMap<>(initialCapacity, loadFactor);
}
/**
*构造一个指定初始容量大小的HashMap,负载因子是默认的0.75
* @param initialCapacity the initial capacity of the hash table
* @throws IllegalArgumentException if the initial capacity is less
* than zero
*/
public HashSet(int initialCapacity) {
map = new HashMap<>(initialCapacity);
}
/**
* 这个构造创建的对象是LinkedHashMap可以指定初始容量大小和负载因子
*
* @param initialCapacity the initial capacity of the hash map
* @param loadFactor the load factor of the hash map
* @param dummy ignored (distinguishes this
* constructor from other int, float constructor.)
* @throws IllegalArgumentException if the initial capacity is less
* than zero, or if the load factor is nonpositive
*/
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
常用方法
add
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
调用HashMap的put方法,PRESENT作为value放在HashMap中,如果key不存在返回null,锁着这里进行判断如果添加的key已经存在返回false,不存在代表添加成功返回true。
contains
public boolean contains(Object o) {
return map.containsKey(o);
}
调用map的containsKey方法,如果找到有key=o返回true,否则返回false。
remove
public boolean remove(Object o) {
return map.remove(o)==PRESENT;
}
iterator
public Iterator<E> iterator() {
return map.keySet().iterator();
}
通过Map使用keySet返回一个key的Iterator对象
isEmpty
public boolean isEmpty() {
return map.isEmpty();
}
判断Set是不是为空,实际上是判断map中的size是不是为0
size
public int size() {
return map.size();
}
返回的是map中的元素个数
HashMap与HashSet的区别
HashMap实现了Map接口,HashSet实现了Set接口。
HashMap存储键值对,HashSet存储对象
HashMap调用put方法增加键值对,HashSet调用add方法添加对象(底层的实现还是map的put)
HashMap使用key计算对应的hashcode;
HashSet使用对象计算hashcode值,如果两个对象的hashcode相同,两者不一定相等;如果equals方法返回true则两个对象相等。
==与equals的相同和不同点
- ==判断的是两个变量或实例的地址(内存空间地址);equals方法判断的是变量或实例所指向的地址的值是不是一样的。
- ==比较的是对象的引用,equals比较的是对象的值是否相同。
为什么要规定重写equals方法时需要重写hashCode?
- 如果两个对象相等,则hashCode也一定相等。
- 两个对象相等,equals方法返回true。
- 两个对象有相同的hashcode,但是也不一定相同,因此在重写equals方法时需要重写hashCode方法,这样可以避免当equals方法返回true的时候因为没有重写hashCode方法,而导致对象的hashCode不相同,这与前面所讲的是矛盾的。hashCode默认是对堆上的对象产生独特的值,如果没有重写那么这两个对象无论如何都不相等。
小结
- HashSet底层声明了一个HashMap,HashSet对他做了一层简单封装,操作HashSet的元素实际上是操作HashMap的元素。
- HashSet不保证存放元素的顺序,无序不可重复。
- HashSet允许值为null,且只有一个。
- 因为HashSet底层调用的是HashMap 方法,所以是线程不安全的。
【Java集合】HashSet源码解析以及HashSet与HashMap的区别的更多相关文章
- Java集合-ArrayList源码解析-JDK1.8
◆ ArrayList简介 ◆ ArrayList 是一个数组队列,相当于 动态数组.与Java中的数组相比,它的容量能动态增长.它继承于AbstractList,实现了List, RandomAcc ...
- Java集合---LinkedList源码解析
一.源码解析1. LinkedList类定义2.LinkedList数据结构原理3.私有属性4.构造方法5.元素添加add()及原理6.删除数据remove()7.数据获取get()8.数据复制clo ...
- java集合类型源码解析之ArrayList
前言 作为一个老码农,不仅要谈架构.谈并发,也不能忘记最基础的语言和数据结构,因此特开辟这个系列的文章,争取每个月写1~2篇关于java基础知识的文章,以温故而知新. 如无特别之处,这个系列文章所使用 ...
- java集合类型源码解析之PriorityQueue
本来第二篇想解析一下LinkedList,不过扫了一下源码后,觉得LinkedList的实现比较简单,没有什么意思,于是移步PriorityQueue. PriorityQueue通过数组实现了一个堆 ...
- Java泛型底层源码解析-ArrayList,LinkedList,HashSet和HashMap
声明:以下源代码使用的都是基于JDK1.8_112版本 1. ArrayList源码解析 <1. 集合中存放的依然是对象的引用而不是对象本身,且无法放置原生数据类型,我们需要使用原生数据类型的包 ...
- 【java集合框架源码剖析系列】java源码剖析之HashSet
注:博主java集合框架源码剖析系列的源码全部基于JDK1.8.0版本.本博客将从源码角度带领大家学习关于HashSet的知识. 一HashSet的定义: public class HashSet&l ...
- Java集合框架源码(二)——hashSet
注:本人的源码基于JDK1.8.0,JDK的版本可以在命令行模式下通过java -version命令查看. 在前面的博文(Java集合框架源码(一)——hashMap)中我们详细讲了HashMap的原 ...
- 【java集合框架源码剖析系列】java源码剖析之TreeSet
本博客将从源码的角度带领大家学习TreeSet相关的知识. 一TreeSet类的定义: public class TreeSet<E> extends AbstractSet<E&g ...
- 【java集合框架源码剖析系列】java源码剖析之TreeMap
注:博主java集合框架源码剖析系列的源码全部基于JDK1.8.0版本.本博客将从源码角度带领大家学习关于TreeMap的知识. 一TreeMap的定义: public class TreeMap&l ...
随机推荐
- 微信小程序下拉加载下一页
小程序做得多了,有些常用功能就有必要记录一下 请看详解: 微信小程序之下拉触底时加载下一页 wxml参考: <scroll-view class='dataContainer' scroll-y ...
- 设置定时任务用rman删除归档日志脚本
之前使用数据库数据迁移过程中出现产生大量归档日志的情况(由于迁移的目标库是DG,必须开启归档). 为避免出现归档空间爆掉的情况,设置定时任务删除系统当前时间30分钟前的归档日志,脚本如下: cat d ...
- 详解汇编语言B和LDR指令与相对跳转和绝对跳转的关系
@ 目录 为什么要有相对跳转和绝对跳转? 在程序中只有相对跳转/绝对跳转是否可以? B(BL)和LDR指令具体怎么执行的? B(BL)和LDR跳转范围是如何规定的? 为什么要有相对跳转和绝对跳转? 顺 ...
- 1.k8s概述、安装、名词解释、资源清单
一.k8s概述 1.简介 Kubernetes是一个开源的,用于管理云平台中多个主机上的容器化的应用,Kubernetes的目标是让部署容器化的应用简单并且高效(powerful),Kubernete ...
- matplotlib学习日记(四)-绘制直方统计图形
(一)柱状图-应用在定性数据的可视化场景或者离散型数据,条形图和柱状图相似,只不过是函数barh import matplotlib as mpl import matplotlib.pyplot a ...
- vscode常用快捷键和插件
常用快捷键 快捷键 用途 ctrl+· 打开终端 shift+alt+A 块注释 ctrl+/ 行注释 ctrl+shift+F 文件夹查找 ctrl+enter 下方插入一行 ctrl+shift+ ...
- 后台返回的json数据,前台需要修改对象的键值名,重新修改数据格式
//例如,后台返回的数据为: var json = { data: [ {value: 103.95, name: "法拉利"}, {value: 103.2, name: & ...
- 为什么spring 被@Repository注解标识注入后是代理类
背景 今天发现一个奇怪的问题,有一个类是用的@Repository注解标识注入的,并且这个类并没有配置任何带代理和aop配置.但是得到的这个类不是一个原生类,而是一个代理类,如果换成了Componen ...
- vue3系列:vue3.0自定义弹框组件V3Popup|vue3.x手机端弹框组件
基于Vue3.0开发的轻量级手机端弹框组件V3Popup. 之前有分享一个vue2.x移动端弹框组件,今天给大家带来的是Vue3实现自定义弹框组件. V3Popup 基于vue3.x实现的移动端弹出框 ...
- AES 逻辑
分组长度 加密逻辑 轮函数 参考:链接 字节代换 两种方法: 1.首先(将字节看做GF(28)上的元素,映射到自己的乘法逆元)换成人话就是(对多项式的逆,参考:链接): 其次,对字节做仿射变换 2 ...