HashSet的故事----Jdk源码解读
Hash,我们在说HashMap的时候,已经知道Hash是散列,Map是映射了。
那么Set又是什么呢 ?
先来看看Set的翻译是什么
n. [数] 集合;一套;布景;[机] 装置
这里Set所取的含义是集合。而且是数学概念上的集合。数学概念上的集合有什么特点呢?那就是Set中所有的元素不能重复。所以HashSet的意思就是以散列的形式维持一套不会有重复元素的集合。
接下来我们看看HashSet是怎么被Jdk实现的吧。(其实逻辑非常简单。)
类的声明:
hashSet 继承自AbstractSet,实现了Set类以及克隆接口和可序列化接口。
public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable
接口中定义了一个瞬时hashMap变量,这个变量是Set用来存储元素的容器:
private transient HashMap<E,Object> map;
接着定义一个Object变量,暂且记住这个变量的作用:标记元素是否存在的,具体如何使用,在后边的方法会介绍。
private static final Object PRESENT = new Object();
接着是五个构造函数:
1、无参构造函数
public HashSet() {
map = new HashMap<>();
}
2、初始化时可以将集合中的元素直接添加到Set中的构造函数
注意看这个地方计算MAP初始化大小的方式,取出当前集合collection参数的size除以0.75+1,这个数值和16计算,取较大值。
这样做的好处是,给map赋予一个足够长的大小,这样在给(防盗连接:本文首发自http://www.cnblogs.com/jilodream/ )map添加集合collection中元素的时候,map不需要反复rehash。同时如果集合collection中的元素较少时,仍然赋予一个较合适的大小16,为未来添加元素留下足够的空间:
public HashSet(Collection<? extends E> c) {
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
}
3、以初始容积大小,加载因子作为参数的构造函数
public HashSet(int initialCapacity, float loadFactor) {
map = new HashMap<>(initialCapacity, loadFactor);
}
4、以初始容积大小作为参数的构造函数
public HashSet(int initialCapacity) {
map = new HashMap<>(initialCapacity);
}
5、非public的构造函数
这个构造函数的前两个参数是初始容积和加载因子,第三个参数不会被使用,可以忽略,这个在jkd的注释中有写到。
同时这个构造函数所使用的map实例时一个linkedHashMap,这与前边的构造函数有所区别。
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
接下来是实例方法:
返回一个迭代器,这个迭代器实质上返回的是Set中map的key的迭代器
public Iterator<E> iterator() {
return map.keySet().iterator();
}
返回set中map的长度,作为Set的长度
public int size() {
return map.size();
}
判断Set是否为空,其实质其实上返回map的长度是否为0
public boolean isEmpty() {
return map.isEmpty();
}
contains(Object o),Set的核心方法,判断Set中是否存在指定元素o,实质上是判断map中是否存在这个key
public boolean contains(Object o) {
return map.containsKey(o);
}
add(E e),Set的核心方法,将元素e添加到Set中。
这个方法的本质,是将e作为key,present变量作为value存入map中。而map.put方法会返回原有的value值,如果是首次添加的话,会返回一个null。(防盗连接:本文首发自http://www.cnblogs.com/jilodream/ )所以add返回的结果表示当前map是否已经存在元素e。如果存在,则返回false,反之返回true。
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
remove(Object o),Set的核心方法,移除Set中的o元素。
如果Set中存在该元素,则返回true。因为map中remove key时,会返回当前key对应的value。
public boolean remove(Object o) {
return map.remove(o)==PRESENT;
}
清除Set中的元素
public void clear() {
map.clear();
}
clone()方法。
克隆当前Set对象,通过代码可以知道只克隆了当前Set对象,以及map对象。即二者的地址发生了改变,但是对于map中具体的元素,是没有克隆的。
@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的方法,但是虚拟机在正反序列化时仍然可以通过反射调用到他们。
先说序列化writeObject方法。
首先调用输出流的defaultWriteObject()记录下当前Set可以序列化的值,接着记录map的容积和负载因子以及大小。接着遍历map中的对象,依次记录下key元素(value不用记录,想想这是为什么?)。
而反序列化则反向的从流中读取数据,然后生成对象。
由于流是顺序读取的,因此反序列化时的顺序,与序列化中时保持一致的。这里不再过多的介绍正反序列化的内容。(防盗连接:本文首发自http://www.cnblogs.com/jilodream/ )
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
// Write out any hidden serialization magic
s.defaultWriteObject(); // Write out HashMap capacity and load factor
s.writeInt(map.capacity());
s.writeFloat(map.loadFactor()); // Write out size
s.writeInt(map.size()); // Write out all elements in the proper order.
for (E e : map.keySet())
s.writeObject(e);
}
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in any hidden serialization magic
s.defaultReadObject(); // Read capacity and verify non-negative.
int capacity = s.readInt();
if (capacity < 0) {
throw new InvalidObjectException("Illegal capacity: " +
capacity);
} // Read load factor and verify positive and non NaN.
float loadFactor = s.readFloat();
if (loadFactor <= 0 || Float.isNaN(loadFactor)) {
throw new InvalidObjectException("Illegal load factor: " +
loadFactor);
} // Read size and verify non-negative.
int size = s.readInt();
if (size < 0) {
throw new InvalidObjectException("Illegal size: " +
size);
} // Set the capacity according to the size and load factor ensuring that
// the HashMap is at least 25% full but clamping to maximum capacity.
capacity = (int) Math.min(size * Math.min(1 / loadFactor, 4.0f),
HashMap.MAXIMUM_CAPACITY); // Create backing HashMap
map = (((HashSet<?>)this) instanceof LinkedHashSet ?
new LinkedHashMap<E,Object>(capacity, loadFactor) :
new HashMap<E,Object>(capacity, loadFactor)); // Read in all elements in the proper order.
for (int i=0; i<size; i++) {
@SuppressWarnings("unchecked")
E e = (E) s.readObject();
map.put(e, PRESENT);
}
}
最后是spliterator()方法,这个方法是1.8中新添加的。
该方法返回的是map中对应的一个新的Spliterator实例。这里简单说下Spliterator的作用:spliterator是 split iterator的意思。也就是返回一个迭代器,但是这个迭代器是分割的,将元素分割成若干组,并不像原有的迭代器只能通过单一线程顺序的访问。它可以通过多线程并行的形式,访问容器中的元素,加快元素的访问效率。关于这个类的详细使用,我会在后续的文章中(防盗连接:本文首发自http://www.cnblogs.com/jilodream/ )更新。
public Spliterator<E> spliterator() {
return new HashMap.KeySpliterator<E,Object>(map, 0, -1, 0, 0);
}
通过查看jkd源代码,我们可以发现HashSet的本质,是对map进行了一层封装。使原有的y=f(x)形式可以更好的已数学Set的形式展现出来。
HashSet的故事----Jdk源码解读的更多相关文章
- Timer的故事----Jdk源码解读
咱们今天也来说说定时器Timer Timer是什么? Timer n. [电子] 定时器:计时器:计时员 从翻译来看,我们可以知道Timer的本意是,定时定点. 而JDK中Timer类也的确是这个本 ...
- HashTable的故事----Jdk源码解读
HashTable的故事 很早之前,在讲HashMap的时候,我们就说过hash是散列,把...弄碎的意思.hashtable中的hash也是这个意思,而table呢,是指数据表格,也就是说hasht ...
- JDK源码解读之toUnsignedString
我们知道,所有整数都是通过二进制编码的形式存储在内存中的.比如32位的整数,最高位是符号位,0代表正数,1代表负数. 那么怎么才能够将整数的二进制编码形式打印出来呢?Integer类提供了一个公有静态 ...
- Java是如何实现自己的SPI机制的? JDK源码(一)
注:该源码分析对应JDK版本为1.8 1 引言 这是[源码笔记]的JDK源码解读的第一篇文章,本篇我们来探究Java的SPI机制的相关源码. 2 什么是SPI机制 那么,什么是SPI机制呢? SPI是 ...
- jdk源码阅读笔记-HashSet
通过阅读源码发现,HashSet底层的实现源码其实就是调用HashMap的方法实现的,所以如果你阅读过HashMap或对HashMap比较熟悉的话,那么阅读HashSet就很轻松,也很容易理解了.我之 ...
- jdk1.8.0_45源码解读——HashSet的实现
jdk1.8.0_45源码解读——HashSet的实现 一.HashSet概述 HashSet实现Set接口,由哈希表(实际上是一个HashMap实例)支持.主要具有以下的特点: 不保证set的迭代顺 ...
- HashSet源码解读
一:先看其实现了哪些接口和继承了哪些类 1.实现了Serializable接口,表明它支持序列化. 2.实现了Cloneable接口,表明它支持克隆,可以调用超类的clone()方法进行浅拷贝. 3. ...
- JDK容器类Map源码解读
java.util.Map接口是JDK1.2开始提供的一个基于键值对的散列表接口,其设计的初衷是为了替换JDK1.0中的java.util.Dictionary抽象类.Dictionary是JDK最初 ...
- JDK容器类List,Set,Queue源码解读
List,Set,Queue都是继承Collection接口的单列集合接口.List常用的实现主要有ArrayList,LinkedList,List中的数据是有序可重复的.Set常用的实现主要是Ha ...
随机推荐
- linux 下cmake 编译 ,调用,调试 poco 1.6.0 小记
上篇文章 小记了: 关于 Poco::TCPServer框架 (windows 下使用的是 select模型) 学习笔记. http://www.cnblogs.com/bleachli/p/4352 ...
- 那些年一起用过的iOS开发利器[4月2号更新]
4月2号新增Runscope. Runscope 这是一家专注于API工具开发的公司,其创始人John Sheehan曾就职于IFTTT和Twilio.Runscope是一款集调试.测试于一身的网络服 ...
- javascript:算法之数组sort排序
数组sort排序 sort比较次数,sort用法,sort常用 描述 方法sort()将在原数组上对数组元素进行排序,即排序时不创建新的数组副本.如果调用方法sort()时没有使用参数,将按字母顺序( ...
- java swing文件内容检索工具
Java相关技术 - 文件内容检索工具 拿到一个几百M甚至上G的project让你去学习 有时候你会想知道某个关键词是在哪个文件里 比如:spring MVC配置的@RequestMapping,你从 ...
- Apache2 worker
http://www.php-internals.com/book/?p=chapt08/08-03-zend-thread-safe-in-php 在多线程系统中,进程保留着资源所有权的属性,而多个 ...
- argparse解析参数模块
一.简介: argparse是python用于解析命令行参数和选项的标准模块,用于代替已经过时的optparse模块.argparse模块的作用是用于解析命令行参数,例如python parseTes ...
- iOS9 适配(杂七杂八)
1.iOS9 以后,table cell 在旋转的时候会自动调整视图内容的布局,设置以下的属性,课禁止该行为. if (runTimeOSVersion >= 9.0f) { _listTabl ...
- freebsd 系统时间
http://blog.csdn.net/wowoto/article/details/5557810 https://www.douban.com/note/150233427/ date #查看当 ...
- MySQL 实现row_number() 分组排序功能
数据库基本脚本 CREATE TABLE `a` ( `ID` INT(10) NULL DEFAULT NULL, `class` INT(10) NULL DEFAULT NULL, `score ...
- 群晖SVN Server远程访问
打开路由器访问界面 选择转发规则->端口映射-新建 在弹出的界面中填写相应的端口号了内网ip 填写svn所在地址的IP,比如:192.168.30.2 添加映射端口,比如svn的默认端口是330 ...