我們知道java HashMap的擴容是有成本的,為了減少擴容的次數和成本,可以給HashMap設定初始容量大小,如下所示:但是在實際使用的過程中,發現效能不但沒有提升,反而顯著下降了!程式碼
2021-06-10 09:17:47
我們知道java HashMap的擴容是有成本的,為了減少擴容的次數和成本,可以給HashMap設定初始容量大小,如下所示:
但是在實際使用的過程中,發現效能不但沒有提升,反而顯著下降了!程式碼裡對HashMap的操作也只有遍歷了,看來是遍歷出了問題,於是做了一番測試,得到如下結果:
迭代器測試
貼上測試程式碼:
這是運行結果:
我們將第一個Map初始化10w大小,第二個map不指定大小(實際16),兩個儲存相同的資料,但是用迭代器遍歷100次的時候發現效能差異,一個36ms一個4ms,實際上效能差距更大,這裡的4ms是600次System.out.print的耗時,這裡將print注掉再試下
輸出結果如下:
可以發現第二個map耗時幾乎為0,第一個達到了28ms,遍歷期間沒有進行任何操作,既然石錘了和 initial capacity 有關,下一步我們去看看為什麼會這樣,找找Map迭代器的源碼看看。
迭代器源碼探究
我們來看看Map.entrySet().iterator()的源碼;
其中EntryIterator是HashMap的內部抽象類,源碼並不多,我全部貼上來並附上中文註釋
上面的程式碼一看就明白了,迭代器每次尋找下一個元素都會去遍歷陣列,如果 initial capacity 特別大的話,也就是說 threshold 也大,table.length就大,所以遍歷比較耗效能。
table陣列的大小設定是在resize()方法裡:
其他遍歷方法
注意程式碼裡我們用的是Map.entrySet().iterator(),實際上和keys().iterator(), values().iterator() 一樣,源碼如下:
這兩個就不分析了,效能一樣。
實際使用中對集合的遍歷還有幾種方法:
普通for迴圈+下標增強型for迴圈Map.forEachStream.forEach普通for迴圈+下標的方法不適用於Map,這裡不討論了。
增強型for迴圈
增強行for迴圈實際上是通過迭代器來實現的,我們來看兩者的聯絡
源碼:
編譯後的位元組碼:
都不用耐心觀察,兩個方法的位元組碼除了局部變數不一樣其他都幾乎一樣,由此可以得出增強型for迴圈效能與迭代器一樣,實際運行結果也一樣
Map.forEach
先說一下為什麼不把各種方法一起運行同時列印效能,這是因為CPU快取的原因和JVM的一些優化會干擾到效能的判斷,附錄全部測試結果有說明
直接來看源碼吧
很簡短的源碼,就不打註釋了,從源碼我們不難獲取到以下資訊:
該方法也是快速失敗的,遍歷期間不能刪除元素需要遍歷整個陣列BiConsumer加了@FunctionalInterface註解,用了 lambda第三點和效能無關,這裡只是提下
通過以上資訊我們能確定這個效能與table陣列的大小有關。
但是在實際測試的時候卻發現效能比迭代器差了不少:
其中詳細原因等我下期的文章吧,這裡不講了
Stream.forEach
Stream與Map.forEach的共同點是都使用了lambda表示式。但兩者的源碼沒有任何複用的地方。
不知道你有沒有看累,先上測試結果吧:
耗時比Map.foreach還要高點。
下面講講Straam.foreach順序流的源碼,這個也不復雜,不過累的話先去看看總結吧。
Stream.foreach的執行者是分流器,HashMap的分流器源碼就在HashMap類中,是一個靜態內部類,類名叫 EntrySpliterator
下面是順序流執行的方法
從以上源碼中我們也可以輕易得出遍歷需要順序掃描所有陣列
總結
至此,Map的四種遍歷方法都測試完了,我們可以簡單得出兩個結論
Map的遍歷效能與內部table陣列大小有關,也就是說與常用參數 initial capacity 有關,不管哪種遍歷方式都是的效能(由高到低):迭代器 == 增強型For迴圈 > Map.forEach > Stream.foreach這裡就不說什麼多少倍多少倍的效能差距了,拋開資料集大小都是扯淡,當我們不指定initial capacity的時候,四種遍歷方法耗時都是3ms,這3ms還是輸入輸出流的耗時,實際遍歷耗時都是0,所以資料集不大的時候用哪種都無所謂,就像不加輸入輸出流耗時不到1ms一樣,很多時候效能消耗是在遍歷中的業務操作,這篇文章不是為了讓你去優化程式碼把foreach改成迭代器的,在大多數場景下並不需要關注迭代本身的效能,Stream與Lambda帶來的可讀性提升更加重要。
所以此文的目的就當是知識拓展吧,除了以上說到的遍歷效能問題,你還應該從中能獲取到的知識點有:
HashMap的陣列是儲存在table數組裡的table陣列是resize方法初始化的,new Map不會初始化陣列Map遍歷是table陣列從下標0遞增排序的,所以他是無序的keySet().iterator,values.iterator, entrySet.iterator 來說沒有本質區別,用的都是同一個迭代器各種遍歷方法裡,只有迭代器可以remove,雖然增強型for迴圈底層也是迭代器,但這個語法糖隱藏了 remove 方法每次呼叫迭代器方法都會new 一個迭代器,但是隻有一個可以修改Map.forEach與Stream.forEach看上去一樣,實際實現是不一樣的附:四種遍歷源碼
附:完整測試類與測試結果+一個奇怪的問題
測試結果:
相關文章
我們知道java HashMap的擴容是有成本的,為了減少擴容的次數和成本,可以給HashMap設定初始容量大小,如下所示:但是在實際使用的過程中,發現效能不但沒有提升,反而顯著下降了!程式碼
2021-06-10 09:17:47
日常工作中經常會使用到word文件,在編輯文件頁首頁尾,有時需要在奇偶頁設定不同的頁首頁尾,該怎麼做呢? 以下用一個示例來說明word奇偶頁設定不同的頁首頁尾。1、word文件示例,比
2021-06-10 09:17:37
6月8日凌晨,蘋果舉行了一年一度的WWDC(全球開發者大會)。今年,蘋果給我們帶來了一系列和軟體的更新,包括了iOS 15、iPadOS 15、watchOS 8、macOS Monterey等。今年與往年
2021-06-10 09:14:24
什麼是Java?這個問題即便是問入行已經多年的老Java開發,也不是所有人都能說出所以然。所以整理出了以下的基礎概念,為初入Java海洋的人一點指引。Java語言有哪些優點?java是純面
2021-06-10 08:53:22
6 月 2 日晚間,華為召開 HarmonyOS 2 及華為全場景新品釋出會,正式釋出了備受期待的華為 MatePad Pro 12.6 英寸平板新品,這是華為首款搭載 HarmonyOS 2 的平板產品,基於 Harmon
2021-06-10 08:52:06
鴻蒙OS釋出一週,更新使用者已經突破1000萬,全國手機使用者對鴻蒙OS的支援熱情高漲,釋出會當日鴻蒙OS直衝百度熱議榜首,兩天居高不下,成了年度科技界最熱門事件。 01先不說庫克和
2021-06-10 08:31:46