聊聊HashSet源码

代码星冰乐

专注成就未来

首页 归档 关于

聊聊HashSet源码

May 29, 2018 | ChanghuiNhaifeiWu | Java | 阅读
文章目录
  1. 1. HashSet的UML图
  2. 2. HashSet简介
    1. 2.1. HashSet数据结构
    2. 2.2. HashSet构造函数
  3. 3. 聊聊HashSet的主要方法实现
    1. 3.1. 迭代器
    2. 3.2. 增加元素
    3. 3.3. 删除元素
    4. 3.4. 对象拷贝
  4. 4. 聊聊HashSet与HashMap的关系
  5. 5. 特性小结
  6. 6. 参考文章

作 者:ChanghuiNhaifeiWu
原文链接:https://www.hchstudio.cn/article/2018/fea5/
版权声明:非特殊声明均为本站原创作品,转载时请注明作者和原文链接。


由于版权原因,请阅读原文 --> 聊聊HashSet源码

关注我们

作 者:ChanghuiNhaifeiWu
原文链接:https://www.hchstudio.cn/article/2018/fea5/
版权声明:非特殊声明均为本站原创作品,转载时请注明作者和原文链接。

作 者:ChanghuiNhaifeiWu
原文链接:https://www.hchstudio.cn/article/2018/fea5/
版权声明:非特殊声明均为本站原创作品,转载时请注明作者和原文链接。

今天聊一下HashSet源码,HashSet内部基本使用HashMap来实现,本博客将通过一下几个方向讲解。

HashSet的UML图

HashMap的UML图

HashSet简介

HashSet数据结构

HashSet内部使用HashMap来实现,HashMap的key为要存储的元素,value为一个Object,大致数据结构如下:

1
2
3
4
5
public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable {
static final long serialVersionUID = -5024744406713321676L;
private transient HashMap<E,Object> map;
private static final Object PRESENT = new Object();
}

  • serialVersionUID:常量,序列化所用的ID
  • map:使用HashMap来保存HashSet中所有元素,并使用transient关键字修饰,防止被序列化,具体序列化过程,后面会有说到
  • PRESENT:常量,默认为map的value值

HashSet构造函数

1
2
3
4
5
6
7
8
9
10
11
12
public HashSet(Collection<? extends E> c) {  
map = new HashMap<E,Object>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
}

public HashSet(int initialCapacity, float loadFactor) {
map = new HashMap<E,Object>(initialCapacity, loadFactor);
}

HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<E,Object>(initialCapacity, loadFactor);
}

这里举例列举了三种构造函数

  1. 第一种构造一个包含指定collection中的元素的新set,容器大小为collection大小的4/3倍,和16的最大值
  2. 第二种传入初始容量和加载因子,构造一个空的HashSetLinkedHashMap,
  3. 第三种传入初始容量、加载因子和标记,构造一个空的LinkedHashMap,此构造函数为包访问权限,不对外公开,实际只是是对LinkedHashSet的支持。

聊聊HashSet的主要方法实现

迭代器

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

返回对此set中元素进行迭代的迭代器。返回元素的顺序并不是特定的。底层实际调用底层HashMap的keySet来返回所有的key,可见HashSet中的元素,只是存放在了底层HashMap的key上。

增加元素

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

底层实际将将该元素作为key放入HashMap。由于HashMap的put()方法添加key-value对时,当新放入HashMap的Entry中key,与集合中原有Entry的key相同(hashCode()返回值相等,通过equals比较也返回true),新添加的Entry的value会将覆盖原来Entry的value,但key不会有任何改变,因此如果向HashSet中添加一个已经存在的元素时,新添加的集合元素将不会被放入HashMap中, 原来的元素也不会有任何改变,这也就满足了Set中元素不重复的特性。

删除元素

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

如果指定元素存在于此set中,则将其移除。更确切地讲,如果此set包含一个满足(o==null ? e==null : o.equals(e))的元素e,则将其移除。如果此set已包含该元素,则返回true。底层实际调用HashMap的remove方法删除指定Entry。

对象拷贝

1
2
3
4
5
6
7
8
9
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();
}
}

返回此HashSet实例的浅表副本:并没有复制这些元素本身。底层实际调用HashMap的clone()方法,HashMap的clone()为浅拷贝,故HashSet的clone也是浅拷贝。

聊聊HashSet与HashMap的关系

从上面的源码可以看出来,HashSet与HashMap的关系不可谓不密切,以至于不敢相信上面的UML是对的。因此,对于HashSet而言,它是基于HashMap实现的,HashSet底层使用HashMap来保存所有元素,因此HashSet源码的实现比较简单,相关HashSet的操作,都是直接调用底层HashMap的相关方法来完成。

特性小结

  1. 从源码来看,HashSet无非是一个阉割版的HashMap,所以要想明白HashSet的实现原理,HashMap源码坑还是要跳的。
  2. 对于HashSet中保存的对象,请注意正确重写其equals和hashCode方法,以保证放入的对象的唯一性。
  3. Set是利用底层的Map对于重复的key不放入的特性来保证元素的不重复的。
  4. HashSet没有提供get()方法,原因是同HashMap一样,Set内部是无序的,只能通过迭代的方式获得。

    参考文章

  • HashSet源码分析(基于JDK8)
  • 深入Java集合学习系列:HashSet的实现原理

关注我们

作 者:ChanghuiNhaifeiWu
原文链接:https://www.hchstudio.cn/article/2018/fea5/
版权声明:非特殊声明均为本站原创作品,转载时请注明作者和原文链接。

分享
Java源码解析
死磕Java之聊聊HashMap源码(基于JDK1.8)classpath* 和 classpath使用遇到的问题
微信关注我们
分类
  • Android8
  • Go4
  • Java59
  • Kafka,Java1
  • Kotlin2
  • Linux1
  • MapReduce1
  • Python2
  • Raft1
  • Redis1
  • ThreadPoolExecutor1
  • go1
  • 工具1
  • 总结8
  • 旅游日记1
标签
Nginx ChanghuiN haifeiWu Android Java 设计模式 hexo Kotlin 算法 MySQL 源码解析 Python Redis golang web Kafka 配置中心 总结 性能优化 旅游日记 Shell Go 问题排查 译文 Docker Spring Boot 工具 学习笔记 WebFlux 性能测试 go 散列表 源码 netty Raft
最近文章
  • Kafka的日志复制机制
  • 从20到21
  • go 并发编程
  • 【译】了解Linux CPU负载-您何时应该担心?
  • Zookeeper 与分布式锁
  • 基于Redis的分布式锁到底安全吗?
  • 【译】Raft 学生指南
  • ThreadPoolExecutor 的简单梳理
  • MapReduce 的简单实现
  • 使用 Map 实现策略模式
福利专区
    免费SSL证书
      阿里云红包
        腾讯云专属福利
        Copyright © 2021 代码星冰乐. Powered by ChanghuiN. 版权所有 晋ICP备15001365号
        特别感谢: 云服务器服务商 、 CDN 服务商