场景
HashSet
HashSet 基于 HashMap 来实现的,是一个不允许有重复元素的集合。HashSet 允许有 null 值。
HashSet 是无序的,即不会记录插入的顺序。 HashSet 不是线程安全的, 如果多个线程尝试同时修改 HashSet,
则最终结果是不确定的。
在Java语言中,Set数据结构可以用于对象排重,常见的Set类有HashSet、LinkedHashSet等。
比如:
代码中使用HashSet数据结构,为了避免城市数据重复,对读取的城市数据进行强制排重。
这里的数据源从csv文件中读取。
注:
博客:
https://blog.csdn.net/badao_liumang_qizhi
读取csv文件内容的方式有很多种,这里使用apache的commons-csv的方式。
首先项目中引入依赖
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-csv</artifactId>
<version>1.7</version>
</dependency>
然后新建读取文件和解析数据的工具类,这里是读取城市数据,所以是
CityHelper
import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVParser; import org.apache.commons.csv.CSVRecord; import java.io.*; import java.util.*; public class CityHelper { public static Collection<City> readCities(String fileName){ try ( FileInputStream stream = new FileInputStream(fileName); InputStreamReader reader = new InputStreamReader(stream,"GBK"); CSVParser parser = new CSVParser(reader, CSVFormat.DEFAULT.withHeader()) ) { Set<City> citySet = new HashSet<>(1024); Iterator<CSVRecord> iterator = parser.iterator(); while (iterator.hasNext()){ citySet.add(parseCity(iterator.next())); } return citySet; } catch (Exception e) { e.printStackTrace(); } return Collections.emptySet(); } /** * 解析城市 * @param record * @return */ private static City parseCity(CSVRecord record){ City city = new City(); city.setCode(record.get(0)); city.setName(record.get(1)); return city; } }
然后新建City实体类
public class City{ private String code; private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getCode() { return code; } public void setCode(String code) { this.code = code; } }
下面进行业务测试
Collection<City> cities = CityHelper.readCities("D:\\test.csv"); System.out.println(cities);
查看输出结果
竟然没有实现去重效果。
这里注意踩坑:
原因分析:
当向集合Set中增加对象时,首先集合计算要增加对象的hashCode,根据该值来得到一个位置用来存放当前对象。
如在该位置没有一个对象存在的话,那么集合Set认为该对象在集合中不存在,直接增加进去。
如果在该位置有一个对象存在的话,接着将准备增加到集合中的对象与该位置上的对象进行equals方法比较:
如果该equals方法返回false,那么集合认为集合中不存在该对象,就把该对象放在这个对象之后;
如果equals方法返回true,那么就认为集合中已经存在该对象了,就不会再将该对象增加到集合中了。
所以,在哈希表中判断两个元素是否重复要使用到hashCode方法和equals方法。
hashCode方法决定数据在表中的存储位置,而equals方法判断表中是否存在相同的数据。
分析上面的问题,由于没有重写City类的hashCode方法和equals方法,就会采用Object类的hashCode方法和equals方法。
Object类的hashCode方法是一个本地方法,返回的是对象地址;Object类的equals方法只比较对象是否相等。
所以,对于两条完全一样的北京数据,由于在解析时初始化了不同的City对象,导致hashCode方法和equals方法值都不一样,
必然被Set认为是不同的对象,所以没有进行排重。
解决Java中使用HashSet进行排重时不生效问题
解决:重写City类的hashCode方法和equals方法
这里我们再新建一个City2实体类并修改如下
import java.util.Objects; /** * 城市类 */ public class City2 { private String code; private String name; /** * 判断相等 * @param obj * @return */ @Override public boolean equals(Object obj) { if(obj == this){ return true; } if(Objects.isNull(obj)){ return false; } if(obj.getClass() != this.getClass()){ return false; } return Objects.equals(this.code,((City2)obj).getCode()); } /** * 哈希编码 * @return */ @Override public int hashCode() { return Objects.hashCode(this.code); } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getCode() { return code; } public void setCode(String code) { this.code = code; } }
这里重写的equals方法中指定了自己需要的逻辑,根据code字段判断,如果相等则认为相同。
然后再重新调用并查看结果
总结:
1、当确定解析的城市数据唯一时,就没有必要进行排重操作,可以直接使用List来存储。
2、确定解析的城市数据不唯一时,需要按照城市名称进行排重操作,可以直接使用Map进行存储。
为什么不建议实现City类的hashCode方法,再采用HashSet来实现排重呢?
首先,不希望把业务逻辑放在模型DO类中;其次,把排重字段放在代码中,便于代码的阅读、理解和维护。
3、不重写hashCode方法和equals方法的自定义类不应该在Set中使用。