Java实现数据去重的几种方案及其去重原理

背景

之前就有写过实现根据对象的某个字段去重的一个方案,利用Java8的流,不过实际上使用的还是TreeSet。
不过,当时对于这个去重原理没有过多深究,只是想着Set结构天生就是去重的,今天再次用到,稍微了解一下原理。

1.TreeSet去重

TreeSet底层是基于红黑树实现的,这意味着存在TreeSet的数据是有序的。

这个排序规则分两种情况:
1.如果在创建TreeSet的时候使用的是无参构造函数,则要求添加到TreeSet中的元素必须实现Comparable接口,并重写compareTo方法,否则添加元素的时候会报如下错误。
在这里插入图片描述
实现根据对象的特定字段去重,不推荐使用该方案。因为一般情况下,compareTo判断相等的对象,equals判断最好也要相等。

2.如果在创建TreeSet的时候传入了一个比较器,则该TreeSet通过传入的比较器去对数据进行排序,也就是传入的元素不需要实现Comparable接口也可以。

TreeSet<User> set= new TreeSet<>(Comparator.comparing(User::getAge));

通过源码层面去分析,TreeSet在调用构造函数的时候,底层都是创建TreeMap。不同的是有比较器作为入参的会将比较器存起来,如下。
在这里插入图片描述

有参构造函数创建的TreeSet,则使用传入的比较器去比较排序。
在这里插入图片描述

而无参构造函数的TreeSet在添加新元素时通过所添加元素的compareTo方法去比较:(返回0则替换值)
在这里插入图片描述
最前面的代码例子就属于情况2,逻辑上系统会根据用户的age去对数据进行排序,在使用比较器比较返回0的时候就认为两个对象是一样的,后一个就会替代前一个。这也意味着如果根据比较器逻辑有重复的数据,最终只会保留最后出现的那一个数据。
也就是说TreeSet里面不会存在age相同的对象。

使用有参的TreeSet+流是比较简洁的一种数据去重方案。(推荐)

public class Test {
	public static void main(String[] args) {
		List<User> list=new ArrayList<>();
		List<User> distinctList=list.stream().collect(Collectors.collectingAndThen(Collectors.toCollection(()->new TreeSet<>(Comparator.comparing(User::getAge))),ArrayList::new));
	}
}

2.HashMap去重

HashMap,从名字其实就可以看出,他是基于哈希进行数据存储的,而上面TreeSet很明显就是基于树结构。
HashMap底层是数组+链表+红黑树,要求加入到HashMap的元素必须要重写hashcode和equals方法。
因为在进行比较的时候会先通过对key的hashcode进行取模获得数组下标。然后访问数组获得链表或者树的头结点,之后遍历,通过对象地址比较,hashcode比较,equals比较等最终判断是否是同一个对象。(判断对象相同要求hashcode一样,并且equals比较也要返回true)

可以通过创建一个HashMap,使用想要作为去重标志的多个字段拼接起来作为key,value存储对象,从而实现数据去重。

public class Test {
	public static void main(String[] args) {
		List<User> list=new ArrayList<>();
		Map<Integer,User> userMap=new HashMap<>();
		for (User user:list){
			userMap.put(user.getAge(),user);
		}
		Collection<User> values = userMap.values();
	}
}

3.使用循环+逻辑判断去重

就是直接遍历数据,把需要去重的条件记录下来,然后用另一个列表来存储去重后的数据。
这是最简单易懂的写法,通俗易懂,不过也比较繁琐,一些简单的根据某些字段去重,使用上面的方法会更简洁。

public class Test {
	public static void main(String[] args) {
		List<User> list=new ArrayList<>();
		List<User> distinctUserList=new ArrayList<>();
		HashSet<Integer> ageSet=new HashSet<>();
		for (User user:list){
			if(!ageSet.contains(user.getAge())){
				distinctUserList.add(user);
				ageSet.add(user.getAge());
			}
		}
	}
}

结论

上述方案其实可以大概归纳为使用Tree结构和使用Hash结构的不同。因为Set在底层上本质用的还是对应的Map。
树结构通过比较器判断元素是否重复,hash通过hashcode和equals比较是否重复。

如果了解流的使用,推荐使用有参的TreeSet+流操作方案实现去重。(推荐)

public class Test {
	public static void main(String[] args) {
		List<User> list=new ArrayList<>();
		List<User> distinctList=list.stream().collect(Collectors.collectingAndThen(Collectors.toCollection(()->new TreeSet<>(Comparator.comparing(User::getAge))),ArrayList::new));
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值