返回顶部
首页 > 资讯 > 后端开发 > Python >Java集合去重导致的线上问题
  • 902
分享到

Java集合去重导致的线上问题

2024-04-02 19:04:59 902人浏览 安东尼

Python 官方文档:入门教程 => 点击学习

摘要

目录前言:HashSet源码性能对比前言: 在工作中一次排查慢接口时,查到了一个函数耗时较长,最终定位到是通过 List 去重导致的。 由于测试环境还有线上早期数据较少,这个接口的性

前言:

在工作中一次排查慢接口时,查到了一个函数耗时较长,最终定位到是通过 List 去重导致的。

由于测试环境还有线上早期数据较少,这个接口的性能问题没有引起较大关注,后面频繁超时,才引起重视。

之前看《阿里巴巴Java开发手册》里面有这样一段描述:

如果需要这本书资源的网上下载也行,私聊我发你也行

今天我就结合源码聊聊Set是怎样保证数据的唯一性的,为什么两种去重方式性能差距这么大

HashSet源码

先看看类注释:

image.png

看类注释上,我们可以得到的信息有:

  • 底层实现基于 HashMap,所以迭代时不能保证按照插入顺序,或者其它顺序进行迭代;
  • add、remove、contanins、size 等方法的耗时性能,是不会随着数据量的增加而增加的,这个主要跟 HashMap 底层的数组数据结构有关,不管数据量多大,不考虑 hash 冲突的情况下,时间复杂度都是 O (1);
  • 线程安全的,如果需要安全请自行加,或者使用 Collections.synchronizedSet;
  • 迭代过程中,如果数据结构被改变,会快速失败的,会抛出 ConcurrentModificationException 异常。

刚才是从类注释中看到,HashSet 的实现是基于 HashMap 的,在 Java 中,要基于基础类进行创新实现,有两种办法:

  • 继承基础类,覆写基础类的方法,比如说继承 HashMap , 覆写其 add 的方法;
  • 组合基础类,通过调用基础类的方法,来复用基础类的能力。

HashSet 使用的就是组合 HashMap,其优点如下:

继承表示父子类是同一个事物,而 Set 和 Map 本来就是想表达两种事物,所以继承不妥,而且 Java 语法限制,子类只能继承一个父类,后续难以扩展。

组合更加灵活,可以任意的组合现有的基础类,并且可以在基础类方法的基础上进行扩展、编排等,而且方法命名可以任意命名,无需和基础类的方法名称保持一致。

组合就是把 HashMap 当作自己的一个局部变量,以下是 HashSet 的组合实现:

// 把 HashMap 组合进来,key 是 Hashset 的 key,value 是下面的 PRESENT
private transient HashMap<E,Object> map;
// HashMap 中的 value
private static final Object PRESENT = new Object();

从这两行代码中,我们可以看出两点:

我们在使用 HashSet 时,比如 add 方法,只有一个入参,但组合的 Map 的 add 方法却有 key,value 两个入参,相对应上 Map 的 key 就是我们 add 的入参,value 就是第二行代码中的 PRESENT,此处设计非常巧妙,用一个默认值 PRESENT 来代替 Map 的 Value;

我们再来看看add方法:

public boolean add(E e) {
    // 直接使用 HashMap 的 put 方法,进行一些简单的逻辑判断
    return map.put(e, PRESENT)==null;
}

我们进入更底层源码java.util.HashMap#put:

public V put(K key, V value) { 
 return putVal(hash(key), key, value, false, true); 
}

再瞧瞧hash方法:

static final int hash(Object key) { 
 int h; 
 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); 
}

可以看到如果 key 为 null ,哈希值为 0,否则将 key 通过自身hashCode函数计算的的哈希值和其右移 16 位进行异或运算得到最终的哈希值。

我们再回到 java.util.HashMap#putVal中:

image.png

在 java.util.HashMap#putVal中,直接通过 (n - 1) & hash 来得到当前元素在节点数组中的位 置。如果不存在,直接构造新节点并存储到该节点数组的对应位置。如果存在,则通过下面逻 辑:

p.hash == hash &amp;&amp; ((k = p.key) == key || (key != null &amp;&amp; key.equals(k)))
复制代码

