成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

記一次集合去重導(dǎo)致的線上問(wèn)題

開發(fā) 前端
在工作中一次排查慢接口時(shí),查到了一個(gè)函數(shù)耗時(shí)較長(zhǎng),最終定位到是通過(guò) List 去重導(dǎo)致的。由于測(cè)試環(huán)境還有線上早期數(shù)據(jù)較少,這個(gè)接口的性能問(wèn)題沒有引起較大關(guān)注,后面頻繁超時(shí),才引起重視。

[[352297]]

前言

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

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

 

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

 

你看,阿里前輩們都免費(fèi)總結(jié)了,不過(guò)還是會(huì)看到有人會(huì)用List的contains函數(shù)來(lái)去重......

不記得的,罰抄10萬(wàn)遍

 

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

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

HashSet源碼

先看看類注釋:

 

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

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

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

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

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

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

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

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

  1. // 把 HashMap 組合進(jìn)來(lái),key 是 Hashset 的 key,value 是下面的 PRESENT 
  2. private transient HashMap<E,Object> map; 
  3. // HashMap 中的 value 
  4. private static final Object PRESENT = new Object(); 

從這兩行代碼中,我們可以看出兩點(diǎn):

我們?cè)谑褂?HashSet 時(shí),比如 add 方法,只有一個(gè)入?yún)ⅲM合的 Map 的 add 方法卻有 key,value 兩個(gè)入?yún)ⅲ鄬?duì)應(yīng)上 Map 的 key 就是我們 add 的入?yún)ⅲ瑅alue 就是第二行代碼中的 PRESENT,此處設(shè)計(jì)非常巧妙,用一個(gè)默認(rèn)值 PRESENT 來(lái)代替 Map 的 Value;

我們?cè)賮?lái)看看add方法:

  1. public boolean add(E e) { 
  2.     // 直接使用 HashMap 的 put 方法,進(jìn)行一些簡(jiǎn)單的邏輯判斷 
  3.     return map.put(e, PRESENT)==null

我們進(jìn)入更底層源碼java.util.HashMap#put:

  1. public V put(K key, V value) {  
  2.  return putVal(hash(key), key, value, falsetrue);  

再瞧瞧hash方法:

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

可以看到如果 key 為 null ,哈希值為 0,否則將 key 通過(guò)自身hashCode函數(shù)計(jì)算的的哈希值和其右移 16 位進(jìn)行異或運(yùn)算得到最終的哈希值。

我們?cè)倩氐?java.util.HashMap#putVal中:

 

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

  1. p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))) 

來(lái)判斷元素是否相等。

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

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

最后再看看:

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

 

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

性能對(duì)比

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

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

java.util.HashSet#contains源碼:

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

最終也是通過(guò)HashMap判斷的

如果 hash 沖突不是極其嚴(yán)重(大多數(shù)都沒怎么有哈希沖突),n 個(gè)元素依次判斷并插入到 Set 的時(shí)間復(fù)雜度接近于 O (n),查找的復(fù)雜度是O(1)。

接下來(lái)我們看java.util.ArrayList#contains的源碼:

  1. public boolean contains(Object o) { 
  2.         return indexOf(o) >= 0; 
  3.     } 
  1. public int indexOf(Object o) { 
  2.         if (o == null) { 
  3.             for (int i = 0; i < size; i++) 
  4.                 if (elementData[i]==null
  5.                     return i; 
  6.         } else { 
  7.             for (int i = 0; i < size; i++) 
  8.                 if (o.equals(elementData[i])) 
  9.                     return i; 
  10.         } 
  11.         return -1; 
  12.     } 

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

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

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

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

 

 

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

本文轉(zhuǎn)載自微信公眾號(hào)「 月伴飛魚」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系 月伴飛魚公眾號(hào)。

 

責(zé)任編輯:武曉燕 來(lái)源: 日常加油站
相關(guān)推薦

2021-11-23 21:21:07

線上排查服務(wù)

2023-10-11 22:24:00

DubboRedis服務(wù)器

2023-01-04 18:32:31

線上服務(wù)代碼

2020-08-12 08:25:43

數(shù)據(jù)庫(kù)MySQL技術(shù)

2022-06-06 11:31:31

MySQL數(shù)據(jù)查詢

2024-10-15 09:27:36

2021-05-13 08:51:20

GC問(wèn)題排查

2023-04-06 07:53:56

Redis連接問(wèn)題K8s

2022-12-17 19:49:37

GCJVM故障

2022-01-10 09:31:17

Jetty異步處理seriesbaid

2019-09-10 10:31:10

JVM排查解決

2021-12-12 18:12:13

Hbase線上問(wèn)題

2021-05-31 10:08:44

工具腳本主機(jī)

2023-01-05 11:44:43

性能HTTPS

2021-03-29 12:35:04

Kubernetes環(huán)境TCP

2019-09-11 08:22:57

MySQL數(shù)據(jù)庫(kù)遠(yuǎn)程登錄

2021-11-11 16:14:04

Kubernetes

2011-08-12 09:30:02

MongoDB

2020-08-20 07:37:21

數(shù)據(jù)庫(kù)開源框架

2013-01-17 10:31:13

JavaScriptWeb開發(fā)firebug
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 天天干精品 | 麻豆一区二区三区精品视频 | 国产成人黄色 | 免费看a | 精品一区在线免费观看 | 欧美亚洲一区二区三区 | 在线综合视频 | 欧美jizzhd精品欧美巨大免费 | 自拍偷拍一区二区三区 | 日本三级网站在线观看 | 亚洲成人中文字幕 | 日韩欧美在线精品 | 欧美一级免费看 | 欧美日本在线观看 | 综合精品 | 日本成人三级电影 | 欧美精品一区二区免费视频 | 91在线观看 | 亚洲一区二区在线电影 | 欧美二区在线 | 1204国产成人精品视频 | 91精品国产91久久久久久不卞 | 日日噜噜噜夜夜爽爽狠狠视频, | 久国久产久精永久网页 | www.久| 青青草综合 | 99久久婷婷国产综合精品首页 | 男女羞羞在线观看 | 久久精品亚洲精品国产欧美 | 亚洲一区二区精品 | 国产精品久久久久久久久久久久冷 | 狠狠操网站 | 91天堂网| 欧美久久精品一级黑人c片 91免费在线视频 | 91视频正在播放 | 亚洲视频在线免费 | 国产欧美日韩精品一区 | 91精品一区二区 | 久久久.com| 国产精品亚洲欧美日韩一区在线 | 日本在线中文 |