小测试:HashSet可以插入重复的元素吗?
Set的定义是一群不重复的元素的集合容器。也就是说,只要使用Set组件,应该是要保证相同的数据只能写入一份,要么报错,要么忽略。当然一般是直接忽略。
如题,HashSet是Set的一种实现,自然也符合其基本的定义。它的自然表现是,一直往里面插入数据,然后最后可以得到全部不重复的数据集合,即直到天然去重的效果。
1. 简单使用如下
先插入几个元素,得到的结果是没有重复的结果数量。
@Test
public void testSetAdd() {
Set<String> data = new HashSet<>();
data.add("a");
data.add("b");
data.add("a");
Assert.assertEquals("数量不正确", 2, data.size());
}
简单说下HashSet的实现原理,它是基于HashMap实现的一种set容器,直白说就是HashSet内部维护了一个HashMap的实例,插入和删除时委托给HashMap去实现,而HashMap中的Key就是HashSet中的值,HashMap的value就是一个常量Object.
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object(); /**
* Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
* default initial capacity (16) and load factor (0.75).
*/
public HashSet() {
map = new HashMap<>();
} /**
* 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;
}
还是比较清晰的。
2. HashSet保证元素不重复的原理
上节讲了HashSet是基于HashMap实现的,只不过它忽略了HashMap中的value信息。那么它怎么样保证不重复呢,自然也是依赖于HashMap了,HashMap中要保证key不重复有两个点:一是hashCode()要返回相同的值;二是equals()要返回true;换句话说就是要我们绝对认为该对象就是同一个时,才会替换原来的值。即要重写 hashCode()和equals()方法。样例如下:
class TableFieldDesc {
private String fieldName;
private String alias;
public TableFieldDesc(String fieldName, String alias) {
this.fieldName = fieldName;
this.alias = alias;
}
@Override
public int hashCode() {
return Objects.hash(fieldName, alias);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TableFieldDesc that = (TableFieldDesc) o;
return Objects.equals(fieldName, that.fieldName) &&
Objects.equals(alias, that.alias);
}
}
这样一来的话, new TableFieldDesc("f_a", "a") 与 new TableFieldDesc("f_a", "a") 就可以相等了,也就是说,如果有两个这样的元素插入,只会被当作同一个来处理了,从而达到去重的效果。测试如下:
@Test
public void testSetAdd2() {
Set<TableFieldDesc> data = new HashSet<>();
data.add(new TableFieldDesc("f_a", "a"));
data.add(new TableFieldDesc("f_a", "a"));
Assert.assertEquals("数量不正确", 1, data.size());
}
3. HashSet真能够保证不插入重复元素吗?
如题,hashSet真的能够保证不插入重复元素吗?我们常规理解好像是的,但是实际上还是有点问题。一般地,我们要求HashMap的key是不可变的,为什么会有这种要求呢?因为简单啊。但是,实际场景需要,也允许可变,就是要做到上节说的hashCode与equals重写。看起来一切都很美好,但是真的就没问题了吗?其实是有的。如下:
@Test
public void testSetAdd3() {
Set<TableFieldDesc> data = new HashSet<>();
TableFieldDesc fa = new TableFieldDesc("f_a", "a");
data.add(fa);
// 将f_a 改成了f_b,即 new TableFieldDesc("f_b", "a");
fa.setFieldName("f_b"); TableFieldDesc fb = new TableFieldDesc("f_b", "a");
data.add(fb);
System.out.println("data:" + data);
// 此处就插入了重复的元素了
Assert.assertEquals("数量不正确", 2, data.size());
}
如上就是,插入了两个重复的元素了,打印信息为:
data:[TableFieldDesc{fieldName='f_b', alias='a'}, TableFieldDesc{fieldName='f_b', alias='a'}]
完整的TableFieldDesc描述如下:

class TableFieldDesc {
private String fieldName;
private String alias;
public TableFieldDesc(String fieldName, String alias) {
this.fieldName = fieldName;
this.alias = alias;
}
public void setFieldName(String fieldName) {
this.fieldName = fieldName;
}
public void setAlias(String alias) {
this.alias = alias;
}
@Override
public int hashCode() {
return Objects.hash(fieldName, alias);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TableFieldDesc that = (TableFieldDesc) o;
return Objects.equals(fieldName, that.fieldName) &&
Objects.equals(alias, that.alias);
}
@Override
public String toString() {
return "TableFieldDesc{" +
"fieldName='" + fieldName + '\'' +
", alias='" + alias + '\'' +
'}';
}
}
为什么会这样呢?就像测试用例中写的,先插入了一个元素,然后再改变里面的某个值,随后再插入一个改变过之后的值,就重复了。因为hashCode是在插入的时候计算的,而当后续用户改变key的数据值,导致hashCode变更,这时就存在,在对应的slot上,不存在对应元素的情况,所以下次再插入另一个相同元素时,就被认为元素不存在从而插入重复数据了。
更严重的,当元素数据达到一定的时候,会存在扩容,会重复迁移所有元素,可能还会存在hash重新计算从而将重复的元素变为不重复的情况,就更玄乎了。(不过幸好,HashMap中的扩容不会重新计算hash,它会保留原来的hash,所以重复的元素永远会重复。)
结语警示:如果想用Set容器去做去重的工作,需要仔细了解其实现原理,而非想当然的认为会去重。能做到不改变key值就尽量避开,甚至不暴露修改数据的方法,即做到对象不可变的效果。从而避免踩坑。
小测试:HashSet可以插入重复的元素吗?的更多相关文章
- HashSet存储自定义类型元素和LinkedHashSet集合
HashSet集合存储自定义类型元素 HashSet存储自定义类型元素 set集合报错元素唯一: ~存储的元素(String,Integer,-Student,Person-)必须重写hashCode ...
- Cad 二次开发关于SelectCrossingPolygon和SelectFence返回结果Status为error的小测试
CAD2008的二次开发,有个很奇怪的现象,只要你选择的点集不在当前视图上SelectCrossingPolygon和SelectFence返回结果Status就会为error,所以要获取正确的结果, ...
- python 程序小测试
python 程序小测试 对之前写的程序做简单的小测试 ... # -*- encoding:utf-8 -*- ''' 对所写程序做简单的测试 @author: bpf ''' def GameOv ...
- PHP中使用PDO操作事务的一些小测试
关于事务的问题,我们就不多解释了,以后在学习 MySQL 的相关内容时再深入的了解.今天我们主要是对 PDO 中操作事务的一些小测试,或许能发现一些比较好玩的内容. 在 MyISAM 上使用事务会怎么 ...
- Java思考——HashSet集合如何保证元素的唯一性也就是不包含重复元素?
首先将源码逐级找出来1.HashSet<String> hs=new HashSet<String>(); hs.add("hello"); ...
- HashSet中实现不插入重复的元素
/* 看一下部分的HashSet源码.... public class HashSet<E> extends AbstractSet<E> implements Set< ...
- HTTP性能小测试
一直说node.js如何如何好,就来测试一下吧~~ 首先接受一个小工具 Apache Bench简称ab 可以用来测试http性能 利用Apache Bench测试Web引擎性能关于此工具的详细介绍参 ...
- mysql注入小测试
转自:http://www.jb51.net/article/46163.htm 在开发网站的时候,出于安全考虑,需要过滤从页面传递过来的字符.通常,用户可以通过以下接口调用数据库的内容:URL地址栏 ...
- 2014.3.12-C语言小测试
测试代码: 学号:1402049 1.请实现一个函数,功能为使用循环输出以下的图案 void print_alpha(int n) { int i, j; for(i=0;i<n;++i){ f ...
- SpringMvc拦截器小测试
前言 俗话说做项目是让人成长最快的方案,最近小编写项目的时候遇到了一个小问题.小编在项目中所负责的后台系统,但是后台系统是通过系统的页面是通过ifame联动的,那么这时候问题就来了,后台所做的所有操作 ...
随机推荐
- linux内核Makefile中的变量build--- 过渡篇(五)
一. kbuild系统主要涉及的几个文件 文件名 作用 Makefile 内核源代码顶层目录的Makefile文件 scripts/Makefile.build 通常在进行递归make时会用到的Mak ...
- YOLOv6在LabVIEW中的推理部署(含源码)
前言 YOLOv6 是美团视觉智能部研发的一款目标检测框架,致力于工业应用.如何使用python进行该模型的部署,官网已经介绍的很清楚了,但是对于如何在LabVIEW中实现该模型的部署,笔者目前还没有 ...
- spring cloud微服务搭建配置中心之携程开源框架Apollo
1.Apollo(阿波罗) Apollo(阿波罗)是携程框架部门研发的分布式配置中心,能够集中化管理应用不同环境.不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限.流程治理等特性,适 ...
- 因为此网站发送了 Google Chrome 无法处理的杂乱凭据
原文地址 thisisunsafe this is unsafe 这是不安全的,呵呵~ 具体描述 在chrome该页面上,直接键盘敲入这11个字符:thisisunsafe (鼠标点击当前页面任意位置 ...
- Web通用漏洞--文件包含
Web通用漏洞--文件包含 文件包含原理 在项目开发过程中,开发人员通常会将重复使用的函数写入单个文件中,在使用该类函数时,直接调用文件即可,无需重新编写,这种调用文件的过程成为文件包含.在文件包含过 ...
- Unity UGUI的Slider(滑动条)件组的介绍及使用
Unity UGUI的Slider(滑动条)件组的介绍及使用 1. 什么是Slider组件? Slider(滑动条)是Unity UGUI中的一种常用UI组件用,于在用户界面中实现滑动选择的功能.通过 ...
- MySQL 使用Navicat delete/insert into/update 大量数据表锁死,kill的线程后线程处于killed状态问题解决
MySQL 使用delete/insert into/update 大量数据表锁死,kill的线程后线程处于killed状态问题解决 实际生产环境问题描述: 使用Navicat备份BigData数据表 ...
- 解密Linux中的通用块层:加速存储系统,提升系统性能
通用块层 通用块层是Linux中的一个重要组件,用于管理不同块设备的统一接口,减少不同块设备的差异带来的影响.它位于文件系统和磁盘驱动之间,类似于Java中的适配器模式,让我们无需关注底层实现,只需提 ...
- 加密 K8s Secrets 的几种方案
前言 你可能已经听过很多遍这个不算秘密的秘密了--Kubernetes Secrets 不是加密的!Secret 的值是存储在 etcd 中的 base64 encoded(编码) 字符串.这意味着, ...
- C#集成ViewFaceCore人脸检测识别库
前言 人脸检测与识别现在已经很成熟了,C# 上有 ViewFaceCore 这个很方便的库,但这种涉及到 native 调用的库,一般会有一些坑,本文记录一下开发和部署的过程. 本文的项目是 AIHu ...