来判断元素是否相等。

如果相等则用新值替换旧值,否则添加红黑树节点或者链表节点。

总结:通过HashMap的key的唯一性来保证的HashSet元素的唯一性。

最后再看看:

《阿里巴巴Java开发手册》里面还有这样一段描述:

 

image.png

到现在是不是明白了,这个2,3点的原因

性能对比

其实HashSet和ArrayList去重性能差异的核心在于contains函数性能对比。

我们分别查看java.util.HashSet#containsjava.util.ArrayList#contains的实现。

java.util.HashSet#contains源码:

public boolean contains(Object o) {
        return map.containsKey(o);
    }

最终也是通过HashMap判断的

如果 hash 冲突不是极其严重(大多数都没怎么有哈希冲突),n 个元素依次判断并插入到 Set 的时间复杂度接近于 O (n),查找的复杂度是O(1)。

接下来我们看java.util.ArrayList#contains的源码:

public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }
public int indexOf(Object o) {
        if (o == null) {
            for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = 0; i < size; i++)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }--pre>

发现其核心逻辑为:如果为 null, 则遍历整个集合判断是否有 null 元素;否则遍历整个列表,通 过 o.equals(当前遍历到的元素) 判断与当前元素是否相等,相等则返回当前循环的索引

所以, java.util.ArrayList#contains判断并插入n个元素到 Set 的时间复杂度接近于O (n^2),查找的复杂度是O(n)。

因此,通过时间复杂度的比较,性能差距就不言而喻了。

我们分别将两个时间复杂度函数进行作图, 两者增速对比非常明显:

image.png

如果数据量不大时采用 List 去重勉强可以接受,但是数据量增大后,接口响应时间会超慢,这 是难以忍受的,甚至造成大量线程阻塞引发故障。

到此这篇关于Java集合去重导致的线上问题的文章就介绍到这了,更多相关Java集合去重内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: Java集合去重导致的线上问题

