1. 程式人生 > >老哥你真的知道ArrayList#sublist的正確用法麼

老哥你真的知道ArrayList#sublist的正確用法麼

我們有這麼一個場景,給你一個列表,可以動態的新增,但是最終要求列表升序,要求長度小於20,可以怎麼做?

這個還不簡單,幾行程式碼就可以了

public List<Integer> trimList(List<Integer> list, int add) {
    list.add(add);
    list.sort(null);
    if (list.size() > 20) {
        list = list.subList(0, 20);
    }
    return list;
}

<!-- more -->

1. 測試驗證

上面的程式碼先不考慮效能的優化方面,有沒有問題?

寫了個簡單的測試case,我們來看下會出現什麼情況

@Test
public void testTri() throws InterruptedException {
    List<Integer> list = new ArrayList<>(30);
    Random random = new Random();
    int cnt = 0;
    while (true) {
        list = trimList(list, random.nextInt(100000));

        Thread.sleep(1);
        ++cnt;
        System.out.println(list + " >> " + cnt);
    }
}

啟動引數修改下,新增jvm最大記憶體條件 -Xmx3m, 然後跑上面程式碼,一段時間之後居然出現stack over flow

sof

有意思的問題來了,從邏輯上看,這個陣列固定長度為20,頂多有21條資料,怎麼就會記憶體溢位呢?

2. SubList 方法揭祕

我們看下ArrayList#sublis方法的實現邏輯,就可以發現獲取子列表,居然只是重置了一下內部陣列的索引

public List<E> subList(int fromIndex, int toIndex) {
    subListRangeCheck(fromIndex, toIndex, size);
    return new SubList(this, 0, fromIndex, toIndex);
}

private class SubList extends AbstractList<E> implements RandomAccess {
    private final AbstractList<E> parent;
    private final int parentOffset;
    private final int offset;
    int size;
  
    SubList(AbstractList<E> parent,
            int offset, int fromIndex, int toIndex) {
        this.parent = parent;
        this.parentOffset = fromIndex;
        this.offset = offset + fromIndex;
        this.size = toIndex - fromIndex;
        this.modCount = ArrayList.this.modCount;
    }
    ...
}

返回的是一個SubList型別物件,這個物件和原來的List公用一個儲存資料的陣列,但是多了兩個記錄子列表起始的偏移;

然後再看下SubList的add方法,也是直接在原來的陣列中新增資料,想到與原來的列表在指定位置插入資料

public void add(int index, E e) {
    rangeCheckForAdd(index);
    checkForComodification();
    parent.add(parentOffset + index, e);
    this.modCount = parent.modCount;
    this.size++;
}

所以上面實現的程式碼中 list = list.subList(0, 20); 這一行,有記憶體洩露,貌似是隻返回了一個20長度大小的列表,但是這個列表中的陣列長度,可能遠遠不止20

為了驗證上面的說法,debug下上面的測試用例

debug

動圖演示如下

gif

3. 正確使用姿勢

上面知道sublist並不會新建立一個列表,舊的資料依然還在,只是我們用不了而已,所以改動也很簡單,根據sublist的結果建立一個新的陣列就好了

public List<Integer> trimList(List<Integer> list, int add) {
    list.add(add);
    list.sort(null);
    if (list.size() > 20) {
        list = new ArrayList<>(list.subList(0, 20));
    }
    return list;
}

再次測試,程式碼一直在順利的執行,看下後面的計數,都已經5w多,前面1w多久報錯了

show

雖然上面解決了記憶體洩露,但是gc也很頻繁了,本篇的重點主要是指出sublist的錯誤使用姿勢,所以上面演算法的優化就不詳細展開了

sof

4. 知識點擴充套件

看下下面的測試程式碼輸出應該是什麼

@ToString
public static class InnerC {
    private String name;
    private Integer id;

    public InnerC(String name, Integer id) {
        this.name = name;
        this.id = id;
    }
}

