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&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;
}

  还是比较清晰的。

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可以插入重复的元素吗?的更多相关文章

  1. HashSet存储自定义类型元素和LinkedHashSet集合

    HashSet集合存储自定义类型元素 HashSet存储自定义类型元素 set集合报错元素唯一: ~存储的元素(String,Integer,-Student,Person-)必须重写hashCode ...

  2. Cad 二次开发关于SelectCrossingPolygon和SelectFence返回结果Status为error的小测试

    CAD2008的二次开发,有个很奇怪的现象,只要你选择的点集不在当前视图上SelectCrossingPolygon和SelectFence返回结果Status就会为error,所以要获取正确的结果, ...

  3. python 程序小测试

    python 程序小测试 对之前写的程序做简单的小测试 ... # -*- encoding:utf-8 -*- ''' 对所写程序做简单的测试 @author: bpf ''' def GameOv ...

  4. PHP中使用PDO操作事务的一些小测试

    关于事务的问题,我们就不多解释了,以后在学习 MySQL 的相关内容时再深入的了解.今天我们主要是对 PDO 中操作事务的一些小测试,或许能发现一些比较好玩的内容. 在 MyISAM 上使用事务会怎么 ...

  5. Java思考——HashSet集合如何保证元素的唯一性也就是不包含重复元素?

    首先将源码逐级找出来1.HashSet<String> hs=new HashSet<String>();         hs.add("hello"); ...

  6. HashSet中实现不插入重复的元素

    /* 看一下部分的HashSet源码.... public class HashSet<E> extends AbstractSet<E> implements Set< ...

  7. HTTP性能小测试

    一直说node.js如何如何好,就来测试一下吧~~ 首先接受一个小工具 Apache Bench简称ab 可以用来测试http性能 利用Apache Bench测试Web引擎性能关于此工具的详细介绍参 ...

  8. mysql注入小测试

    转自:http://www.jb51.net/article/46163.htm 在开发网站的时候,出于安全考虑,需要过滤从页面传递过来的字符.通常,用户可以通过以下接口调用数据库的内容:URL地址栏 ...

  9. 2014.3.12-C语言小测试

    测试代码: 学号:1402049 1.请实现一个函数,功能为使用循环输出以下的图案 void print_alpha(int n) { int i, j; for(i=0;i<n;++i){ f ...

  10. SpringMvc拦截器小测试

    前言 俗话说做项目是让人成长最快的方案,最近小编写项目的时候遇到了一个小问题.小编在项目中所负责的后台系统,但是后台系统是通过系统的页面是通过ifame联动的,那么这时候问题就来了,后台所做的所有操作 ...

随机推荐

  1. 飞桨paddlespeech语音唤醒推理C定点实现

    前面的文章(飞桨paddlespeech语音唤醒推理C浮点实现)讲了飞桨paddlespeech语音唤醒推理的C浮点实现.但是嵌入式设备通常CPU频率低和memory小,在嵌入式设备上要想流畅的运行语 ...

  2. mysql中使用sql语句统计日志计算每天的访问量

    日志建表语句: CREATE TABLE `syslog` ( `syslogid` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(255) ...

  3. DevOps|研发效能治理:进化史、规模化与治理复杂性

    麻广广@码猿外 研发效能这个词近几年火遍全网,各大企业都加入了研发效能治理的行列,开始梳理企业内部各个团队的研发流程,以期望找到企业降本增效的方向. 抛开政治因素,研发效能治理我们到底是在谈什么呢?从 ...

  4. .Net Core控制台程序读取Json配置文件

    1 添加依赖(可以直接通过nuget包安装 Microsoft.Extensions.Configuration.Json 这个会依赖其他两个会自动安装依赖的) Microsoft.Extension ...

  5. GGTalk 开源即时通讯系统源码剖析之:客户端全局缓存及本地存储

    继上篇<GGTalk 开源即时通讯系统源码剖析之:虚拟数据库>详细介绍了 GGTalk 内置的虚拟的数据库,无需部署真实数据库便能体验GGTalk的全部功能,虚拟数据库将极大地简化服务端的 ...

  6. PE文件结构1

    引言 PE文件格式是Windows操作系统下的可执行文件的格式,包括.exe文件和.dll文件,通过PE文件格式的学习,可以帮助我们更加熟悉有关Windows系统下的逆向分析和PC端病毒的学习,同时P ...

  7. [MAUI]在.NET MAUI中实现可拖拽排序列表

    .NET MAUI 中提供了拖放(drag-drop)手势识别器,允许用户通过拖动手势来移动控件.在这篇文章中,我们将学习如何使用拖放手势识别器来实现可拖拽排序列表.在本例中,列表中显示不同大小的磁贴 ...

  8. React请求机制优化思路

    说起数据加载的机制,有一个绕不开的话题就是前端性能,很多电商门户的首页其实都会做一些垂直的定制优化,比如让请求在页面最早加载,或者在前一个页面就进行预加载等等.随着react18的发布,请求机制这一块 ...

  9. SQL Server 2022新功能概览

    开始之前   本篇文章仅仅是针对SQL Server 2022新推出功能的概览,以及我个人作为用户视角对于每个功能的理解,有些功能会结合一些我的经验进行描述,实际上,SQL Server 2022在引 ...

  10. Unity TextMeshPro 添加中文字体遇见的问题以及解决方案

    前言 按标准官方教程为 Unity TextMeshPro 添加中文字体时出现了各种奇奇怪怪的问题,于是有了这篇随笔. 中文字体解决方案 以下步骤适用于 TextMeshPro 3.0.6. 字符数量 ...