当前位置: 首页 > article >正文

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

http://www.kler.cn/a/374138.html

相关文章:

  • 微信小程序服务通知
  • 【小白学机器学习29】 概率统计与图形 ( hist, bar, pie , box ,scatter ,line)
  • windows系统命令讲解
  • Spring 框架中常见的注解(Spring、SpringMVC、SpringBoot)
  • 字符脱敏工具类
  • 串口通信以及USART和UART以及IIC和SPI-学习笔记
  • 【skywalking】监控 Spring Cloud Gateway 数据
  • flask框架用法介绍(一)
  • 从零学习大模型(十)-----剪枝基本概念
  • 【SSE】前端vue3使用SSE,EventSource携带请求头
  • H2 Database IDEA 源码 DEBUG 环境搭建
  • VuePress文档初始化请求过多问题探讨
  • 设计模式07-结构型模式(装饰模式/外观模式/代理模式/Java)
  • HTB:Cicada[WriteUP]
  • 【Linux-进程间通信】匿名管道的应用-进程池实现+命名管道的应用ClientServer通信
  • 手机收银云进销存管理软件,商品档案Excel格式批量导入导出,一键导入Excel的商品档案
  • 跨可用区的集群k8s的基本操作和配置理解
  • 【开源免费】基于SpringBoot+Vue.JS网上订餐系统(JAVA毕业设计)
  • SQL 通用数据类型
  • 【数据库设计】规范设计理论之数据依赖的公理系统(1)
  • 百数功能更新——表单提交支持跳转到外部链接并支持传参
  • ssm基于WEB的人事档案管理系统的设计与实现+jsp
  • 【c++ gtest】使用谷歌提供的gtest和抖音豆包提供的AI大模型来对代码中的函数进行测试
  • WPF自定义日历控件Calendar 的方法
  • STM32F103C8T6学习笔记2--LED流水灯与蜂鸣器
  • 树莓派5调取本地视频