问题

当下互联网技术成熟,越来越多的趋向去中心化、分布式、流计算,使得很多以前在数据库侧做的事情放到了Java端。今天有人问道,如果数据库字段没有索引,那么应该如何根据该字段去重?大家都一致认为用Java来做,但怎么做呢?

解答

忽然想起以前写过list去重的文章,找出来一看。做法就是将list中对象的hashcode和equals方法重写,然后丢到HashSet里,然后取出来。这是最初刚学Java的时候像被字典一样背写出来的答案。就比如面试,面过号称做了3年Java的人,问Set和HashMap的区别可以背出来,问如何实现就不知道了。也就是说,初学者只背特性。但真正在项目中使用的时候你需要确保一下是不是真的这样。因为背书没用,只能相信结果。你需要知道HashSet如何帮我做到去重了。换个思路,不用HashSet可以去重吗?最简单,最直接的办法不就是每次都拿着和历史数据比较,都不相同则插入队尾。而HashSet只是加速了这个过程而已。

首先,给出我们要排序的对象User

@Data

@Builder

@AllArgsConstructor

public class User {

private Integer id;

private String name;

}

List users = Lists.newArrayList(

new User(1, "a"),

new User(1, "b"),

new User(2, "b"),

new User(1, "a"));

目标是取出id不重复的user,为了防止扯皮,给个规则,只要任意取出id唯一的数据即可,不用拘泥id相同时算哪个。

用最直观的办法

这个办法就是用一个空list存放遍历后的数据。

@Test

public void dis1() {

List result = new LinkedList<>();

for (User user : users) {

boolean b = result.stream().anyMatch(u -> u.getId().equals(user.getId()));

if (!b) {

result.add(user);

}

}

System.out.println(result);

}

用HashSet

背过特性的都知道HashSet可以去重,那么是如何去重的呢? 再深入一点的背过根据hashcode和equals方法。那么如何根据这两个做到的呢?没有看过源码的人是无法继续的,面试也就到此结束了。

事实上,HashSet是由HashMap来实现的(没有看过源码的时候曾经一直直观的以为HashMap的key是HashSet来实现的,恰恰相反)。这里不展开叙述,只要看HashSet的构造方法和add方法就能理解了。

public HashSet() {

map = new HashMap<>();

}

/**

* 显然,存在则返回false,不存在的返回true

*/

public boolean add(E e) {

return map.put(e, PRESENT)==null;

}

那么,由此也可以看出HashSet的去重复就是根据HashMap实现的,而HashMap的实现又完全依赖于hashcode和equals方法。这下就彻底打通了,想用HashSet就必须看好自己的这两个方法。

在本题目中,要根据id去重,那么,我们的比较依据就是id了。修改如下:

@Override

public boolean equals(Object o) {

if (this == o) {

return true;

}

if (o == null || getClass() != o.getClass()) {

return false;

}

User user = (User) o;

return Objects.equals(id, user.id);

}

@Override

public int hashCode() {

return Objects.hash(id);

}

//hashcode

result = 31 * result + (element == null ? 0 : element.hashCode());

其中, Objects调用Arrays的hashcode,内容如上述所示。乘以31等于x<<5-x。

最终实现如下:

@Test

public void dis2() {

Set result = new HashSet<>(users);

System.out.println(result);

}

使用Java的Stream去重

回到最初的问题,之所以提这个问题是因为想要将数据库侧去重拿到Java端,那么数据量可能比较大,比如10w条。对于大数据,采用Stream相关函数是最简单的了。正好Stream也提供了distinct函数。那么应该怎么用呢?

users.parallelStream().distinct().forEach(System.out::println);

没看到用lambda当作参数,也就是没有提供自定义条件。幸好Javadoc标注了去重标准:

Returns a stream consisting of the distinct elements

