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. Node版本更新及切换

    Node版本升级 # 清除npm缓存 npm cache clean -f # n模块是专门用来管理nodejs的版本,安装n模块 npm install -g n 1.Windows 由于n命令是在 ...

  2. MAUI中如何打开应用商店应用详情页

    //打开本应用的应用商店详情页 public Task<bool> OpenStoreAppDetails() { return OpenStoreAppDetails(AppInfo.P ...

  3. 行行AI人才直播第14期:【国内第二波人工智能进入者、连续创业者】土豆《土豆利用GPT成功融资两次的提示词和故事》

    行行AI人才(海南行行智能科技有限公司)是博客园和顺顺智慧共同运营的AI行业人才全生命周期服务平台. 此刻,ChatGPT的火热程度已经无需多言.一时间,追逐大模型成了国内AI行业的标准动作,&quo ...

  4. 注意!JAVA中的值传递

    前言:今天在解决一个问题时,程序总是不能输出正确值,分析逻辑思路没问题后,发现原来是由于函数传递导致了这个情况. LeetCode 113 问题:给你二叉树的根节点root和一个整数目标和target ...

  5. 【go语言】3.1.2 接口的定义和实现

    在 Go 中,接口是一种抽象类型,用来描述其他类型应该有哪些方法.它定义了一组方法,但没有实现.这些方法由其他类型实现. 接口的定义 接口定义的格式如下: type InterfaceName int ...

  6. 仅三天,我用 GPT-4 生成了性能全网第一的 Golang Worker Pool,轻松打败 GitHub 万星项目

    目录 1. 我写了一个超牛的开源项目 1.1 你看看这性能 1.2 你看看这功能 1.3 你猜我这一百天都经历了啥 2. 你有多久没写并发程序了? 3. 问:一个 Worker Pool 程序需要包含 ...

  7. 【Azure K8S | AKS】在AKS集群中创建 PVC(PersistentVolumeClaim)和 PV(PersistentVolume) 示例

    问题描述 在AKS集群中创建 PVC(PersistentVolumeClaim)和 PV(PersistentVolume) 示例 问题解答 在Azure Kubernetes Service(AK ...

  8. [golang]使用mTLS双向加密认证http通信

    前言 假设一个场景,服务端部署在内网,客户端需要通过暴露在公网的nginx与服务端进行通信.为了避免在公网进行 http 明文通信造成的信息泄露,nginx与客户端之间的通信应当使用 https 协议 ...

  9. 性能监控平台搭建(grafana+telegraf+influxdb) 及 配置 jmeter后端监听

    搞性能测试,可以搭建Grafana+Telegraf+InfluxDB 监控平台,监控服务器资源使用率.jmeter性能测试结果等. telegraf: 是一个用 Go 编写的代理程序,可收集系统和服 ...

  10. IDA的使用2

    IDA的使用2 string类型的选择 Rename 要注意如果再namelist和public name里面是不能重名 操作数 这个主要和开发结合精密, change sign-改变符号 bitwi ...