<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          學(xué)習(xí)集合類(lèi)源碼對(duì)我們實(shí)際工作的幫助和應(yīng)用!

          共 7002字,需瀏覽 15分鐘

           ·

          2023-08-09 16:54

          文章首發(fā)在公眾號(hào)(月伴飛魚(yú)),之后同步到掘金和個(gè)人網(wǎng)站:xiaoflyfish.cn/

          「覺(jué)得有收獲,希望幫忙點(diǎn)贊,轉(zhuǎn)發(fā)下哈,謝謝,謝謝」

          Java的集合類(lèi)包括Map和Collection兩大類(lèi)。Collection包括List、Set和Queue三個(gè)小類(lèi)。

          「如下圖:」

          這邊文章通過(guò)源碼解讀的方式帶大家了解一下:集合類(lèi)使用過(guò)程中常見(jiàn)的問(wèn)題以及學(xué)習(xí)一些優(yōu)秀的設(shè)計(jì)思想。

          「集合批量操作性能」

          集合的單個(gè)操作,一般都沒(méi)有性能問(wèn)題,性能問(wèn)題主要出現(xiàn)的批量操作上。

          ?

          如批量新增操作:

          ?

          在 List 和 Map 大量數(shù)據(jù)新增的時(shí)候,使用 for 循環(huán) + add/put 方法新增,這樣子會(huì)有很大的擴(kuò)容成本,我們應(yīng)該盡量使用 addAll 和 putAll 方法進(jìn)行新增,如下演示了兩種方案的性能對(duì)比:

          ?

          單個(gè) for 循環(huán)新增 300 w 個(gè),耗時(shí)1518。

          批量新增 300 w 個(gè),耗時(shí)8。

          ?

          可以看到,批量新增方法性能是單個(gè)新增方法性能的 189 倍,主要原因在于批量新增,只會(huì)擴(kuò)容一次,大大縮短了運(yùn)行時(shí)間,而單個(gè)新增,每次到達(dá)擴(kuò)容閥值時(shí),都會(huì)進(jìn)行擴(kuò)容,在整個(gè)過(guò)程中就會(huì)不斷的擴(kuò)容,浪費(fèi)了很多時(shí)間。

          我們來(lái)看下批量新增的源碼:

          我們可以看到,整個(gè)批量新增的過(guò)程中,只擴(kuò)容了一次。

          「集合線程安全性」

          集合的非線程安全指的是:集合類(lèi)作為共享變量,被多線程讀寫(xiě)的時(shí)候是不安全的,如果要實(shí)現(xiàn)線程安全的集合,在類(lèi)注釋中,JDK 統(tǒng)一推薦我們使用 Collections.synchronized* 類(lèi)。

          Collections 幫我們實(shí)現(xiàn)了 List、Set、Map 對(duì)應(yīng)的線程安全的方法, 如下圖:

          從源碼中我們可以看到 Collections 是通過(guò) synchronized 關(guān)鍵字給 List 操作數(shù)組的方法加上鎖,來(lái)實(shí)現(xiàn)線程安全的。

          集合類(lèi)方法常見(jiàn)的問(wèn)題

          List

          「Arrays.asList()方法」

          我們把數(shù)組轉(zhuǎn)化成集合時(shí),常使用 Arrays.asList(array),這個(gè)方法有兩個(gè)問(wèn)題,代碼演示如下:

          ?

          問(wèn)題一:修改數(shù)組的值,會(huì)直接影響原list。

          ?
          public void testArrayToList(){
            Integer[] array = new Integer[]{1,2,3,4,5,6};
            List<Integer> list = Arrays.asList(array);
           
            // 問(wèn)題1:修改數(shù)組的值,會(huì)直接影響原 list
            log.info("數(shù)組被修改之前,集合第一個(gè)元素為:{}",list.get(0));
            array[0] = 10;
            log.info("數(shù)組被修改之前,集合第一個(gè)元素為:{}",list.get(0));
          }
          ?

          問(wèn)題二:不能對(duì)新 List 進(jìn)行 add、remove 等操作,否則運(yùn)行時(shí)會(huì)報(bào) UnsupportedOperationException 錯(cuò)誤。

          ?
          public void testArrayToList(){
            Integer[] array = new Integer[]{1,2,3,4,5,6};
            List<Integer> list = Arrays.asList(array);
           
            // 問(wèn)題2:使用 add、remove 等操作 list 的方法時(shí),
            // 會(huì)報(bào) UnsupportedOperationException 異常
            list.add(7);
          }

          原因分析:

          從上圖中,我們可以發(fā)現(xiàn),Arrays.asList 方法返回的 List 并不是 java.util.ArrayList,而是自己內(nèi)部的一個(gè)靜態(tài)類(lèi),該靜態(tài)類(lèi)直接持有數(shù)組的引用,并且沒(méi)有實(shí)現(xiàn) add、remove 等方法,這些就是問(wèn)題 1 和 2 的原因。

          「list.toArray方法」

            public void testListToArray(){
              List<Integer> list = new ArrayList<Integer>(){{
                add(1);
                add(2);
                add(3);
                add(4);
              }};
           
              // 下面這行代碼是無(wú)法轉(zhuǎn)化成數(shù)組的,無(wú)參 toArray 返回的是 Object[],
              // 無(wú)法向下轉(zhuǎn)化成 List<Integer>,編譯都無(wú)法通過(guò)
              // List<Integer> list2 = list.toArray();
           
              // 有參 toArray 方法,數(shù)組大小不夠時(shí),得到數(shù)組為 null 情況
              Integer[] array0 = new Integer[2];
              list.toArray(array0);
              log.info("toArray 數(shù)組大小不夠,array0 數(shù)組[0] 值是{},數(shù)組[1] 值是{},",array0[0],array0[1]);
            
              // 數(shù)組初始化大小正好,正好轉(zhuǎn)化成數(shù)組
              Integer[] array1 = new Integer[list.size()];
              list.toArray(array1);
              log.info("toArray 數(shù)組大小正好,array1 數(shù)組[3] 值是{}",array1[3]);
           
              // 數(shù)組初始化大小大于實(shí)際所需大小,也可以轉(zhuǎn)化成數(shù)組
              Integer[] array2 = new Integer[list.size()+2];
              list.toArray(array2);
              log.info("toArray 數(shù)組大小多了,array2 數(shù)組[3] 值是{},數(shù)組[4] 值是{}",array2[3],array2[4]);
            }
          toArray 數(shù)組大小不夠,array0 數(shù)組[0] 值是null,數(shù)組[1] 值是null,
          toArray 數(shù)組大小正好,array1 數(shù)組[3] 值是4
          toArray 數(shù)組大小多了,array2 數(shù)組[3] 值是4,數(shù)組[4] 值是null
          ?

          原因分析:

          ?

          toArray 的無(wú)參方法,無(wú)法強(qiáng)轉(zhuǎn)成具體類(lèi)型,這個(gè)編譯的時(shí)候,就會(huì)有提醒,我們一般都會(huì)去使用帶有參數(shù)的 toArray 方法,這時(shí)就有一個(gè)坑,如果參數(shù)數(shù)組的大小不夠,這時(shí)候返回的數(shù)組值是空。

          「Collections.emptyList()方法」

          ?

          問(wèn)題:

          ?

          在返回的 Collections.emptyList(); 上調(diào)用了add()方法,拋出異常 UnsupportedOperationException。

          ?

          分析:

          ?

          Collections.emptyList() 返回的是不可變的空列表,這個(gè)空列表對(duì)應(yīng)的類(lèi)型是EmptyList,這個(gè)類(lèi)是Collections中的靜態(tài)內(nèi)部類(lèi),繼承了AbstractList。

          AbstractList中默認(rèn)的add方法是沒(méi)有實(shí)現(xiàn)的,直接拋出UnsupportedOperationException異常。

          而EmptyList只是繼承了AbstractList,卻并沒(méi)有重寫(xiě)add方法,因此直接調(diào)用add方法會(huì)拋異常。

          除了emptyList,還有emptySet、emptyMap等也一樣。

          「List.subList()方法」

          list.subList() 產(chǎn)生的集合也會(huì)與原始List互相影響。

          建議使用時(shí),通過(guò)List list = Lists.newArrayList(arrays); 來(lái)生成一個(gè)新的list,不要再操作原列表。

          「UnmodifiableList」

          UnmodifiableList是Collections中的內(nèi)部類(lèi),通過(guò)調(diào)用 Collections.unmodifiableList(List list) 可返回指定集合的不可變集合。

          集合只能被讀取,不能做任何增刪改操作,從而保護(hù)不可變集合的安全。但這個(gè)不可變僅僅是正向的不可變。

          反過(guò)來(lái)如果修改了原來(lái)的集合,則這個(gè)不可變集合仍會(huì)被同步修改。因?yàn)椴豢勺兗系讓邮褂玫倪€是原來(lái)的List。

          Map

          「ConcurrentHashMap不允許為null」

          ConcurrentHashMap#put方法的源碼,開(kāi)頭就看到了對(duì)KV的判空校驗(yàn)。

          為什么ConcurrentHashMapHashMap設(shè)計(jì)的判斷邏輯不一樣?

          Doug Lea 老爺子的解釋是:

          • null會(huì)引起歧義,如果valuenull,我們無(wú)法得知是值為null,還是key未映射具體值?
          • Doug Lea 并不喜歡null,認(rèn)為null 就是個(gè)隱藏的炸彈。

          貼一下常用Map子類(lèi)集合對(duì)于 null存儲(chǔ)情況:

          「HashMap 是無(wú)序的」

          舉例:

          import java.util.HashMap;
           
          public class App {
           
              public static void main(String[] args) {
                  HashMap<String, Object> result = getList();
                  result.forEach((k, v) -> {
                      System.out.println(k + ":" + v);
                  });
              }
           
              // 查詢(xún)方法(簡(jiǎn)化版)
              public static HashMap<String, Object> getList() {
                  HashMap<String, Object> result = new HashMap<>(); // 最終返回的結(jié)果集
                  // 偽代碼:從數(shù)據(jù)庫(kù)中查詢(xún)出了數(shù)據(jù),然后對(duì)數(shù)據(jù)進(jìn)行處理之后,存到了
                  for (int i = 1; i <= 5; i++) {
                      result.put("2022-" + i, "hello java" + i);
                  }
                  return result;
              }
          }

          結(jié)果并沒(méi)有按先后順序返回。

          ?

          原因分析

          ?

          HashMap 使用的是哈希方式進(jìn)行存儲(chǔ)的,因此存入和讀取的順序可能是不一致的,這也說(shuō) HashMap 是無(wú)序的集合,所以會(huì)導(dǎo)致插入的順序,與最終展示的順序不一致。

          解決方案:將無(wú)序的 HashMap 改為有序的 LinkedHashMap。

          LinkedHashMap 屬于 HashMap 的子類(lèi),所以 LinkedHashMap 除了擁有 HashMap 的所有特性之后,還具備自身的一些擴(kuò)展屬性,其中就包括 LinkedHashMap 中額外維護(hù)了一個(gè)雙向鏈表,這個(gè)雙向鏈表就是用來(lái)保存元素的(插入)順序的。

          Set

          如果是需要對(duì)我們自定義的對(duì)象去重,就需要我們重寫(xiě) hashCode 和 equals 方法。

          不然HashSet調(diào)用默認(rèn)的hashCode方法判斷對(duì)象的地址,不等就達(dá)不到想根據(jù)對(duì)象的值去重的目的。

          瀏覽 1216
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  五月丁香激情综合久久 | 人人靠人人操 | 一级爱ai高清免费在线视频 | 中文字幕一区二区三区免费2023 | 做爱视频小网站 |