(according to {@link Object#equals(Object)}) of this stream.

我们知道,也必须背过这样一个准则:equals返回true的时候,hashcode的返回值必须相同. 这个在背的时候略微有些逻辑混乱,但只要了解了HashMap的实现方式就不会觉得拗口了。HashMap先根据hashcode方法定位,再比较equals方法。

所以,要使用distinct来实现去重,必须重写hashcode和equals方法,除非你使用默认的。

那么,究竟为啥要这么做?点进去看一眼实现。

Node reduce(PipelineHelper helper, Spliterator spliterator) {

// If the stream is SORTED then it should also be ORDERED so the following will also

// preserve the sort order

TerminalOp> reduceOp

= ReduceOps.>makeRef(LinkedHashSet::new, LinkedHashSet::add,

LinkedHashSet::addAll);

return Nodes.node(reduceOp.evaluateParallel(helper, spliterator));

}

内部是用reduce实现的啊,想到reduce,瞬间想到一种自己实现distinctBykey的方法。我只要用reduce,计算部分就是把Stream的元素拿出来和我自己内置的一个HashMap比较,有则跳过,没有则放进去。其实,思路还是最开始的那个最直白的方法。

@Test

public void dis3() {

users.parallelStream().filter(distinctByKey(User::getId))

.forEach(System.out::println);

}

public static Predicate distinctByKey(Function super T, ?> keyExtractor) {

Set seen = ConcurrentHashMap.newKeySet();

return t -> seen.add(keyExtractor.apply(t));

}

当然,如果是并行stream,则取出来的不一定是第一个,而是随机的。

上述方法是至今发现最好的,无侵入性的。但如果非要用distinct。只能像HashSet那个方法一样重写hashcode和equals。

小结

会不会用这些东西,你只能去自己练习过,不然到了真正要用的时候很难一下子就拿出来,不然就冒险用。而若真的想大胆使用,了解规则和实现原理也是必须的。比如,LinkedHashSet和HashSet的实现有何不同。

附上贼简单的LinkedHashSet源码:

public class LinkedHashSet

extends HashSet

implements Set, Cloneable, java.io.Serializable {

private static final long serialVersionUID = -2851667679971038690L;

public LinkedHashSet(int initialCapacity, float loadFactor) {

super(initialCapacity, loadFactor, true);

}

public LinkedHashSet(int initialCapacity) {

super(initialCapacity, .75f, true);

}

public LinkedHashSet() {

super(16, .75f, true);

}

public LinkedHashSet(Collection extends E> c) {

super(Math.max(2*c.size(), 11), .75f, true);

addAll(c);

}

@Override

public Spliterator spliterator() {

return Spliterators.spliterator(this, Spliterator.DISTINCT | Spliterator.ORDERED);

}

}

java steam 去重_Java中对List去重, Stream去重相关推荐

  1. java list 比较_Java中List的五种去重方法及效率对比,你都用对了吗?

    01.使用两个for循环实现List去重(有序) /**使用两个for循环实现List去重(有序) * * @param list * */ public static List removeDupl ...

  2. java list 效率_Java中5种List的去重方法及它们的效率对比,你用对了吗?

    01.使用两个for循环实现List去重(有序) /**使用两个for循环实现List去重(有序) * * @param list * */ public static List removeDupl ...

  3. java遍历栈_Java中使用StackWalker和Stream API进行堆栈遍历

    1.Java 9以前堆栈遍历到目前为止,官方解决方案是获取当前线程并调用其getStackTrace()方法: StackTraceElement[] stackTraceElements = Thr ...

  4. java steam 去重_Java中对List去重 Stream去重的解决方法

    问题 当下互联网技术成熟,越来越多的趋向去中心化.分布式.流计算,使得很多以前在数据库侧做的事情放到了Java端.今天有人问道,如果数据库字段没有索引,那么应该如何根据该字段去重?大家都一致认为用Ja ...

  5. set列表对象去重_java中List对象列表去重或取出以及排序

    面试碰到几次list的去重和排序.下面介绍一种做法: 1. list去重 1.1 实体类Student List容量10k以上,要求去重复.这里Student的重复标准是属性相同,因此需要重写equa ...

  6. python对excel某一列去重-python中怎么对dataframe列去重

    python中对已经生成的Series,怎样组合成DataFrame 如 a = Series([1,2,3]) b = Series([2,3,4]) 怎样将a b组合成一个DataFzip函数接受 ...

  7. java mod %区别_Java中 % 与Math.floorMod() 区别详解

    %为取余(rem),Math.floorMod()为取模(mod) 取余取模有什么区别呢? 对于整型数a,b来说,取模运算或者取余运算的方法都是: 1.求 整数商: c = a/b; 2.计算模或者余 ...

  8. java show过时_Java中show() 方法被那个方法代替了? java编程 显示类中信

    你说的show是swing里的吧,在老版本中Component这个超类确实有show这个方法,而且这个方法也相当有用,使一个窗口可见,并放到最前面.在jdk5.0中阻止了这个方法,普遍用setVisi ...

  9. java io系统_java中的io系统详解

    Java 流在处理上分为字符流和字节流.字符流处理的单元为 2 个字节的 Unicode 字符,分别操作字符.字符数组或字符串,而字节流处理单元为 1 个字节,操作字节和字节数组. Java 内用 U ...

最新文章

  1. 学习《Linux设备模型浅析之设备篇》笔记(深挖二)
  2. Zabbix监控网络设备日志文件及字段报警
  3. 学习:Java泛型之一
  4. T-SQL 根据年月日创建DateTime
  5. 小程序--显示图形效果
  6. 矩阵交换行(信息学奥赛一本通-T1119)
  7. 大数据技术周报第 003 期
  8. Junit4单元测试报错
  9. 解决service iptables save出错please try to use systemctl.
  10. 同一个行业为什么股票差异大?
  11. Python实现决策树
  12. 69期-Java SE-004_循环、数组-001-002
  13. 前端代码——前端代码规范(含html、css、javascript、vue等)
  14. 微软笔试题2:403 Forbidden
  15. ubuntu安装配置搜狗拼音输入法
  16. 相比REG007 不仅免费还好用 的手机号绑定查询工具
  17. dk编程真好玩 python_皮皮学编程(1):从Scratch到Python
  18. 互联网金融想革中国金融体系的命?
  19. .obj 和 .mtl文件格式
  20. tcpdump如何避免dropped by kernel

热门文章

  1. c# 后台 添加datable 数据
  2. 『.NET Core CLI工具文档』(二).NET Core 工具遥测(应用信息收集)
  3. 计算机组成原理——Cache与主存的地址映射
  4. idea springmvc项目搭建_ssm框架整合搭建流程 - 小白小承
  5. stm32f103移植到f0_STM32F042替换STM32F103
  6. 简述div标签和span标签的不同_div与span区别及用法
  7. 吉林大学计算机英语成绩,吉林大学复试出结果,初试分差84分,复试成绩却相差无几!...
  8. html代码type,HTML中type是什么意思
  9. fir c语言程序,fir.rar fir的c语言程序 - 下载 - 搜珍网
  10. linux 检测mysql链接_MySQL笔记