首页 > 其他分享 >小测试:HashSet可以插入重复的元素吗?

小测试:HashSet可以插入重复的元素吗?

时间:2023-11-05 15:13:38浏览次数:30  
标签:HashMap alias HashSet TableFieldDesc 插入 fieldName 测试 data public

  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 + '\'' +
                '}';
    }
}
View Code

  为什么会这样呢?就像测试用例中写的,先插入了一个元素,然后再改变里面的某个值,随后再插入一个改变过之后的值,就重复了。因为hashCode是在插入的时候计算的,而当后续用户改变key的数据值,导致hashCode变更,这时就存在,在对应的slot上,不存在对应元素的情况,所以下次再插入另一个相同元素时,就被认为元素不存在从而插入重复数据了。

  更严重的,当元素数据达到一定的时候,会存在扩容,会重复迁移所有元素,可能还会存在hash重新计算从而将重复的元素变为不重复的情况,就更玄乎了。(不过幸好,HashMap中的扩容不会重新计算hash,它会保留原来的hash,所以重复的元素永远会重复。)

  结语警示:如果想用Set容器去做去重的工作,需要仔细了解其实现原理,而非想当然的认为会去重。能做到不改变key值就尽量避开,甚至不暴露修改数据的方法,即做到对象不可变的效果。从而避免踩坑。

标签:HashMap,alias,HashSet,TableFieldDesc,插入,fieldName,测试,data,public
From: https://www.cnblogs.com/yougewe/p/17810541.html

相关文章

  • 测试规范相关
    测试报告的主要内容:测试项目概述测试过程回顾(时间、工作内容、人员、原因)测试统计分析(用例数、用例覆盖、bug数、修复率、每种级别的bug)测试结果确认(能否上线)测试工作总结与改进 开发模型: APP发布相关: ......
  • 2023.11.4测试
    \[\text{NOIP模拟赛-2023.11.4}\]T1难题设\(f(i)\)表示最小的非\(i\)因数的正整数,求\(\sum\limits_{i=1}^nf(i)\)\(T\leq10^4\),\(1\leqn\leq10^{16}\)考虑计算数\(x\)对\(f(1\simn)\)的贡献通过分析可以发现,\(1\simx\)能筛掉的数的个数为\(n-\dfrac{n}{\ope......
  • 基于FPGA的Lorenz混沌系统verilog开发,含testbench和matlab辅助测试程序
    1.算法运行效果图预览   将vivado的仿真结果导入到matlab显示三维混沌效果:    2.算法运行软件版本vivado2019.2 matlab2022a 3.算法理论概述      洛伦兹混沌系统是一种非线性动力系统,最初由爱德华·洛伦兹(EdwardLorenz)于1963年引入,它的简单方......
  • Jmeter分布式测试的注意事项和常见问题
    Jmeter分布式测试的注意事项和常见问题Jmeter是一款开源的性能测试工具,使用Jmeter进行分布式测试时,也需要注意一些细节和问题,否则可能会影响测试结果的准确性和可靠性。Jmeter分布式测试时需要特别注意的几个方面1.参数化文件的位置和内容如果使用csv文件进行参数化,即通过......
  • word中如何在双栏排版中插入单栏排版内容
    在需要单栏排版的部分,将光标定位到该部分的开头和结尾。分别点击“布局”选项卡,在“页面设置”组中找到“分隔符”选项,然后选择连续分节符(开头与结尾都需要)。这样就在当前位置插入了一个分隔符,建议在Word选项中将显示打开。接下你可以在两个分节符之间单独设置单栏排版。将......
  • 打印机 zebra 斑马 ZT211CN 测试备忘
    条码打印系统  首页-神奇条码标签打印系统(shenqitiaoma.com) 斑马 ZT211CN  ZT211IndustrialPrinterSupport&Downloads|Zebra产品序号(SN): T2J231600121  ,Zebra 通过sn查询产品型号,找到相关手册和问题排除文档。 设置注意事项:1、设置ip后,重启打印机,在......
  • 无涯教程-MongoDB - 插入数据
    在本章中,无涯教程将学习如何在MongoDB集合中插入文档。要将数据插入MongoDB集合,您需要使用MongoDB的insert()或save()方法。Insert-语法insert()命令的基本语法如下:>db.COLLECTION_NAME.insert(document)Insert-示例1>db.mycol.insert({_id:ObjectId(7df78ad8......
  • 基于仿真的飞机ICD工具测试
    ​机载电子系统是飞机完成飞行任务的核心保障之一。从1949年新中国建立至今70余年的发展过程中,随着我国在航空航天领域的投资逐年增多,机载电子系统大致经历了四个发展过程阶段,按照出现的先后顺序进行排序,分别为:1、分立式机载电子系统:由多个不同并且分别独立的子系统采用离散的形......
  • 如何实施符合功能安全及ASPICE要求的模型动态测试 ——TPT Workshop邀请函
    尊敬的女士/先生:2023年3月,北汇信息与诸多工程师相约上海,成功举办了今年第一场TPTWorkshop活动,与大家进行了深入的技术交流。如今,2023年已渐渐步入尾声,我们将在北汇信息上海总部再次举办题为“如何实施符合功能安全及ASPICE要求的模型动态测试”的TPTWorkshop活动,诚邀各位新老......
  • MySQL使用函数、存储过程实现:向数据表快速插入大量测试数据
    实现过程创建表CREATETABLE`user`( `id`INT(11)NOTNULLAUTO_INCREMENT, `name`VARCHAR(20)DEFAULTNULL, `age`INT(3)DEFAULTNULL, `pwd`VARCHAR(20)DEFAULTNULL, `address`VARCHAR(30)DEFAULTNULL, PRIMARYKEY(`id`))ENGINE=INNODBAUTO_INCREMENT=......