最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java集合去重導(dǎo)致的線上問題

 更新時間:2022年04月21日 14:00:58   作者:程序員段飛?  
這篇文章主要介紹了Java集合去重導(dǎo)致的線上問題,根據(jù)主題提出Set是怎樣保證數(shù)據(jù)的唯一性的,為什么兩種去重方式性能差距這么大的問題,下文解決思路需要的小伙伴可以參考一下

前言:

在工作中一次排查慢接口時,查到了一個函數(shù)耗時較長,最終定位到是通過 List 去重導(dǎo)致的。

由于測試環(huán)境還有線上早期數(shù)據(jù)較少,這個接口的性能問題沒有引起較大關(guān)注,后面頻繁超時,才引起重視。

之前看《阿里巴巴Java開發(fā)手冊》里面有這樣一段描述:

如果需要這本書資源的網(wǎng)上下載也行,私聊我發(fā)你也行

今天我就結(jié)合源碼聊聊Set是怎樣保證數(shù)據(jù)的唯一性的,為什么兩種去重方式性能差距這么大

HashSet源碼

先看看類注釋:

image.png

看類注釋上,我們可以得到的信息有:

  • 底層實現(xiàn)基于 HashMap,所以迭代時不能保證按照插入順序,或者其它順序進行迭代;
  • add、remove、contanins、size 等方法的耗時性能,是不會隨著數(shù)據(jù)量的增加而增加的,這個主要跟 HashMap 底層的數(shù)組數(shù)據(jù)結(jié)構(gòu)有關(guān),不管數(shù)據(jù)量多大,不考慮 hash 沖突的情況下,時間復(fù)雜度都是 O (1);
  • 線程不安全的,如果需要安全請自行加鎖,或者使用 Collections.synchronizedSet;
  • 迭代過程中,如果數(shù)據(jù)結(jié)構(gòu)被改變,會快速失敗的,會拋出 ConcurrentModificationException 異常。

剛才是從類注釋中看到,HashSet 的實現(xiàn)是基于 HashMap 的,在 Java 中,要基于基礎(chǔ)類進行創(chuàng)新實現(xiàn),有兩種辦法:

  • 繼承基礎(chǔ)類,覆寫基礎(chǔ)類的方法,比如說繼承 HashMap , 覆寫其 add 的方法;
  • 組合基礎(chǔ)類,通過調(diào)用基礎(chǔ)類的方法,來復(fù)用基礎(chǔ)類的能力。

HashSet 使用的就是組合 HashMap,其優(yōu)點如下:

繼承表示父子類是同一個事物,而 Set 和 Map 本來就是想表達兩種事物,所以繼承不妥,而且 Java 語法限制,子類只能繼承一個父類,后續(xù)難以擴展。

組合更加靈活,可以任意的組合現(xiàn)有的基礎(chǔ)類,并且可以在基礎(chǔ)類方法的基礎(chǔ)上進行擴展、編排等,而且方法命名可以任意命名,無需和基礎(chǔ)類的方法名稱保持一致。

組合就是把 HashMap 當(dāng)作自己的一個局部變量,以下是 HashSet 的組合實現(xiàn):

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

從這兩行代碼中,我們可以看出兩點:

我們在使用 HashSet 時,比如 add 方法,只有一個入?yún)ⅲM合的 Map 的 add 方法卻有 key,value 兩個入?yún)?,相對?yīng)上 Map 的 key 就是我們 add 的入?yún)?,value 就是第二行代碼中的 PRESENT,此處設(shè)計非常巧妙,用一個默認值 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函數(shù)計算的的哈希值和其右移 16 位進行異或運算得到最終的哈希值。

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

image.png

在 java.util.HashMap#putVal中,直接通過 (n - 1) & hash 來得到當(dāng)前元素在節(jié)點數(shù)組中的位 置。如果不存在,直接構(gòu)造新節(jié)點并存儲到該節(jié)點數(shù)組的對應(yīng)位置。如果存在,則通過下面邏 輯:

p.hash == hash &amp;&amp; ((k = p.key) == key || (key != null &amp;&amp; key.equals(k)))
復(fù)制代碼

來判斷元素是否相等。

如果相等則用新值替換舊值,否則添加紅黑樹節(jié)點或者鏈表節(jié)點。

總結(jié):通過HashMap的key的唯一性來保證的HashSet元素的唯一性。

最后再看看:

《阿里巴巴Java開發(fā)手冊》里面還有這樣一段描述:

 

image.png

到現(xiàn)在是不是明白了,這個2,3點的原因

性能對比

其實HashSet和ArrayList去重性能差異的核心在于contains函數(shù)性能對比。

我們分別查看java.util.HashSet#containsjava.util.ArrayList#contains的實現(xiàn)。

java.util.HashSet#contains源碼:

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

最終也是通過HashMap判斷的

如果 hash 沖突不是極其嚴重(大多數(shù)都沒怎么有哈希沖突),n 個元素依次判斷并插入到 Set 的時間復(fù)雜度接近于 O (n),查找的復(fù)雜度是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>

