Java集合源码分析(九)——HashSet
简介
HashSet就是一个集合,里面不能有重复的元素,元素也是无序的。
HashSet其实就是调用了HashMap实现的,所以,它也不是线程安全的。
HashSet通过iterator()返回的迭代器是fail-fast的。
源码分析
由于HashSet的源码很短也很容易理解,这里就不再选取分析了。
package java.util;
import java.io.InvalidObjectException;
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;
// 用于插入HashMap时的固定值
private static final Object PRESENT = new Object();
// 默认构造
public HashSet() {
map = new HashMap<>();
}
// 填入集合的构造
public HashSet(Collection<? extends E> c) {
// 这里为什么需要在原来的容量和16中取最大值呢?看总结
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);
}
// 采用LinkedHashMap来实现
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();
}
// 克隆函数,直接用map的clone函数,将原来的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);
}
}
// 将set输出为序列化
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);
}
// 将输入写入set
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);
}
// 计算容量
capacity = (int) Math.min(size * Math.min(1 / loadFactor, 4.0f),
HashMap.MAXIMUM_CAPACITY);
// 创建一个新的内部map
map = (((HashSet<?>)this) instanceof LinkedHashSet ?
new LinkedHashMap<E,Object>(capacity, loadFactor) :
new HashMap<E,Object>(capacity, loadFactor));
// 依次写入集合元素
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);
}
}
总结
HashSet内部都是对HashMap的调用,不同的Key值,相同的value值。
可以通过迭代器和for-each方法对其进行遍历。
为什么在初始化的时候有map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));?
- 首先HashMap的默认初始容量是16,如果填入集合的大小经过变换之后还小于默认初始容量,那么就直接使用默认初始容量。
- HashMap的默认加载因子就是0.75,当HashMap的“阈值”(阈值=HashMap总的大小*加载因子) < “HashMap实际大小”时,就会将容量翻倍,所以 c.size()/.75f) + 1 计算出来的正好是总的空间大小。
Java集合源码分析(九)——HashSet的更多相关文章
- java集合源码分析(三):ArrayList
概述 在前文:java集合源码分析(二):List与AbstractList 和 java集合源码分析(一):Collection 与 AbstractCollection 中,我们大致了解了从 Co ...
- java集合源码分析(六):HashMap
概述 HashMap 是 Map 接口下一个线程不安全的,基于哈希表的实现类.由于他解决哈希冲突的方式是分离链表法,也就是拉链法,因此他的数据结构是数组+链表,在 JDK8 以后,当哈希冲突严重时,H ...
- Java 集合源码分析(一)HashMap
目录 Java 集合源码分析(一)HashMap 1. 概要 2. JDK 7 的 HashMap 3. JDK 1.8 的 HashMap 4. Hashtable 5. JDK 1.7 的 Con ...
- Java集合源码分析(四)Vector<E>
Vector<E>简介 Vector也是基于数组实现的,是一个动态数组,其容量能自动增长. Vector是JDK1.0引入了,它的很多实现方法都加入了同步语句,因此是线程安全的(其实也只是 ...
- Java集合源码分析(三)LinkedList
LinkedList简介 LinkedList是基于双向循环链表(从源码中可以很容易看出)实现的,除了可以当做链表来操作外,它还可以当做栈.队列和双端队列来使用. LinkedList同样是非线程安全 ...
- Java集合源码分析(二)ArrayList
ArrayList简介 ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长,类似于C语言中的动态申请内存,动态增长内存. ArrayList不是线程安全的,只能用在单线程环境下,多线 ...
- java集合源码分析几篇文章
java集合源码解析https://blog.csdn.net/ns_code/article/category/2362915
- Java集合源码分析(五)HashSet<E>
HashSet简介 HashSet实现Set接口,由哈希表(实际上是一个HashMap实例)支持.它不保证set 的迭代顺序:特别是它不保证该顺序恒久不变.此类允许使用null元素. HashSet源 ...
- Java集合源码分析(六)TreeSet<E>
TreeSet简介 TreeSet 是一个有序的集合,它的作用是提供有序的Set集合.它继承于AbstractSet抽象类,实现了NavigableSet<E>, Cloneable, j ...
随机推荐
- spring mvc 基础知识
spring mvc 在web.xml中的配置: 例子: <?xml version="1.0" encoding="UTF-8"?> <we ...
- 转载 数据库优化 - SQL优化
判断问题SQL判断SQL是否有问题时可以通过两个表象进行判断: 系统级别表象CPU消耗严重IO等待严重页面响应时间过长应用的日志出现超时等错误可以使用sar命令,top命令查看当前系统状态. 也可以通 ...
- Python _PyQt5对话框
Python 调用PyQt5 制作对话框,退出时候有二次确认(注:默认是直接退出) 1 # -*- ytf-8 -*- 2 """ 3 用PyQt建一个对话框,退出时提示 ...
- Python_迭代器与生成器
迭代器 迭代是Python最强大的功能之一,是访问集合元素的一种方式.. 迭代器是一个可以记住遍历的位置的对象. 迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束.迭代器只能往前不会后 ...
- SpringBoot Redis切换数据库遇到的坑
项目不同业务的redis数据存在不同的库中,操作数据需要切换redis库,在网上找了一段代码,确实可以切换数据库.但是使用一段时间后发现部分数据存储的数据库不正确,排查后发现setDatabase是线 ...
- CSS属性(display)
1.display属性 <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset= ...
- Bugku-cms1
一.先用御剑扫描 二.点击第一个目录,发现sql文件 三.把它载下来,用Notepad++打开.发现管理员的账号和密码(admin的密码好像被人改了,然后我用admin888登的后台) 四.后台地址 ...
- 厉害啊!第一次见到把Shiro运行流程写的这么清楚的,建议收藏起来慢慢看
前言 shiro是apache的一个开源框架,是一个权限管理的框架,实现 用户认证.用户授权. spring中有spring security (原名Acegi),是一个权限框架,它和spring依赖 ...
- mongodb分页查询,排序
mongodb代码 根据时间倒序,查看10条 db.表名.find({"_id":"xxx"}).sort({"inserted":-1}) ...
- Ubuntu14.04下安装Composer
下载Composer: curl -sS https://getcomposer.org/installer | php 安装Composer: /usr/bin/php composer.phar ...