@Test
public void subList() {
    List<Integer> list = new ArrayList<>();
    for (int i = 0; i < 20; i++) {
        list.add(i);
    }

    // case 1
    List<Integer> sub = list.subList(10, 15);
    sub.add(100);
    System.out.println("list: " + list);
    System.out.println("sub: " + sub);

    // case 2
    list.set(11, 200);
    System.out.println("list: " + list);
    System.out.println("sub: " + sub);

    // case 3
    list = new ArrayList<>(sub);
    sub.set(0, 999);
    System.out.println("list: " + list);
    System.out.println("sub: " + sub);

    // case 4
    List<InnerC> cl = new ArrayList<>();
    cl.add(new InnerC("a", 1));
    cl.add(new InnerC("a2", 2));
    cl.add(new InnerC("a3", 3));
    cl.add(new InnerC("a4", 4));

    List<InnerC> cl2 = new ArrayList<>(cl.subList(1, 3));
    cl2.get(0).name = "a5";
    cl2.get(0).id = 5;
    System.out.println("list cl: " + cl);
    System.out.println("list cl2: " + cl2);
}

再看具體的答案之前,先分析一下

針對case1/2,我們知道sublist返回的列表和原列表公用一個底層陣列,所以這兩個列表的增刪,都是相互影響的

  • case1 執行之後相當於在list陣列的下標15這裡,插入資料100
  • case2 執行之後,list的下標11,相當於sub的下標1,也就是說sub[1] 變成了200

對於case3/4 而言,根據sub建立了一個新的列表,這個時候修改新的列表中的值,會影響到原來的列表中的值麼?

分析這個場景,就需要看一下原始碼了

public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // replace with empty array.
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

// 對應的核心邏輯就在 Arrays.copyOf,而這個方法主要呼叫的是native方法`System.arraycopy`

public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
    @SuppressWarnings("unchecked")
    T[] copy = ((Object)newType == (Object)Object[].class)
        ? (T[]) new Object[newLength]
        : (T[]) Array.newInstance(newType.getComponentType(), newLength);
    System.arraycopy(original, 0, copy, 0,
                     Math.min(original.length, newLength));
    return copy;
}

從上面的原始碼分析,會不會相互影響就看這個陣列拷貝是怎麼實現的了(深拷貝?淺拷貝?)


接下來看下實際的輸出結果

list: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 100, 15, 16, 17, 18, 19]
sub: [10, 11, 12, 13, 14, 100]
list: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 200, 12, 13, 14, 100, 15, 16, 17, 18, 19]
sub: [10, 200, 12, 13, 14, 100]
list: [10, 200, 12, 13, 14, 100]
sub: [999, 200, 12, 13, 14, 100]
list cl: [BasicTest.InnerC(name=a, id=1), BasicTest.InnerC(name=a5, id=5), BasicTest.InnerC(name=a3, id=3), BasicTest.InnerC(name=a4, id=4)]
list cl2: [BasicTest.InnerC(name=a5, id=5), BasicTest.InnerC(name=a3, id=3)]

out

從上面可以知道,case1/2的分析沒啥問題,case3、4的輸出有點意思了

  • 陣列內為Integer時,兩者互不影響
  • 陣列內為普通物件時,修改其中一個,會影響另外一個

關從輸出結果來看 System.arraycopy 是淺拷貝,至於為什麼int不影響呢,這個就和方法呼叫傳參是基本資料型別時,在方法內部修改引數不會影響到外部一個道理了

II. 其他

盡信書則不如,已上內容,純屬一家之言,因個人能力有限,難免有疏漏和錯誤之處,如發現bug或者有更好的建議,歡迎批評指正,不吝感激

一灰灰blog

相關推薦

真的知道ArrayList#sublist正確用法

我們有這麼一個場景,給你一個列表,可以動態的新增,但是最終要求列表升序,要求長度小於20,可以怎麼做? 這個還不簡單,幾行程式碼就