發(fā)現(xiàn)其核心邏輯為:如果為 null, 則遍歷整個集合判斷是否有 null 元素;否則遍歷整個列表,通 過 o.equals(當(dāng)前遍歷到的元素) 判斷與當(dāng)前元素是否相等,相等則返回當(dāng)前循環(huán)的索引。

所以, java.util.ArrayList#contains判斷并插入n個元素到 Set 的時間復(fù)雜度接近于O (n^2),查找的復(fù)雜度是O(n)。

因此,通過時間復(fù)雜度的比較,性能差距就不言而喻了。

我們分別將兩個時間復(fù)雜度函數(shù)進行作圖, 兩者增速對比非常明顯:

image.png

如果數(shù)據(jù)量不大時采用 List 去重勉強可以接受,但是數(shù)據(jù)量增大后,接口響應(yīng)時間會超慢,這 是難以忍受的,甚至造成大量線程阻塞引發(fā)故障。

到此這篇關(guān)于Java集合去重導(dǎo)致的線上問題的文章就介紹到這了,更多相關(guān)Java集合去重內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • mybatis開啟spring事務(wù)代碼解析

    mybatis開啟spring事務(wù)代碼解析

    這篇文章主要介紹了mybatis開啟spring事務(wù)代碼解析,具有一定參考價值,需要的朋友可以了解下。
    2017-12-12
  • Java利用Geotools實現(xiàn)不同坐標(biāo)系之間坐標(biāo)轉(zhuǎn)換

    Java利用Geotools實現(xiàn)不同坐標(biāo)系之間坐標(biāo)轉(zhuǎn)換

    GeoTools 是一個開源的 Java GIS 工具包,可利用它來開發(fā)符合標(biāo)準(zhǔn)的地理信息系統(tǒng)。本文將利用工具包Geotools實現(xiàn)不同坐標(biāo)系之間坐標(biāo)轉(zhuǎn)換,感興趣的可以了解一下
    2022-08-08
  • 關(guān)于Java中避免空指針的方法

    關(guān)于Java中避免空指針的方法

    這篇文章主要介紹了關(guān)于Java中避免空指針的方法,空指針異常就是我們在對空對象進行的任何操作都會報空指針異常,所謂的指針,就是java中的對象的引用,比如String?s;這個就是指針,需要的朋友可以參考下
    2023-07-07
  • java實現(xiàn)簡單石頭剪刀布游戲

    java實現(xiàn)簡單石頭剪刀布游戲

    這篇文章主要介紹了java實現(xiàn)簡單石頭剪刀布游戲,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-02-02
  • 淺談foreach寫失效的問題

    淺談foreach寫失效的問題

    下面小編就為大家?guī)硪黄獪\談foreach寫失效的問題。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-07-07
  • 圖片疊加效果Java代碼實現(xiàn)

    圖片疊加效果Java代碼實現(xiàn)

    這篇文章主要為大家詳細介紹了圖片疊加效果Java代碼實現(xiàn),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-02-02
  • SpringMVC如何把后臺文件打印到前臺

    SpringMVC如何把后臺文件打印到前臺

    這篇文章主要介紹了SpringMVC如何把后臺文件打印到前臺,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-09-09
  • Spring中@Repository注解的作用和用法以及和@Mapper的區(qū)別詳析

    Spring中@Repository注解的作用和用法以及和@Mapper的區(qū)別詳析

    這篇文章主要給大家介紹了關(guān)于Spring中@Repository注解的作用和用法以及和@Mapper的區(qū)別的相關(guān)資料,注解的作用是標(biāo)識一個類為數(shù)據(jù)訪問對象,并由Spring框架進行實例化和管理,需要的朋友可以參考下
    2023-09-09
  • Java正則驗證正整數(shù)的方法分析【測試可用】

    Java正則驗證正整數(shù)的方法分析【測試可用】

    這篇文章主要介紹了Java正則驗證正整數(shù)的方法,結(jié)合實例形式對比分析了java針對正整數(shù)的驗證方法及相關(guān)注意事項,需要的朋友可以參考下
    2017-08-08
  • Java堆排序算法詳解

    Java堆排序算法詳解

    這篇文章主要為大家詳細介紹了Java堆排序算法的相關(guān)代碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-09-09

最新評論

靖边县| 恩施市| 彰武县| 曲水县| 财经| 米易县| 织金县| 绿春县| 丹巴县| 太谷县| 甘孜| 阿城市| 冀州市| 深水埗区| 恩平市| 宜君县| 洪湖市| 长宁区| 马公市| 双城市| 南溪县| 金门县| 湾仔区| 京山县| 彭山县| 闸北区| 榆社县| 雷山县| 成武县| 独山县| 正定县| 揭阳市| 新乡市| 太湖县| 壶关县| 东明县| 临潭县| 正阳县| 绥棱县| 益阳市| 福清市|