直接进入正题,HashSet是通过组合模式,使用HashMap的key是不重复的来实现HashSet中的元素是不重复的,遍历时为该map对象的keySet()即map的key集合。而HashMap通过hash函数和key实现,具体实现见HashMap,篇幅有限这里就不展开说明了。

以下为HashSet的源码,HashSet有一个全局唯一的PRESENT指向的Object对象,add的时候使用其作为map的value。

现在有疑问的地方就是,这里为什么不用null来代理PRESENT比直接使用new Object()还能节约空间?

    // Dummy value to associate with an Object in the backing Mapprivate static final Object PRESENT = new Object();
public boolean add(E e) {return map.put(e, PRESENT)==null;}

其中奥秘就在以下使用PRESENT的两个方法的返回值上

 public boolean add(E e) {return map.put(e, PRESENT)==null;}public boolean remove(Object o) {return map.remove(o)==PRESENT;}

两个值都是boolean类型,map.put和remove方法的处理逻辑均是,key有关联的值则返回对应的值,否则返回null。

/*** Removes the mapping for the specified key from this map if present.** @param  key key whose mapping is to be removed from the map* @return the previous value associated with <tt>key</tt>, or*         <tt>null</tt> if there was no mapping for <tt>key</tt>.*         (A <tt>null</tt> return can also indicate that the map*         previously associated <tt>null</tt> with <tt>key</tt>.)*/public V remove(Object key)..../*** Associates the specified value with the specified key in this map.* If the map previously contained a mapping for the key, the old* value is replaced.** @param key key with which the specified value is to be associated* @param value value to be associated with the specified key* @return the previous value associated with <tt>key</tt>, or*         <tt>null</tt> if there was no mapping for <tt>key</tt>.*         (A <tt>null</tt> return can also indicate that the map*         previously associated <tt>null</tt> with <tt>key</tt>.)*/public V put(K key, V value)...

直接上代码验证,以下代码为HashSet的源码拷贝版,只保留了主要方法。

public class MyHashSet<E>extends AbstractSet<E>implements Cloneable, java.io.Serializable
{static final long serialVersionUID = -5024744406713321676L;private transient HashMap<E,Object> map;// Dummy value to associate with an Object in the backing Map
//    private static final Object PRESENT = new Object();private static final Object PRESENT = null;/*** Constructs a new, empty set; the backing <tt>HashMap</tt> instance has* default initial capacity (16) and load factor (0.75).*/public MyHashSet() {map = new HashMap<>();}/*** Returns an iterator over the elements in this set.  The elements* are returned in no particular order.** @return an Iterator over the elements in this set* @see ConcurrentModificationException*/public Iterator<E> iterator() {return map.keySet().iterator();}/*** Returns the number of elements in this set (its cardinality).** @return the number of elements in this set (its cardinality)*/public int size() {return map.size();}/*** Returns <tt>true</tt> if this set contains no elements.** @return <tt>true</tt> if this set contains no elements*/public boolean isEmpty() {return map.isEmpty();}/*** Returns <tt>true</tt> if this set contains the specified element.* More formally, returns <tt>true</tt> if and only if this set* contains an element <tt>e</tt> such that* <tt>(o==null&nbsp;?&nbsp;e==null&nbsp;:&nbsp;o.equals(e))</tt>.** @param o element whose presence in this set is to be tested* @return <tt>true</tt> if this set contains the specified element*/public boolean contains(Object o) {return map.containsKey(o);}/*** Adds the specified element to this set if it is not already present.* More formally, adds the specified element <tt>e</tt> to this set if* this set contains no element <tt>e2</tt> such that* <tt>(e==null&nbsp;?&nbsp;e2==null&nbsp;:&nbsp;e.equals(e2))</tt>.* If this set already contains the element, the call leaves the set* unchanged and returns <tt>false</tt>.** @param e element to be added to this set* @return <tt>true</tt> if this set did not already contain the specified* element */public boolean add(E e) {return map.put(e, PRESENT)==null;}/*** Removes the specified element from this set if it is present.* More formally, removes an element <tt>e</tt> such that* <tt>(o==null&nbsp;?&nbsp;e==null&nbsp;:&nbsp;o.equals(e))</tt>,* if this set contains such an element.  Returns <tt>true</tt> if* this set contained the element (or equivalently, if this set* changed as a result of the call).  (This set will not contain the* element once the call returns.)** @param o object to be removed from this set, if present* @return <tt>true</tt> if the set contained the specified element*/public boolean remove(Object o) {return map.remove(o)==PRESENT;}/*** Removes all of the elements from this set.* The set will be empty after this call returns.*/public void clear() {map.clear();}/*** Returns a shallow copy of this <tt>HashSet</tt> instance: the elements* themselves are not cloned.** @return a shallow copy of this set*/@SuppressWarnings("unchecked")public Object clone() {try {MyHashSet<E> newSet = (MyHashSet<E>) super.clone();newSet.map = (HashMap<E, Object>) map.clone();return newSet;} catch (CloneNotSupportedException e) {throw new InternalError(e);}}}

针对以上代码执行测试案例:

        Set myHashSet = new MyHashSet();System.out.println(myHashSet.add("zhangsan"));System.out.println(myHashSet.add("zhangsan"));System.out.println(myHashSet.add("zhangsan"));System.out.println(myHashSet.remove("zhangsan"));System.out.println(myHashSet.remove("zhangsan"));System.out.println(myHashSet.remove("zhangsan"));

修改PRESENT为null之后,输出结果为

true
true
true
true
true
true

结果显然不符合要求,无法区分key在HashSet是否存在,使用PRESENT的理论是,使用一个对象,remove和add,key存在时操作返回true,key不存在时返回false,可以对key是否存在作出区分。

当PRESENT为null时,是无法区分的。使用一些空间可以换取操作的有效信息,操作上可以更加灵活,因此是有价值的,到此就回答了最初的问题。

最后抛出一个问题,MyHashSet没有像HashSet一样实现Set接口,因为AbstractSet已经implements Set接口,从刚才的使用上来看MyHashSet和HashSet并无区别,那为什么HashSet还有再实现Set接口,欢迎大家说出自己的看法。

HashSet为什么要设置PRESENT相关推荐

  1. 集合{LinkedHashMap TreeMap HashSet LinkedHashSet TreeSet 快速失败机制 ConcurrentHashMap CAS 多线程协同扩容}(二)

    目录标题 LinkedHashMap Map集合框架结构体系图 什么是LinkedHashMap Linked 链式 的意思 HashMap "哈希映射"的意思 LinkedHas ...

  2. 【集合框架】JDK1.8源码分析HashSet LinkedHashSet(八)

    一.前言 分析完了List的两个主要类之后,我们来分析Set接口下的类,HashSet和LinkedHashSet,其实,在分析完HashMap与LinkedHashMap之后,再来分析HashSet ...

  3. java面试题29 牛客 以下关于集合类ArrayList、LinkedList、HashMap描述

    java面试题29 牛客 以下关于集合类ArrayList.LinkedList.HashMap描述错误的是() A HashMap实现Map接口,它允许任何类型的键和值对象,并允许将null用作键或 ...

  4. Puppet--用户自动化管理

     三.用户和组 用户资源:user 该资源类型用于管理系统用户,所以缺少一些特性来管理普通用户.利用 符合POSIX API标准的puppet自带的私有工具来进行用户和组管理, puppet不会直接修 ...

  5. Linux内存管理:kmemcheck介绍

    目录 Linux内核内存管理第3部分. Linux内核中的kmemcheck简介 该kmemcheck机制在Linux内核中的实现 结论 链接 读原文:<Linux内存管理:kmemcheck介 ...

  6. 集合 (一) ----- 集合的基本概念与Collection集合详解

    相关文章: <集合 (一) ----- 集合的基本概念与Collection集合详解> <集合 (二) ----- Map集合详解> 文章目录 集合的基本概念 一.集合的分类 ...

  7. linux内存管理_Linux内存管理(转)

    声明:这篇文章是从网上转来的,本来想注上原作者,发现不少相同文章不同作者都标注原创,这就尴尬了...后来,我看此文章中插图都是英文,用图片搜索到原英文作者及出处,发现CSDN上真是TM乱七八糟,直接拿 ...

  8. 从零实现Transformer、ChatGLM-6B、本地知识库、模型(训练/推理)加速

    前言 最近一直在做类ChatGPT项目的部署 微调,关注比较多的是两个:一个LLaMA,一个ChatGLM,会发现有不少模型是基于这两个模型去做微调的,说到微调,那具体怎么微调呢,因此又详细了解了一下 ...

  9. DirectX 图形接口指南

    这些指南是我在阅读 DirectX9.0 SDK 中逐步翻译出来的.对于初次接触 DirectX Graphics 的编程者而言,这应该是很好的上手资料.其实,本人就是从这些指南开始深入 Direct ...

  10. JavaWeb Filter 过滤器

    参考:JavaWeb过滤器(Filter)详解 1.简介 顾名思义就是对事物进行过滤的,在Web中的过滤器,当然就是对请求进行过滤,我们使用过滤器,就可以对请求进行拦截,然后做相应的处理,实现许多特殊 ...

最新文章

  1. U-Boot的LDS文件分析
  2. MSSQL数据库全库批量替换
  3. 雅虎开源可以提升流操作速度的DataSketches
  4. Swift学习字符串、数组、字典
  5. 什么是Apache Spark?这篇文章带你从零基础学起
  6. 理解lua中的metatable和__index
  7. vue 给取data值_一些Vue相关的面试题,帮助求职者提升竞争力
  8. 没有U盘怎么重装系统 无U盘重装系统教程
  9. v4l2_async_subdev_notifier_register 分析
  10. ORA-06401: NETCMN: invalid driver designator 的解决办法
  11. SVN解决冲突的办法
  12. photoshop插件制作_使用Photoshop更快地制作全景
  13. win7电脑,勾选“显示隐藏的文件、文件夹和驱动器”后自动取消勾选的解决方法...
  14. BZOJ 3007: 拯救小云公主
  15. DYA9面向对象中--super关键字
  16. 2.dex反编译工具的安装(jadx、gda、jeb)
  17. requirejs的源码学习(01)——初始化流程
  18. 阿里巴巴开源产品列表
  19. hive中字段长度函数_技术分享:hive常用内部函数
  20. 七大行星排列图片_七大行星大小排列顺序,其实是八大(水星最小/木星最大)【图文】...

热门文章

  1. 安卓手表wear开发获取心率
  2. MyDockFinder(mydock myfinder合二为一版)
  3. Ceph集群配置系列《四》Ceph块设备/RBD的使用
  4. 世界流调——Gary
  5. linux彻底清除历史记录
  6. Big Endian 和 Little Endian 详解
  7. “奋斗者”号下潜10909米:我们为什么要做深海探索?
  8. Anchor box坐标(Sac,Sar,Eac,Ear)到Precdict box坐标(Spc,Spr,Epc,Epr)关系推导
  9. can软件android,appcan-android
  10. 手把手教你搭建高逼格监控平台,动起来吧