本文链接: https://lsjlt.com/news/146785.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • Java集合去重导致的线上问题
    目录前言:HashSet源码性能对比前言: 在工作中一次排查慢接口时,查到了一个函数耗时较长,最终定位到是通过 List 去重导致的。 由于测试环境还有线上早期数据较少,这个接口的性...
    99+
    2024-04-02
  • java中List集合去重的方式有哪些
    去重方式:一、通过set集合的特性,集合元素的唯一性public static List heavyListMethod01(List list){ Set set=new HashSet(list); //...
    99+
    2014-10-12
    java教程 java List 去重 方式
  • MySQL 表名中的下划线会导致问题吗?
    不,MySQL 表名中的下划线不会出现任何问题。 MySQL 表名中的破折号会出现问题。这是一个演示,表名中的下划线没有任何问题 -_StudentTrackerDemo让我们在创建表时看到同样的情况。创建表的查询如下 -mysql>...
    99+
    2023-10-22
  • 导致服务器重启的硬件问题有哪些
    导致服务器重启的硬件问题有:1、增添新硬件时超出电源输入的额定功率,导致服务器重启;2、CPU资源占用过高,导致服务器重启;3、刻录盘或硬盘内部电路损坏,导致服务器在运行过程中突然重启;4、主板出现故障或与RESET相关的电路出现故障,如插...
    99+
    2024-04-02
  • 导致服务器重启的软件问题有哪些
    导致服务器重启的软件问题有:1、病毒感染,导致服务器重启,需要清除病毒或重装系统;2、同时启动两个或以上的软件,出现软件冲突,导致服务器重启;3、因操作失误或系统自身问题,导致系统文件被损坏,系统启动时无法加载系统文件而强迫重启。具体内容如...
    99+
    2024-04-02
  • 导致服务器重启的软件问题是什么
    导致服务器重启的软件问题可能包括: 软件的bug或故障:某些软件可能存在bug或故障,导致服务器出现异常并最终需要重启以恢复正常...
    99+
    2024-04-24
    服务器
  • java中怎么去掉List集合中重复的元素
    本篇内容介绍了“java中怎么去掉List集合中重复的元素”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!&...
    99+
    2024-04-02
  • 多线程update导致的mysql死锁问题处理方法
    最近想起之前处理过的一个mysql 死锁问题,是在高并发下update批量更新导致的,这里探讨一下发生的原因,以及解决办法; 发生死锁的sql语句如下,其中where条件后的字段是有复合索引的。 update t_push_mes...
    99+
    2023-09-06
    数据库 java 开发语言 mysql死锁问题 mysql
  • Java中Lombok @Value注解导致的variable not been initialized问题
    目录背景 解决背景 想要修改一个POJO类,在其中增加一个字段,但是增加以后就开始报错: 该类已经存在一个构造函数,为了不破坏该类原来的使用方式,于是重新写了一个构造方...
    99+
    2024-04-02
  • 导致服务器频繁重启的硬件问题有哪些
    导致服务器频繁重启的硬件问题有:1、增添新硬件后功率超出电源输入的额定功率,导致服务器重启;2、CUP资源占用过高,导致服务器超负荷运行,服务器重启;3、刻录盘或硬盘内部电路损坏,导致服务器在运行过程中突然重启;4、主板出现故障,导致服务器...
    99+
    2024-04-02
  • 导致服务器频繁重启的软件问题有哪些
    导致服务器频繁重启的软件问题有:1、病毒,系统被感染病毒时,会提示将在1分钟后自动重启;2、软件冲突,同时启动两个或以上的软件时,系统会发生重启;3、系统文件损坏或驱动问题,导致系统文件被损坏,服务器重启。具体内容如下:病毒病毒一直都是服务...
    99+
    2024-04-02
  • nginx上传请求体太大导致的问题怎么解决
    这篇文章主要讲解了“nginx上传请求体太大导致的问题怎么解决”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“nginx上传请求体太大导致的问题怎么解决”吧!1.原因GitLab  ...
    99+
    2023-06-04
  • Mybatis返回map集合时,列的顺序与select不一致问题
    目录返回map集合,列的顺序与select不一致mybatis中返回map集合问题1.mapper.xml中写一个查询返回map的sql2.mapper.java 对应接收...
    99+
    2024-04-02
  • C#中常见的并发集合和线程安全问题
    C#中常见的并发集合和线程安全问题在C#编程中,处理并发操作是非常常见的需求。当多个线程同时访问和修改同一数据时,就会出现线程安全问题。为了解决这个问题,C#提供了一些并发集合和线程安全的机制。本文将介绍C#中常见的并发集合以及如何处理线程...
    99+
    2023-10-22
    集合 并发 线程安全
  • 解决SpringBoot集成Eureka导致返回结果由json变为xml的问题
    SpringBoot集成Eureka导致返回结果由json变为xml 解决方案 在请求的Mapping上加上 produces = { “application/json;cha...
    99+
    2024-04-02
  • windows集线器端口上的电涌问题怎么解决
    这篇文章主要介绍“windows集线器端口上的电涌问题怎么解决”,在日常操作中,相信很多人在windows集线器端口上的电涌问题怎么解决问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大...
    99+
    2022-11-30
    windows
  • 解决使用stream将list转map时,key重复导致报错的问题
    要将List对象集合转为map集合,可以通过stream流的形式快速实现转换: //三个Users对象组成一个List集合 List<Users> list = ne...
    99+
    2024-04-02
  • JAVA8新特性stream流收集为Map,value为null导致空指针的问题
    jdk8 新特性stream深受喜爱,平时使用比较多,其中有: Map collect2 =  list.stream().collect(Collectors.toMap(Book::getName, Book::getIdNO,(p...
    99+
    2023-09-24
    java 开发语言
  • 如何解决JVMFullGC引发严重线上事故的问题
    今天就跟大家聊聊有关如何解决JVMFullGC引发严重线上事故的问题,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。一、业务场景介绍先简单说说线上生产系统的一个背景,因为仅仅是文章作为...
    99+
    2023-06-03
  • 如何解决Java中Lombok @Value注解导致的variable not been initialized问题
    本篇内容介绍了“如何解决Java中Lombok @Value注解导致的variable not been initialized问题”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情...
    99+
    2023-06-20
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作