HashSet为什么要设置PRESENT
直接进入正题,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 ? e==null : 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 ? e2==null : 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 ? e==null : 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相关推荐
- 集合{LinkedHashMap TreeMap HashSet LinkedHashSet TreeSet 快速失败机制 ConcurrentHashMap CAS 多线程协同扩容}(二)
目录标题 LinkedHashMap Map集合框架结构体系图 什么是LinkedHashMap Linked 链式 的意思 HashMap "哈希映射"的意思 LinkedHas ...
- 【集合框架】JDK1.8源码分析HashSet LinkedHashSet(八)
一.前言 分析完了List的两个主要类之后,我们来分析Set接口下的类,HashSet和LinkedHashSet,其实,在分析完HashMap与LinkedHashMap之后,再来分析HashSet ...
- java面试题29 牛客 以下关于集合类ArrayList、LinkedList、HashMap描述
java面试题29 牛客 以下关于集合类ArrayList.LinkedList.HashMap描述错误的是() A HashMap实现Map接口,它允许任何类型的键和值对象,并允许将null用作键或 ...
- Puppet--用户自动化管理
三.用户和组 用户资源:user 该资源类型用于管理系统用户,所以缺少一些特性来管理普通用户.利用 符合POSIX API标准的puppet自带的私有工具来进行用户和组管理, puppet不会直接修 ...
- Linux内存管理:kmemcheck介绍
目录 Linux内核内存管理第3部分. Linux内核中的kmemcheck简介 该kmemcheck机制在Linux内核中的实现 结论 链接 读原文:<Linux内存管理:kmemcheck介 ...
- 集合 (一) ----- 集合的基本概念与Collection集合详解
相关文章: <集合 (一) ----- 集合的基本概念与Collection集合详解> <集合 (二) ----- Map集合详解> 文章目录 集合的基本概念 一.集合的分类 ...
- linux内存管理_Linux内存管理(转)
声明:这篇文章是从网上转来的,本来想注上原作者,发现不少相同文章不同作者都标注原创,这就尴尬了...后来,我看此文章中插图都是英文,用图片搜索到原英文作者及出处,发现CSDN上真是TM乱七八糟,直接拿 ...
- 从零实现Transformer、ChatGLM-6B、本地知识库、模型(训练/推理)加速
前言 最近一直在做类ChatGPT项目的部署 微调,关注比较多的是两个:一个LLaMA,一个ChatGLM,会发现有不少模型是基于这两个模型去做微调的,说到微调,那具体怎么微调呢,因此又详细了解了一下 ...
- DirectX 图形接口指南
这些指南是我在阅读 DirectX9.0 SDK 中逐步翻译出来的.对于初次接触 DirectX Graphics 的编程者而言,这应该是很好的上手资料.其实,本人就是从这些指南开始深入 Direct ...
- JavaWeb Filter 过滤器
参考:JavaWeb过滤器(Filter)详解 1.简介 顾名思义就是对事物进行过滤的,在Web中的过滤器,当然就是对请求进行过滤,我们使用过滤器,就可以对请求进行拦截,然后做相应的处理,实现许多特殊 ...
最新文章
- U-Boot的LDS文件分析
- MSSQL数据库全库批量替换
- 雅虎开源可以提升流操作速度的DataSketches
- Swift学习字符串、数组、字典
- 什么是Apache Spark?这篇文章带你从零基础学起
- 理解lua中的metatable和__index
- vue 给取data值_一些Vue相关的面试题,帮助求职者提升竞争力
- 没有U盘怎么重装系统 无U盘重装系统教程
- v4l2_async_subdev_notifier_register 分析
- ORA-06401: NETCMN: invalid driver designator 的解决办法
- SVN解决冲突的办法
- photoshop插件制作_使用Photoshop更快地制作全景
- win7电脑,勾选“显示隐藏的文件、文件夹和驱动器”后自动取消勾选的解决方法...
- BZOJ 3007: 拯救小云公主
- DYA9面向对象中--super关键字
- 2.dex反编译工具的安装(jadx、gda、jeb)
- requirejs的源码学习(01)——初始化流程
- 阿里巴巴开源产品列表
- hive中字段长度函数_技术分享:hive常用内部函数
- 七大行星排列图片_七大行星大小排列顺序,其实是八大(水星最小/木星最大)【图文】...
热门文章
- 安卓手表wear开发获取心率
- MyDockFinder(mydock myfinder合二为一版)
- Ceph集群配置系列《四》Ceph块设备/RBD的使用
- 世界流调——Gary
- linux彻底清除历史记录
- Big Endian 和 Little Endian 详解
- “奋斗者”号下潜10909米:我们为什么要做深海探索?
- Anchor box坐标(Sac,Sar,Eac,Ear)到Precdict box坐标(Spc,Spr,Epc,Epr)关系推导
- can软件android,appcan-android
- 手把手教你搭建高逼格监控平台,动起来吧