成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專欄INFORMATION COLUMN

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

loonggg / 3549人閱讀

摘要:我們有這么一個(gè)場(chǎng)景,給你一個(gè)列表,可以動(dòng)態(tài)的新增,但是最終要求列表升序,要求長(zhǎng)度小于,可以怎么做這個(gè)還不簡(jiǎn)單,幾行代碼就可以了測(cè)試驗(yàn)證上面的代碼先不考慮性能的優(yōu)化方面,有沒(méi)有問(wèn)題寫了個(gè)簡(jiǎn)單的測(cè)試,我們來(lái)看下會(huì)出現(xiàn)什么情況啟動(dòng)參數(shù)修改

我們有這么一個(gè)場(chǎng)景,給你一個(gè)列表,可以動(dòng)態(tài)的新增,但是最終要求列表升序,要求長(zhǎng)度小于20,可以怎么做?

這個(gè)還不簡(jiǎn)單,幾行代碼就可以了

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

1. 測(cè)試驗(yàn)證

上面的代碼先不考慮性能的優(yōu)化方面,有沒(méi)有問(wèn)題?

寫了個(gè)簡(jiǎn)單的測(cè)試case,我們來(lái)看下會(huì)出現(xiàn)什么情況

@Test
public void testTri() throws InterruptedException {
    List 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);
    }
}

啟動(dòng)參數(shù)修改下,添加jvm最大內(nèi)存條件 -Xmx3m, 然后跑上面代碼,一段時(shí)間之后居然出現(xiàn)stack over flow

有意思的問(wèn)題來(lái)了,從邏輯上看,這個(gè)數(shù)組固定長(zhǎng)度為20,頂多有21條數(shù)據(jù),怎么就會(huì)內(nèi)存溢出呢?

2. SubList 方法揭秘

我們看下ArrayList#sublis方法的實(shí)現(xiàn)邏輯,就可以發(fā)現(xiàn)獲取子列表,居然只是重置了一下內(nèi)部數(shù)組的索引

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

private class SubList extends AbstractList implements RandomAccess {
    private final AbstractList parent;
    private final int parentOffset;
    private final int offset;
    int size;
  
    SubList(AbstractList 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;
    }
    ...
}

返回的是一個(gè)SubList類型對(duì)象,這個(gè)對(duì)象和原來(lái)的List公用一個(gè)存儲(chǔ)數(shù)據(jù)的數(shù)組,但是多了兩個(gè)記錄子列表起始的偏移;

然后再看下SubList的add方法,也是直接在原來(lái)的數(shù)組中新增數(shù)據(jù),想到與原來(lái)的列表在指定位置插入數(shù)據(jù)

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

所以上面實(shí)現(xiàn)的代碼中 list = list.subList(0, 20); 這一行,有內(nèi)存泄露,貌似是只返回了一個(gè)20長(zhǎng)度大小的列表,但是這個(gè)列表中的數(shù)組長(zhǎng)度,可能遠(yuǎn)遠(yuǎn)不止20

為了驗(yàn)證上面的說(shuō)法,debug下上面的測(cè)試用例

動(dòng)圖演示如下

3. 正確使用姿勢(shì)

上面知道sublist并不會(huì)新創(chuàng)建一個(gè)列表,舊的數(shù)據(jù)依然還在,只是我們用不了而已,所以改動(dòng)也很簡(jiǎn)單,根據(jù)sublist的結(jié)果創(chuàng)建一個(gè)新的數(shù)組就好了

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

再次測(cè)試,代碼一直在順利的執(zhí)行,看下后面的計(jì)數(shù),都已經(jīng)5w多,前面1w多久報(bào)錯(cuò)了

雖然上面解決了內(nèi)存泄露,但是gc也很頻繁了,本篇的重點(diǎn)主要是指出sublist的錯(cuò)誤使用姿勢(shì),所以上面算法的優(yōu)化就不詳細(xì)展開(kāi)了

4. 知識(shí)點(diǎn)擴(kuò)展

看下下面的測(cè)試代碼輸出應(yīng)該是什么

@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 list = new ArrayList<>();
    for (int i = 0; i < 20; i++) {
        list.add(i);
    }

    // case 1
    List 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 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 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);
}

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

針對(duì)case1/2,我們知道sublist返回的列表和原列表公用一個(gè)底層數(shù)組,所以這兩個(gè)列表的增刪,都是相互影響的

case1 執(zhí)行之后相當(dāng)于在list數(shù)組的下標(biāo)15這里,插入數(shù)據(jù)100

case2 執(zhí)行之后,list的下標(biāo)11,相當(dāng)于sub的下標(biāo)1,也就是說(shuō)sub[1] 變成了200

