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源码解读的更多相关文章

  1. Timer的故事----Jdk源码解读

    咱们今天也来说说定时器Timer Timer是什么? Timer  n. [电子] 定时器:计时器:计时员 从翻译来看,我们可以知道Timer的本意是,定时定点. 而JDK中Timer类也的确是这个本 ...

  2. HashTable的故事----Jdk源码解读

    HashTable的故事 很早之前,在讲HashMap的时候,我们就说过hash是散列,把...弄碎的意思.hashtable中的hash也是这个意思,而table呢,是指数据表格,也就是说hasht ...

  3. JDK源码解读之toUnsignedString

    我们知道,所有整数都是通过二进制编码的形式存储在内存中的.比如32位的整数,最高位是符号位,0代表正数,1代表负数. 那么怎么才能够将整数的二进制编码形式打印出来呢?Integer类提供了一个公有静态 ...

  4. Java是如何实现自己的SPI机制的? JDK源码(一)

    注:该源码分析对应JDK版本为1.8 1 引言 这是[源码笔记]的JDK源码解读的第一篇文章,本篇我们来探究Java的SPI机制的相关源码. 2 什么是SPI机制 那么,什么是SPI机制呢? SPI是 ...

  5. jdk源码阅读笔记-HashSet

    通过阅读源码发现,HashSet底层的实现源码其实就是调用HashMap的方法实现的,所以如果你阅读过HashMap或对HashMap比较熟悉的话,那么阅读HashSet就很轻松,也很容易理解了.我之 ...

  6. jdk1.8.0_45源码解读——HashSet的实现

    jdk1.8.0_45源码解读——HashSet的实现 一.HashSet概述 HashSet实现Set接口,由哈希表(实际上是一个HashMap实例)支持.主要具有以下的特点: 不保证set的迭代顺 ...

  7. HashSet源码解读

    一:先看其实现了哪些接口和继承了哪些类 1.实现了Serializable接口,表明它支持序列化. 2.实现了Cloneable接口,表明它支持克隆,可以调用超类的clone()方法进行浅拷贝. 3. ...

  8. JDK容器类Map源码解读

    java.util.Map接口是JDK1.2开始提供的一个基于键值对的散列表接口,其设计的初衷是为了替换JDK1.0中的java.util.Dictionary抽象类.Dictionary是JDK最初 ...

  9. JDK容器类List,Set,Queue源码解读

    List,Set,Queue都是继承Collection接口的单列集合接口.List常用的实现主要有ArrayList,LinkedList,List中的数据是有序可重复的.Set常用的实现主要是Ha ...

随机推荐

  1. android开发中fragment获取context

    在用到fragment时无法使用.this来指定当前context内容,android开发中fragment获取context,可以使用getActivity().getApplicationCont ...

  2. react-native 之布局篇

    一.宽度单位和像素密度 react的宽度不支持百分比,设置宽度时不需要带单位,那么默认的单位是什么呢? /** * Sample React Native App * https://github.c ...

  3. Python2 下 Unicode 的一个小bug

    关于Python的编码问题已经是老生常谈了,此处主要是介绍一个罕见的问题,也算是Python2的一个bug了(Python3不会有此问题). 在有时候我们去爬取网页或者调用一些第三方库获取文本的时候, ...

  4. Python之路第一课Day1--随堂笔记

    课堂大纲: 一.Python介绍 二.发展史 三.Python 2 or 3? 四.安装 五.Hello World程序 六.变量 七.用户输入 八.模块初识 九..pyc是个什么鬼? 十.数据类型初 ...

  5. linux自用命令

    文件操作权限设置: chmod 777 文件名/文件夹名         拥有所有权限 http://www_xpc8_com/ chmod 755 文件名/文件夹名         属主有所有权限, ...

  6. php5.6+apache2.4环境配置

    Apache2.4 环境:https://www.microsoft.com/en-US/download/details.aspx?id=48145php5.6 环境:http://www.micr ...

  7. js入门篇之Math对象

    Math对象用于执行数学任务 Math对象的属性: Math对象的方法: 常用属性和方法: Math.PI ----------------返回圆周率3.14 ... Math.ceil(x) --- ...

  8. wifi 破解

      基础的知识: ESSID :无线网络的名字 BSSID  :是AP的mac地址 CH: 工作信道 AP   :无线访问接入点 WEP WPA/WPA2 STATION   :客户机mac DHCP ...

  9. 榮耀6 Plus將是一部沒有對手的手機

    華為榮耀官方微博發佈消息正式確定了年度旗艦新品將命名為榮耀6 Plus,據稱,該機將是“2014年度最最旗艦手機”,並將集“科學與美學一身”.“探索幾何與視覺極限”,同時,官方微博還不低調地宣稱該機將 ...

  10. awk之特征相同行的合并 ~转

    awk之特征相同行的合并 文本: 1001  hisk01 1001  hisk02 1001  hisk03 1002  hisk04 1002  hisk05 1002  hisk06 1003 ...