史上最透徹:為什麼TTL邏輯驅動CMOS要接上拉電阻?知道

除了前一節討論的拉電阻基本使用方法外,上拉電阻也可以提升高電平的電壓閾值,以便於前後級訊號相匹配, 我們經常會看到網上有這種說法:TTL邏輯電平驅動CMOS邏輯電平時,我們通常會新增一個上拉電阻R1,如下圖所示: 大多數人會這麼想:哦,我知道了,下次如果用TLL邏輯驅動CMOS邏輯的話

精通ArrayList,關於ArrayList知道的一切

over which 1.5 lar into intern 執行 發現 數據 精通ArrayList,關於ArrayList你想知道的一切 @(ArrayList)[數據結構|擴容|序列化] [TOC] 前言 在做Java開發中,ArrayList是最常用的數據結構之一

對,飛就是做安全建設的,內網安全有知道的事兒

內網安全who am i 各位博友見置頂博客第一章。信息安全從業7年,帽子沒有白過,也沒有在烏雲等src留過名,但是各位hacker的動機和大事我也關註過,一直從事於企業安全防護工作,識別內網資產的脆弱性,讓hacker小哥哥們不是那麽特別輕易拿下數據和網絡及站點。熱愛生活,熱愛自由。一路走來,沒有敵人,都是

軟考高項考試結束!考試題解析,讓提前知道自己成績

軟考考試已經在上個週末結束了,那麼考試成績成績要什麼時候才能查?怎麼樣才能提前知道自己的成績呢?考試答案在哪找? 考試剛考完,大家都想第一時間知道真題答案,但是網上的真題答案僅僅包括選擇題還不夠詳細,那麼怎麼樣才能獲得完整的真題答案呢?這篇文章給你答案 近期公開課: 科目:資訊

JavaScript中Array方法知道正確開啟方法

前言 在過去的幾個月,我發現我的拉取請求中存在四個完全相同的 JavaScript 錯誤。於是我寫了這篇文章,總結了如何在 JavaScript 中正確使用地使用 Array 的方法! Array物件為JavaScript內建物件,具有以下屬性: 用 Array.includes 代替 Ar

ArrayList中set()和add()中知道的坑

一般使用List集合,估計都是使用這個ArrayList,一般呢也就是簡單遍歷資料和儲存資料。 很少使用到add(int index, E element)和set(int index, E element)兩個方法。 這兩個方法,乍一看,就是在指定的位置插入一條資料。 區別: set()是

知道的Java小知識——動態陣列實現(ArrayList原理)

你不知道的JAVA小知識——動態陣列實現(ArrayList原理) 什麼是陣列 同類資料元素的集合,在計算機中以連續的地址儲存,編譯時確定長度,無法改變。 什麼是動態陣列 資料結構中順序表的物理實現,同類資料元素的集合,在計算機中以連續的地址儲

正確評估SQL資料庫效能,必須知道的原理和方法!

作者簡介: Max Shen(阿特),為了成為資料專家而努力,萬一實現了呢! 昨天寫了一篇如何監視資料庫效能,瞭解資料庫的執行狀態。被有人質疑,說沒有用。說要直接用資料庫的profile和monitor就可以了,到這一步那已經是到了資料庫查詢效能,已經

CPU:網絡卡到底怎麼工作的?

阿Q造訪 我是一個網絡卡,居住在一個機箱內的主機板上,負責整臺計算機的網路通訊,要是沒有我,這裡就成了一個資訊孤島了,那也太無聊了~ 上個週末,伺服器斷電維護了,這是我難得的休息時間,我準備打個盹兒眯一會兒。 這才剛合上眼,CPU一號車間的阿Q跑過來串門了。 “怎麼是你小子,聽說你背後說了我很

深入JDK源碼,這裏總有知道的知識點!

