資料的集合處理,有哪些規則?

雲端計算頻道發表於2018-10-30


大資料,即海量資料,意味著基礎設施構建必須高效與穩定。如何在指定時間範圍內,透過常規工具處理資料集合,以下規則,可供參考。

Rule 1. 【推薦】底層資料結構是陣列的集合,指定集合初始大小

底層資料結構為陣列的集合包括 ArrayList,HashMap,HashSet,ArrayDequeue等。

陣列有大小限制,當超過容量時,需要進行復制式擴容,新申請一個是原來容量150% or 200%的陣列,將原來的內容複製過去,同時浪費了記憶體與效能。HashMap/HashSet的擴容,還需要所有鍵值對重新落位,消耗更大。

預設建構函式使用預設的陣列大小,比如ArrayList預設大小為10,HashMap為16。因此建議使用ArrayList(int initialCapacity)等建構函式,明確初始化大小。

HashMap/HashSet的初始值還要考慮載入因子:

為了降低雜湊衝突的機率(Key的雜湊值按陣列大小取模後,如果落在同一個陣列下標上,將組成一條需要遍歷的Entry鏈),預設當HashMap中的鍵值對達到陣列大小的75%時,即會觸發擴容。因此,如果預估容量是100,即需要設定100/0.75 +1=135的陣列大小。vjkit的MapUtil的Map建立函式封裝了該計算。

如果希望加快Key查詢的時間,還可以進一步降低載入因子,加大初始大小,以降低雜湊衝突的機率。

Rule 2. 【推薦】儘量使用新式的foreach語法遍歷Collection與陣列

foreach是語法糖,遍歷集合的實際位元組碼等價於基於Iterator的迴圈。

foreach程式碼一來程式碼簡潔,二來有效避免了有多個迴圈或巢狀迴圈時,因為不小心的複製貼上,用錯了iterator或迴圈計數器(i,j)的情況。

Rule 3. 【強制】不要在foreach迴圈裡進行元素的remove/add操作,remove元素可使用Iterator方式

  • Facebook-Contrib: Correctness - Method modifies collection element while iterating

  • Facebook-Contrib: Correctness - Method deletes collection element while iterating

Rule 4. 【強制】使用entrySet遍歷Map類集合Key/Value,而不是keySet 方式進行遍歷

keySet遍歷的方式,增加了N次用key獲取value的查詢。

Sonar-2864:"entrySet()" should be iterated when both the key and value are needed

Rule 5. 【強制】當物件用於集合時,下列情況需要重新實現hashCode()和 equals()

1) 以物件做為Map的KEY時;

2) 將物件存入Set時。

上述兩種情況,都需要使用hashCode和equals比較物件,預設的實現會比較是否同一個物件(物件的引用相等)。

另外,物件放入集合後,會影響hashCode(),equals()結果的屬性,將不允許修改。

Sonar-2141:Classes that don't define "hashCode()" should not be used in hashes

Rule 6. 【強制】高度注意各種Map類集合Key/Value能不能儲存null值的情況

 

   

由於HashMap的干擾,很多人認為ConcurrentHashMap是可以置入null值。同理,Set中的value實際是Map中的key。

Rule 7. 【強制】長生命週期的集合,裡面內容需要及時清理,避免記憶體洩漏

長生命週期集合包括下面情況,都要小心處理。

1) 靜態屬性定義;

2) 長生命週期物件的屬性;

3) 儲存在ThreadLocal中的集合。

如無法保證集合的大小是有限的,使用合適的快取方案代替直接使用HashMap。

另外,如果使用WeakHashMap儲存物件,當物件本身失效時,就不會因為它在集合中存在引用而阻止回收。但JDK的WeakHashMap並不支援併發版本,如果需要併發可使用Guava Cache的實現。

Rule 8. 【強制】集合如果存在併發修改的場景,需要使用執行緒安全的版本

1) 著名的反例,HashMap擴容時,遇到併發修改可能造成100%CPU佔用。

推薦使用java.util.concurrent(JUC)工具包中的併發版集合,如ConcurrentHashMap等,優於使用Collections.synchronizedXXX()系列函式進行同步化封裝(等價於在每個方法都加上synchronized關鍵字)。

例外:ArrayList所對應的CopyOnWriteArrayList,每次更新時都會複製整個陣列,只適合於讀多寫很少的場景。如果頻繁寫入,可能退化為使用Collections.synchronizedList(list)。

2) 即使執行緒安全類仍然要注意函式的正確使用。

例如:即使用了ConcurrentHashMap,但直接是用get/put方法,仍然可能會多執行緒間互相覆蓋。

Rule 9. 【推薦】正確使用集合泛型的萬用字元

List<String>並不是List<Object>的子類,如果希望泛型的集合能向上向下相容轉型,而不僅僅適配唯一類,則需定義萬用字元,可以按需要extends 和 super的字面意義,也可以遵循PECS(Producer Extends Consumer Super)原則:

1) 如果集合要被讀取,定義成<? extends T>

2) 如果集合要被寫入,定義成<? super T>

Rule 10. 【推薦】List, List<?> 與 List<Object>的選擇

定義成List,會被IDE提示需要定義泛型。 如果實在無法確定泛型,就倉促定義成List<?>來矇混過關的話,該list只能讀,不能增改。定義成List<Object>呢,如規則9所述,List<String> 並不是List<Object>的子類,除非函式定義使用了萬用字元。

因此實在無法明確其泛型時,使用List也是可以的。

Rule 11. 【推薦】如果Key只有有限的可選值,先將Key封裝成Enum,並使用EnumMap

EnumMap,以Enum為Key的Map,內部儲存結構為Object[enum.size],訪問時以value = Object[enum.ordinal()]獲取值,同時具備HashMap的清晰結構與陣列的效能。

Sonar-1640: Maps with keys that are enum values should be replaced with EnumMap

Rule 12. 【推薦】Array 與 List互轉的正確寫法

Arrays.asList(array),如果array是原始型別陣列如int[],會把整個array當作List的一個元素,String[] 或 Foo[]則無此問題。 Collections.addAll()實際是迴圈加入元素,效能相對較低,同樣會把int[]認作一個元素。

Facebook-Contrib: Correctness - Impossible downcast of toArray() result

Facebook-Contrib: Correctness - Method calls Array.asList on an array of primitive values

 

 

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31545808/viewspace-2218102/,如需轉載,請註明出處,否則將追究法律責任。

相關文章