對(duì)于case3/4 而言,根據(jù)sub創(chuàng)建了一個(gè)新的列表,這個(gè)時(shí)候修改新的列表中的值,會(huì)影響到原來(lái)的列表中的值么?

分析這個(gè)場(chǎng)景,就需要看一下源碼了

public ArrayList(Collection 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;
    }
}

// 對(duì)應(yīng)的核心邏輯就在 Arrays.copyOf,而這個(gè)方法主要調(diào)用的是native方法`System.arraycopy`

public static  T[] copyOf(U[] original, int newLength, Class 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;
}

從上面的源碼分析,會(huì)不會(huì)相互影響就看這個(gè)數(shù)組拷貝是怎么實(shí)現(xiàn)的了(深拷貝?淺拷貝?)

接下來(lái)看下實(shí)際的輸出結(jié)果

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)]

從上面可以知道,case1/2的分析沒(méi)啥問(wèn)題,case3、4的輸出有點(diǎn)意思了

數(shù)組內(nèi)為Integer時(shí),兩者互不影響

數(shù)組內(nèi)為普通對(duì)象時(shí),修改其中一個(gè),會(huì)影響另外一個(gè)

關(guān)從輸出結(jié)果來(lái)看 System.arraycopy 是淺拷貝,至于為什么int不影響呢,這個(gè)就和方法調(diào)用傳參是基本數(shù)據(jù)類型時(shí),在方法內(nèi)部修改參數(shù)不會(huì)影響到外部一個(gè)道理了

II. 其他

盡信書則不如,已上內(nèi)容,純屬一家之言,因個(gè)人能力有限,難免有疏漏和錯(cuò)誤之處,如發(fā)現(xiàn)bug或者有更好的建議,歡迎批評(píng)指正,不吝感激

微博地址: 小灰灰Blog

QQ: 一灰灰/3302797840

個(gè)人博客站點(diǎn) 一灰灰Blog: https://liuyueyi.github.io/he...

一灰灰blog

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/74653.html

相關(guān)文章

  • 尋找Java中String.split性能更好方法

    摘要:有沒(méi)有更快的方法如果分隔符不是單字符而且也不需要按正則分隔的話,使用的方法還會(huì)和一樣使用正則表達(dá)式。使用分隔字符串,針對(duì)不需要按正則分隔的場(chǎng)景提供更好的實(shí)現(xiàn),分隔符支持字符串。 String.split 是Java里很常用的字符串操作,在普通業(yè)務(wù)操作里使用的話并沒(méi)有什么問(wèn)題,但如果需要追求高性能的分割的話,需要花一點(diǎn)心思找出可以提高性能的方法。 String.split方法的分割參數(shù)r...

    QiShare 評(píng)論0 收藏0
  • 了解集合世界fail-fast機(jī)制 和 CopyOnWriteArrayList 源碼詳解

    摘要:體現(xiàn)的就是適配器模式。數(shù)組對(duì)象集合世界中的機(jī)制機(jī)制集合世界中比較常見(jiàn)的錯(cuò)誤檢測(cè)機(jī)制,防止在對(duì)集合進(jìn)行遍歷過(guò)程當(dāng)中,出現(xiàn)意料之外的修改,會(huì)通過(guò)異常暴力的反應(yīng)出來(lái)。而在增強(qiáng)循環(huán)中,集合遍歷是通過(guò)進(jìn)行的。 前言 學(xué)習(xí)情況記錄 時(shí)間:week 2 SMART子目標(biāo) :Java 容器 記錄在學(xué)習(xí)Java容器 知識(shí)點(diǎn)中,關(guān)于List的重點(diǎn)知識(shí)點(diǎn)。 知識(shí)點(diǎn)概覽: 容器中的設(shè)計(jì)模式 從Array...

    young.li 評(píng)論0 收藏0
  • 源碼|jdk源碼-ArrayList與Vector源碼閱讀

    摘要:畢業(yè)兩個(gè)星期了,開(kāi)始成為一名正式的碼農(nóng)了。將指定位置的數(shù)據(jù)移除。但是問(wèn)題是,為時(shí),并不是直接一個(gè)大小為的數(shù)組,而是使用靜態(tài)變量來(lái)代替。此外,函數(shù)還做了越界檢查。返回迭代器,與之有一個(gè)搭配的輔助類。 畢業(yè)兩個(gè)星期了,開(kāi)始成為一名正式的java碼農(nóng)了。一直對(duì)偏底層比較感興趣,想著深入自己的java技能,看書、讀源碼、總結(jié)、造輪子實(shí)踐都是付諸行動(dòng)的方法。說(shuō)到看源碼,就應(yīng)該由簡(jiǎn)入難,逐漸加深,...

    0x584a 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

閱讀需要支付1元查看
<