方法 int com 運行時異常 form 成對 adl 拷貝 般的 Java的基礎知識有很多,但是我認為最基礎的知識應該要屬jdk的基礎代碼,jdk的基礎代碼裏面,有分了很多基礎模塊,其中又屬jdk包下面的lang包最為基礎。 我們下面將總結和分析一下lang包下面最為基

1.一男子在路邊一根接著一根地抽煙。一個女士走過來對他說:“嘿,知道你是在慢性自殺嗎?註意看看煙盒上的警告信息。”“沒關系”, 男子悠然自得地又吸了一口:“我是個程序員。”“嗯?這和是程序員有什麽關系?...

我不知道 不知道 對他 上網 是我 .com 一個 但是 err 1.一男子在路邊一根接著一根地抽煙。一個女士走過來對他說:“嘿,你不知道你是在慢性自殺嗎?註意看看煙盒上的警告信息。”“沒關系”,男子悠然自得地又吸了一口:“我是個程序員。”“嗯?這和你是程序員有什麽關系?”

知道嗎?come

影響 views 方法 out 基本 assets else -a span 從大二接觸Java開始,到現在也差不多三個年頭了。從最基礎的HTML、CSS到最後的SSH自己都是一步一個腳印走出來的,其中開心過、失落過、寂寞過。雖然是半道出家但是經過自己的努力也算是完成了“學

C#刨根究底:《必須知道的.NET》讀書筆記系列

wid 最終 table bsp 圖解 萬能 展望 應用 light 一、此書到底何方神聖?   《你必須知道的.NET》來自於微軟MVP—王濤(網名:AnyTao,博客園大牛之一,其博客地址為:http://anytao.cnblogs.com/)的最新技術心得和感悟,

必須知道的.NET》讀書筆記一:小OO有大智慧

實現 職責 可靠性 基本 code cfile 生存 最好 min() 此篇已收錄至《你必須知道的.Net》讀書筆記目錄貼,點擊訪問該目錄可以獲取更多內容。 一、對象   (1)出生:系統首先會在內存中分配一定的存儲空間,然後初始化其附加成員,調用構造函數執行初始化,這

【轉載】史上最全:TensorFlow 好玩的技術、應用和知道的黑科技

tube map 高性能 知識 seq 出現 執行時間 mes lex 【導讀】TensorFlow 在 2015 年年底一出現就受到了極大的關註,經過一年多的發展,已經成為了在機器學習、深度學習項目中最受歡迎的框架之一。自發布以來,TensorFlow 不斷在完善並增加新

【轉載】看完知道什麽是HTTPS了

答案 賬號 不同 傳輸層安全 被人 交互 .html 中間人 非對稱加密 什麽是 HTTPS ? 不管是使用手機還是電腦上網,都離不開數據的通訊 現在互聯網上傳輸數據,普遍使用的是超文本傳輸協議,即 HTTP (HyperText Transfer Protocol) 所以

Swift具體解釋之三----------函數(知道的都在這裏)

sta down type .com taobao opera ota types ref 函數(你想知道的都在這裏) 註:本文為作者自己總結。過於基礎的就不再贅述 ,都是親自測試的結果。如有錯誤或者遺漏的地方。歡迎指正。一起學習。 1、 函

必須知道的改變中國人工智能命運的20個人

.com 翻譯 智能 團隊 自己 世界 榜單 中心 我們 近日,福布斯發表一篇名為《20個推動人工智能改革的科技領導者》的署名文章,介紹了中國頂尖科技公司中的20位致力於人工智能的重要人物,並認為在人工智能領域中國正在挑戰美國的領導地位。在福布斯列出的20位重要人物中,有1

為什麽要開展等級保護測評?必須知道

等保測評 隨著我國信息技術的快速發展,為維護國家安全和社會穩定,維護信息網絡安全,國務院於1994年頒布了《中華人民共和國計算機信息系統安全保護條例》(國務院147號令)。條例中規定:我國的“計算機信息系統實行安全等級保護。 2003年**辦公廳、國務院辦公廳轉發的《國家信息化領導