国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

List列表操作中的坑

這篇具有很好參考價值的文章主要介紹了List列表操作中的坑。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

使用 Arrays.asList 把數(shù)據(jù)轉(zhuǎn)換為 List 的三個坑

在如下代碼中,我們初始化三個數(shù)字的 int[]數(shù)組,然后使用 Arrays.asList 把數(shù)組轉(zhuǎn)換為 List:

int[] arr = {1, 2, 3};
List list = Arrays.asList(arr);
log.info("list:{} size:{} class:{}", list, list.size(), list.get(0).getClass());

但,這樣初始化的 List 并不是我們期望的包含 3 個數(shù)字的 List。通過日志可以發(fā)現(xiàn),這個 List 包含的其實是一個 int 數(shù)組,整個 List 的元素個數(shù)是 1,元素類型是整數(shù)數(shù)組。

12:50:39.445 [main] INFO org.geekbang.time.commonmistakes.collection.aslist.AsListApplication - list:[[I@1c53fd30] size:1 class:class [I

其原因是,只能是把 int 裝箱為 Integer,不可能把 int 數(shù)組裝箱為 Integer 數(shù)組。我們知道,Arrays.asList 方法傳入的是一個泛型 T 類型可變參數(shù),最終 int 數(shù)組整體作為了一個對象成為了泛型類型 T:

public static <T> List<T> asList(T... a) {
    return new ArrayList<>(a);
}

直接遍歷這樣的 List 必然會出現(xiàn) Bug,修復(fù)方式有兩種,如果使用 Java8 以上版本可以使用 Arrays.stream 方法來轉(zhuǎn)換,否則可以把 int 數(shù)組聲明為包裝類型 Integer 數(shù)組:

int[] arr1 = {1, 2, 3};
List list1 = Arrays.stream(arr1).boxed().collect(Collectors.toList());
log.info("list:{} size:{} class:{}", list1, list1.size(), list1.get(0).getClass());


Integer[] arr2 = {1, 2, 3};
List list2 = Arrays.asList(arr2);
log.info("list:{} size:{} class:{}", list2, list2.size(), list2.get(0).getClass());

?修復(fù)后的代碼得到如下日志,可以看到 List 具有三個元素,元素類型是 Integer:

13:10:57.373 [main] INFO org.geekbang.time.commonmistakes.collection.aslist.AsListApplication - list:[1, 2, 3] size:3 class:class java.lang.Integer

可以看到第一個坑是,不能直接使用 Arrays.asList 來轉(zhuǎn)換基本類型數(shù)組。那么,我們獲得了正確的 List,是不是就可以像普通的 List 那樣使用了呢?我們繼續(xù)往下看。

把三個字符串 1、2、3 構(gòu)成的字符串數(shù)組,使用 Arrays.asList 轉(zhuǎn)換為 List 后,將原始字符串數(shù)組的第二個字符修改為 4,然后為 List 增加一個字符串 5,最后數(shù)組和 List 會是怎樣呢?

String[] arr = {"1", "2", "3"};
List list = Arrays.asList(arr);
arr[1] = "4";
try {
    list.add("5");
} catch (Exception ex) {
    ex.printStackTrace();
}
log.info("arr:{} list:{}", Arrays.toString(arr), list);

可以看到,日志里有一個 UnsupportedOperationException,為 List 新增字符串 5 的操作失敗了,而且把原始數(shù)組的第二個元素從 2 修改為 4 后,asList 獲得的 List 中的第二個元素也被修改為 4 了:

java.lang.UnsupportedOperationException
  at java.util.AbstractList.add(AbstractList.java:148)
  at java.util.AbstractList.add(AbstractList.java:108)
  at org.geekbang.time.commonmistakes.collection.aslist.AsListApplication.wrong2(AsListApplication.java:41)
  at org.geekbang.time.commonmistakes.collection.aslist.AsListApplication.main(AsListApplication.java:15)
13:15:34.699 [main] INFO org.geekbang.time.commonmistakes.collection.aslist.AsListApplication - arr:[1, 4, 3] list:[1, 4, 3]

這里,又引出了兩個坑。

第二個坑,Arrays.asList 返回的 List 不支持增刪操作。Arrays.asList 返回的 List 并不是我們期望的 java.util.ArrayList,而是 Arrays 的內(nèi)部類 ArrayList。ArrayList 內(nèi)部類繼承自 AbstractList 類,并沒有覆寫父類的 add 方法,而父類中 add 方法的實現(xiàn),就是拋出 UnsupportedOperationException。相關(guān)源碼如下所示:

public static <T> List<T> asList(T... a) {
    return new ArrayList<>(a);
}

private static class ArrayList<E> extends AbstractList<E>
    implements RandomAccess, java.io.Serializable
{
    private final E[] a;


    ArrayList(E[] array) {
        a = Objects.requireNonNull(array);
    }
...

    @Override
    public E set(int index, E element) {
        E oldValue = a[index];
        a[index] = element;
        return oldValue;
    }
    ...
}

public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
...
public void add(int index, E element) {
        throw new UnsupportedOperationException();
    }
}

第三個坑,對原始數(shù)組的修改會影響到我們獲得的那個 List??匆幌?ArrayList 的實現(xiàn),可以發(fā)現(xiàn) ArrayList 其實是直接使用了原始的數(shù)組。所以,我們要特別小心,把通過 Arrays.asList 獲得的 List 交給其他方法處理,很容易因為共享了數(shù)組,相互修改產(chǎn)生 Bug。

修復(fù)方式比較簡單,重新 new 一個 ArrayList 初始化 Arrays.asList 返回的 List 即可:

String[] arr = {"1", "2", "3"};
List list = new ArrayList(Arrays.asList(arr));
arr[1] = "4";
try {
    list.add("5");
} catch (Exception ex) {
    ex.printStackTrace();
}
log.info("arr:{} list:{}", Arrays.toString(arr), list);

修改后的代碼實現(xiàn)了原始數(shù)組和 List 的“解耦”,不再相互影響。同時,因為操作的是真正的 ArrayList,add 也不再出錯:

13:34:50.829 [main] INFO org.geekbang.time.commonmistakes.collection.aslist.AsListApplication - arr:[1, 4, 3] list:[1, 2, 3, 5]

使用 List.subList 進行切片操作居然會導(dǎo)致 OOM?

業(yè)務(wù)開發(fā)時常常要對 List 做切片處理,即取出其中部分元素構(gòu)成一個新的 List,我們通常會想到使用 List.subList 方法。但,和 Arrays.asList 的問題類似,List.subList 返回的子 List 不是一個普通的 ArrayList。這個子 List 可以認為是原始 List 的視圖,會和原始 List 相互影響。如果不注意,很可能會因此產(chǎn)生 OOM 問題。接下來,我們就一起分析下其中的坑。

如下代碼所示,定義一個名為 data 的靜態(tài) List 來存放 Integer 的 List,也就是說 data 的成員本身是包含了多個數(shù)字的 List。循環(huán) 1000 次,每次都從一個具有 10 萬個 Integer 的 List 中,使用 subList 方法獲得一個只包含一個數(shù)字的子 List,并把這個子 List 加入 data 變量:

private static List<List<Integer>> data = new ArrayList<>();

private static void oom() {
    for (int i = 0; i < 1000; i++) {
        List<Integer> rawList = IntStream.rangeClosed(1, 100000).boxed().collect(Collectors.toList());
        data.add(rawList.subList(0, 1));
    }
}

?你可能會覺得,這個 data 變量里面最終保存的只是 1000 個具有 1 個元素的 List,不會占用很大空間,但程序運行不久就出現(xiàn)了 OOM:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
  at java.util.Arrays.copyOf(Arrays.java:3181)
  at java.util.ArrayList.grow(ArrayList.java:265)

出現(xiàn) OOM 的原因是,循環(huán)中的 1000 個具有 10 萬個元素的 List 始終得不到回收,因為它始終被 subList 方法返回的 List 強引用。那么,返回的子 List 為什么會強引用原始的 List,它們又有什么關(guān)系呢?我們再繼續(xù)做實驗觀察一下這個子 List 的特性。

首先初始化一個包含數(shù)字 1 到 10 的 ArrayList,然后通過調(diào)用 subList 方法取出 2、3、4;隨后刪除這個 SubList 中的元素數(shù)字 3,并打印原始的 ArrayList;最后為原始的 ArrayList 增加一個元素數(shù)字 0,遍歷 SubList 輸出所有元素:

List<Integer> list = IntStream.rangeClosed(1, 10).boxed().collect(Collectors.toList());
List<Integer> subList = list.subList(1, 4);
System.out.println(subList);
subList.remove(1);
System.out.println(list);
list.add(0);
try {
    subList.forEach(System.out::println);
} catch (Exception ex) {
    ex.printStackTrace();
}

代碼運行后得到如下輸出:

[2, 3, 4]
[1, 2, 4, 5, 6, 7, 8, 9, 10]
java.util.ConcurrentModificationException
  at java.util.ArrayList$SubList.checkForComodification(ArrayList.java:1239)
  at java.util.ArrayList$SubList.listIterator(ArrayList.java:1099)
  at java.util.AbstractList.listIterator(AbstractList.java:299)
  at java.util.ArrayList$SubList.iterator(ArrayList.java:1095)
  at java.lang.Iterable.forEach(Iterable.java:74)

可以看到兩個現(xiàn)象:

  • 原始 List 中數(shù)字 3 被刪除了,說明刪除子 List 中的元素影響到了原始 List;
  • 嘗試為原始 List 增加數(shù)字 0 之后再遍歷子 List,會出現(xiàn) ConcurrentModificationException

我們分析下 ArrayList 的源碼,看看為什么會是這樣。

1 public class ArrayList<E> extends AbstractList<E>
2         implements List<E>, RandomAccess, Cloneable, java.io.Serializable
3 {
4     protected transient int modCount = 0;
5   private void ensureExplicitCapacity(int minCapacity) {
6         modCount++;
7         // overflow-conscious code
8         if (minCapacity - elementData.length > 0)
9             grow(minCapacity);
10    }
11  public void add(int index, E element) {
12    rangeCheckForAdd(index);
13
14    ensureCapacityInternal(size + 1);  // Increments modCount!!
15    System.arraycopy(elementData, index, elementData, index + 1,
16                     size - index);
17    elementData[index] = element;
18    size++;
19  }
20
21  public List<E> subList(int fromIndex, int toIndex) {
22    subListRangeCheck(fromIndex, toIndex, size);
23    return new SubList(this, offset, fromIndex, toIndex);
24  }
25
26  private class SubList extends AbstractList<E> implements RandomAccess {
27    private final AbstractList<E> parent;
28    private final int parentOffset;
29    private final int offset;
30    int size;
31
32    SubList(AbstractList<E> parent,
33          int offset, int fromIndex, int toIndex) {
34        this.parent = parent;
35        this.parentOffset = fromIndex;
36        this.offset = offset + fromIndex;
37        this.size = toIndex - fromIndex;
38        this.modCount = ArrayList.this.modCount;
39    }
40
41        public E set(int index, E element) {
42            rangeCheck(index);
43            checkForComodification();
44            return l.set(index+offset, element);
45        }
46
47    public ListIterator<E> listIterator(final int index) {
48                checkForComodification();
49                ...
50    }
51
52    private void checkForComodification() {
53        if (ArrayList.this.modCount != this.modCount)
54            throw new ConcurrentModificationException();
55    }
56    ...
57  }
58}

第一,ArrayList 維護了一個叫作 modCount 的字段,表示集合結(jié)構(gòu)性修改的次數(shù)。所謂結(jié)構(gòu)性修改,指的是影響 List 大小的修改,所以 add 操作必然會改變 modCount 的值。

第二,分析第 21 到 24 行的 subList 方法可以看到,獲得的 List 其實是內(nèi)部類 SubList,并不是普通的 ArrayList,在初始化的時候傳入了 this。

第三,分析第 26 到 39 行代碼可以發(fā)現(xiàn),這個 SubList 中的 parent 字段就是原始的 List。SubList 初始化的時候,并沒有把原始 List 中的元素復(fù)制到獨立的變量中保存。我們可以認為 SubList 是原始 List 的視圖,并不是獨立的 List。雙方對元素的修改會相互影響,而且 SubList 強引用了原始的 List,所以大量保存這樣的 SubList 會導(dǎo)致 OOM。

第四,分析第 47 到 55 行代碼可以發(fā)現(xiàn),遍歷 SubList 的時候會先獲得迭代器,比較原始 ArrayList modCount 的值和 SubList 當前 modCount 的值。獲得了 SubList 后,我們?yōu)樵?List 新增了一個元素修改了其 modCount,所以判等失敗拋出 ConcurrentModificationException 異常。

既然 SubList 相當于原始 List 的視圖,那么避免相互影響的修復(fù)方式有兩種:

  • 一種是,不直接使用 subList 方法返回的 SubList,而是重新使用 new ArrayList,在構(gòu)造方法傳入 SubList,來構(gòu)建一個獨立的 ArrayList;
  • 另一種是,對于 Java 8 使用 Stream 的 skip 和 limit API 來跳過流中的元素,以及限制流中元素的個數(shù),同樣可以達到 SubList 切片的目的。
//方式一:
List<Integer> subList = new ArrayList<>(list.subList(1, 4));

//方式二:
List<Integer> subList = list.stream().skip(1).limit(3).collect(Collectors.toList());

修復(fù)后代碼輸出如下:

[2, 3, 4]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
2
4

可以看到,刪除 SubList 的元素不再影響原始 List,而對原始 List 的修改也不會再出現(xiàn) List 迭代異常。

一定要讓合適的數(shù)據(jù)結(jié)構(gòu)做合適的事情

第一個誤區(qū)是,使用數(shù)據(jù)結(jié)構(gòu)不考慮平衡時間和空間。

首先,定義一個只有一個 int 類型訂單號字段的 Order 類:

@Data
@NoArgsConstructor
@AllArgsConstructor
static class Order {
    private int orderId;
}

然后,定義listSearch (elementCount ,loopCount ),初始化一個具有 elementCount 個訂單對象的 ArrayList,循環(huán) loopCount 次搜索這個 ArrayList,每次隨機搜索一個訂單號:

private static Object listSearch(int elementCount, int loopCount) {
    List<Order> list = IntStream.rangeClosed(1, elementCount).mapToObj(i -> new Order(i)).collect(Collectors.toList());
    IntStream.rangeClosed(1, loopCount).forEach(i -> {
        int search = ThreadLocalRandom.current().nextInt(elementCount);
        Order result = list.stream().filter(order -> order.getOrderId() == search).findFirst().orElse(null);
        Assert.assertTrue(result != null && result.getOrderId() == search);
    });
    return list;
}

隨后,定義另一個 mapSearch 方法,從一個具有 elementCount 個元素的 Map 中循環(huán) loopCount 次查找隨機訂單號。Map 的 Key 是訂單號,Value 是訂單對象:

private static Object mapSearch(int elementCount, int loopCount) {
    Map<Integer, Order> map = IntStream.rangeClosed(1, elementCount).boxed().collect(Collectors.toMap(Function.identity(), i -> new Order(i)));
    IntStream.rangeClosed(1, loopCount).forEach(i -> {
        int search = ThreadLocalRandom.current().nextInt(elementCount);
        Order result = map.get(search);
        Assert.assertTrue(result != null && result.getOrderId() == search);
    });
    return map;
}

我們知道,搜索 ArrayList 的時間復(fù)雜度是 O(n),而 HashMap 的 get 操作的時間復(fù)雜度是 O(1)。所以,要對大 List 進行單值搜索的話,可以考慮使用 HashMap,其中 Key 是要搜索的值,Value 是原始對象,會比使用 ArrayList 有非常明顯的性能優(yōu)勢。

如下代碼所示,對 100 萬個元素的 ArrayList 和 HashMap,分別調(diào)用 listSearch 和 mapSearch 方法進行 1000 次搜索:

int elementCount = 1000000;
int loopCount = 1000;
StopWatch stopWatch = new StopWatch();
stopWatch.start("listSearch");
Object list = listSearch(elementCount, loopCount);
System.out.println(ObjectSizeCalculator.getObjectSize(list));
stopWatch.stop();
stopWatch.start("mapSearch");
Object map = mapSearch(elementCount, loopCount);
stopWatch.stop();
System.out.println(ObjectSizeCalculator.getObjectSize(map));
System.out.println(stopWatch.prettyPrint());

可以看到,僅僅是 1000 次搜索,listSearch 方法耗時 3.3 秒,而 mapSearch 耗時僅僅 108 毫秒。

即使我們要搜索的不是單值而是條件區(qū)間,也可以嘗試使用 HashMap 來進行“搜索性能優(yōu)化”。如果你的條件區(qū)間是固定的話,可以提前把 HashMap 按照條件區(qū)間進行分組,Key 就是不同的區(qū)間。

的確,如果業(yè)務(wù)代碼中有頻繁的大 ArrayList 搜索,使用 HashMap 性能會好很多。類似,如果要對大 ArrayList 進行去重操作,也不建議使用 contains 方法,而是可以考慮使用 HashSet 進行去重。說到這里,還有一個問題,使用 HashMap 是否會犧牲空間呢?

為此,我們使用 ObjectSizeCalculator 工具打印 ArrayList 和 HashMap 的內(nèi)存占用,可以看到 ArrayList 占用內(nèi)存 21M,而 HashMap 占用的內(nèi)存達到了 72M,是 List 的三倍多。進一步使用 MAT 工具分析堆可以再次證明,ArrayList 在內(nèi)存占用上性價比很高,77% 是實際的數(shù)據(jù)(如第 1 個圖所示,16000000/20861992),而 HashMap 的“含金量”只有 22%(如第 2 個圖所示,16000000/72386640)。

List列表操作中的坑,list,數(shù)據(jù)結(jié)構(gòu)

List列表操作中的坑,list,數(shù)據(jù)結(jié)構(gòu)?所以,在應(yīng)用內(nèi)存吃緊的情況下,我們需要考慮是否值得使用更多的內(nèi)存消耗來換取更高的性能。這里我們看到的是平衡的藝術(shù),空間換時間,還是時間換空間,只考慮任何一個方面都是不對的。

第二個誤區(qū)是,過于迷信教科書的大 O 時間復(fù)雜度。

數(shù)據(jù)結(jié)構(gòu)中要實現(xiàn)一個列表,有基于連續(xù)存儲的數(shù)組和基于指針串聯(lián)的鏈表兩種方式。在 Java 中,有代表性的實現(xiàn)是 ArrayList 和 LinkedList,前者背后的數(shù)據(jù)結(jié)構(gòu)是數(shù)組,后者則是(雙向)鏈表。

在選擇數(shù)據(jù)結(jié)構(gòu)的時候,我們通常會考慮每種數(shù)據(jù)結(jié)構(gòu)不同操作的時間復(fù)雜度,以及使用場景兩個因素。查看這里,你可以看到數(shù)組和鏈表大 O 時間復(fù)雜度的顯著差異:

  • 對于數(shù)組,隨機元素訪問的時間復(fù)雜度是 O(1),元素插入操作是 O(n);
  • 對于鏈表,隨機元素訪問的時間復(fù)雜度是 O(n),元素插入操作是 O(1)。

那么,在大量的元素插入、很少的隨機訪問的業(yè)務(wù)場景下,是不是就應(yīng)該使用 LinkedList 呢?接下來,我們寫一段代碼測試下兩者隨機訪問和插入的性能吧。

定義四個參數(shù)一致的方法,分別對元素個數(shù)為 elementCount 的 LinkedList 和 ArrayList,循環(huán) loopCount 次,進行隨機訪問和增加元素到隨機位置的操作:

//LinkedList訪問
private static void linkedListGet(int elementCount, int loopCount) {
    List<Integer> list = IntStream.rangeClosed(1, elementCount).boxed().collect(Collectors.toCollection(LinkedList::new));
    IntStream.rangeClosed(1, loopCount).forEach(i -> list.get(ThreadLocalRandom.current().nextInt(elementCount)));
}

//ArrayList訪問
private static void arrayListGet(int elementCount, int loopCount) {
    List<Integer> list = IntStream.rangeClosed(1, elementCount).boxed().collect(Collectors.toCollection(ArrayList::new));
    IntStream.rangeClosed(1, loopCount).forEach(i -> list.get(ThreadLocalRandom.current().nextInt(elementCount)));
}

//LinkedList插入
private static void linkedListAdd(int elementCount, int loopCount) {
    List<Integer> list = IntStream.rangeClosed(1, elementCount).boxed().collect(Collectors.toCollection(LinkedList::new));
    IntStream.rangeClosed(1, loopCount).forEach(i -> list.add(ThreadLocalRandom.current().nextInt(elementCount),1));
}

//ArrayList插入
private static void arrayListAdd(int elementCount, int loopCount) {
    List<Integer> list = IntStream.rangeClosed(1, elementCount).boxed().collect(Collectors.toCollection(ArrayList::new));
    IntStream.rangeClosed(1, loopCount).forEach(i -> list.add(ThreadLocalRandom.current().nextInt(elementCount),1));
}

測試代碼如下,10 萬個元素,循環(huán) 10 萬次:

int elementCount = 100000;
int loopCount = 100000;
StopWatch stopWatch = new StopWatch();
stopWatch.start("linkedListGet");
linkedListGet(elementCount, loopCount);
stopWatch.stop();
stopWatch.start("arrayListGet");
arrayListGet(elementCount, loopCount);
stopWatch.stop();
System.out.println(stopWatch.prettyPrint());


StopWatch stopWatch2 = new StopWatch();
stopWatch2.start("linkedListAdd");
linkedListAdd(elementCount, loopCount);
stopWatch2.stop();
stopWatch2.start("arrayListAdd");
arrayListAdd(elementCount, loopCount);
stopWatch2.stop();
System.out.println(stopWatch2.prettyPrint());

運行結(jié)果可能會讓你大跌眼鏡。在隨機訪問方面,我們看到了 ArrayList 的絕對優(yōu)勢,耗時只有 11 毫秒,而 LinkedList 耗時 6.6 秒,這符合上面我們所說的時間復(fù)雜度;但,隨機插入操作居然也是 LinkedList 落敗,耗時 9.3 秒,ArrayList 只要 1.5 秒:

---------------------------------------------
ns         %     Task name
---------------------------------------------
6604199591  100%  linkedListGet
011494583  000%  arrayListGet


StopWatch '': running time = 10729378832 ns
---------------------------------------------
ns         %     Task name
---------------------------------------------
9253355484  086%  linkedListAdd
1476023348  014%  arrayListAdd

翻看 LinkedList 源碼發(fā)現(xiàn),插入操作的時間復(fù)雜度是 O(1) 的前提是,你已經(jīng)有了那個要插入節(jié)點的指針。但,在實現(xiàn)的時候,我們需要先通過循環(huán)獲取到那個節(jié)點的 Node,然后再執(zhí)行插入操作。前者也是有開銷的,不可能只考慮插入操作本身的代價:

public void add(int index, E element) {
    checkPositionIndex(index);

    if (index == size)
        linkLast(element);
    else
        linkBefore(element, node(index));
}

Node<E> node(int index) {
    // assert isElementIndex(index);

    if (index < (size >> 1)) {
        Node<E> x = first;
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } else {
        Node<E> x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}

所以,對于插入操作,LinkedList 的時間復(fù)雜度其實也是 O(n)。繼續(xù)做更多實驗的話你會發(fā)現(xiàn),在各種常用場景下,LinkedList 幾乎都不能在性能上勝出 ArrayList。

這告訴我們,任何東西理論上和實際上是有差距的,請勿迷信教科書的理論

重點回顧

第一,想當然認為,Arrays.asList 和 List.subList 得到的 List 是普通的、獨立的 ArrayList,在使用時出現(xiàn)各種奇怪的問題。

  • Arrays.asList 得到的是 Arrays 的內(nèi)部類 ArrayList,List.subList 得到的是 ArrayList 的內(nèi)部類 SubList,不能把這兩個內(nèi)部類轉(zhuǎn)換為 ArrayList 使用。
  • Arrays.asList 直接使用了原始數(shù)組,可以認為是共享“存儲”,而且不支持增刪元素;List.subList 直接引用了原始的 List,也可以認為是共享“存儲”,而且對原始 List 直接進行結(jié)構(gòu)性修改會導(dǎo)致 SubList 出現(xiàn)異常。
  • 對 Arrays.asList 和 List.subList 容易忽略的是,新的 List 持有了原始數(shù)據(jù)的引用,可能會導(dǎo)致原始數(shù)據(jù)也無法 GC 的問題,最終導(dǎo)致 OOM。

第二,想當然認為,Arrays.asList 一定可以把所有數(shù)組轉(zhuǎn)換為正確的 List。當傳入基本類型數(shù)組的時候,List 的元素是數(shù)組本身,而不是數(shù)組中的元素。

第三,想當然認為,內(nèi)存中任何集合的搜索都是很快的,結(jié)果在搜索超大 ArrayList 的時候遇到性能問題。我們考慮利用 HashMap 哈希表隨機查找的時間復(fù)雜度為 O(1) 這個特性來優(yōu)化性能,不過也要考慮 HashMap 存儲空間上的代價,要平衡時間和空間。

第四,想當然認為,鏈表適合元素增刪的場景,選用 LinkedList 作為數(shù)據(jù)結(jié)構(gòu)。在真實場景中讀寫增刪一般是平衡的,而且增刪不可能只是對頭尾對象進行操作,可能在 90% 的情況下都得不到性能增益,建議使用之前通過性能測試評估一下。文章來源地址http://www.zghlxwxcb.cn/news/detail-806504.html

到了這里,關(guān)于List列表操作中的坑的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔相關(guān)法律責任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費用

相關(guān)文章

  • Redis數(shù)據(jù)結(jié)構(gòu)——鏈表list

    Redis數(shù)據(jù)結(jié)構(gòu)——鏈表list

    鏈表是一種常用的數(shù)據(jù)結(jié)構(gòu),提供了順序訪問的方式,而且高效地增刪操作。 Redis中廣泛使用了鏈表,例如:列表的底層實現(xiàn)之一就是鏈表。 在Redis中,鏈表分為兩部分:鏈表信息 + 鏈表節(jié)點。 鏈表節(jié)點用來表示鏈表中的一個節(jié)點,基礎(chǔ)的值和指向前和后的指針 鏈表信息,

    2024年02月13日
    瀏覽(28)
  • day32 泛型 數(shù)據(jù)結(jié)構(gòu) List

    ?概述 ? ? ? ? JDK1.5同時推出了兩個和集合相關(guān)的特性:增強for循環(huán),泛型 ????????泛型可以修飾泛型類中的屬性,方法返回值,方法參數(shù), 構(gòu)造函數(shù)的參數(shù) Java提供的泛型類/接口 Collection, List, Set,Iterator 等 自定義的泛型 public class Student H,W {} 自定義的泛型方法 public

    2024年02月09日
    瀏覽(19)
  • 數(shù)據(jù)結(jié)構(gòu)編程題:Phone List

    Given a list of phone numbers, determine if it is consistent in the sense that no number is the prefix of another. Let’s say the phone catalogue listed these numbers: 段落大意:給定一組電話號碼,判斷它們是否一致,即沒有一個號碼是另一個號碼的前綴。假設(shè)電話目錄列出了以下號碼: Emergency 911 Alice 97 625 999

    2024年01月22日
    瀏覽(28)
  • 3.redis數(shù)據(jù)結(jié)構(gòu)之List

    列表類型:有序、可重復(fù) Arraylist是使用數(shù)組來存儲數(shù)據(jù),特點:查詢快、增刪慢 Linkedlist是使用雙向鏈表存儲數(shù)據(jù),特點:增刪快、查詢慢,但是查詢鏈表兩端的數(shù)據(jù)也很快。 Redis的list是采用來鏈表來存儲的,所以對于redis的list數(shù)據(jù)類型的操作,是操作list的兩端數(shù)據(jù)來操作

    2024年02月11日
    瀏覽(58)
  • 數(shù)據(jù)結(jié)構(gòu)與算法 | 鏈表(Linked List)

    數(shù)據(jù)結(jié)構(gòu)與算法 | 鏈表(Linked List)

    鏈表(Linked List)是一種線性數(shù)據(jù)結(jié)構(gòu),它由一系列節(jié)點(Node)組成,每個節(jié)點包含兩部分:數(shù)據(jù)和指向下(上)一個節(jié)點的引用(或指針)。鏈表中的節(jié)點按照線性順序連接在一起(相鄰節(jié)點不需要存儲在連續(xù)內(nèi)存位置),不像數(shù)組一樣存儲在連續(xù)的內(nèi)存位置。鏈表通常由

    2024年02月08日
    瀏覽(24)
  • 數(shù)據(jù)結(jié)構(gòu)之List(雙向鏈表)的實現(xiàn)

    方法名 參數(shù) 功能 返回 find const T val, int n, listNode * p 區(qū)間查找 從p往前數(shù)n個節(jié)點 指針或NULL const T val, listNode * p 區(qū)間查找 從p往前到首節(jié)點 const T val 查找 Size void 鏈表規(guī)模 size empty void 判空 bool first void 返回首節(jié)點 首節(jié)點指針 clear void 清空鏈表 void insertAsFirst const T val 作為首節(jié)

    2024年02月16日
    瀏覽(25)
  • 數(shù)據(jù)結(jié)構(gòu)英文習題解析-第二章 鏈表List

    前言:最近快到FDS考試了,po重刷了一下學校的題目,自己整理了一些解析orz 因為po在自己找解析和學習的過程中非常痛苦,所以在此共享一下我的題目和自己寫的解題思路,歡迎各位指出錯誤~全章節(jié)預(yù)計會陸續(xù)更新,可在專欄查看~ HW2 1. For a sequentially stored linear list of leng

    2024年04月11日
    瀏覽(26)
  • Map,List,Set 等集合以及底層數(shù)據(jù)結(jié)構(gòu)

    Map,List,Set 等集合以及底層數(shù)據(jù)結(jié)構(gòu)

    集合類存放于java.util包中。集合類存放的都是對象的引用,而非對象本身。常見的集合主要有三種——Set(集)、List(列表)和Map(映射)。其中,List和Set 都 實現(xiàn) 了 Collection 接口,并且List和Set也是接口,而 Map 為獨立接口 。常見的實現(xiàn)類如下: List 的實現(xiàn)類有:ArrayList、

    2024年02月09日
    瀏覽(21)
  • 【數(shù)據(jù)結(jié)構(gòu)】 List與順序表及接口的實現(xiàn)

    【數(shù)據(jù)結(jié)構(gòu)】 List與順序表及接口的實現(xiàn)

    在集合框架中, List是一個接口,繼承自Collection。 Collection也是一個接口,該接口中規(guī)范了后序容器中常用的一些方法,具體如下所示: Iterable也是一個接口,表示實現(xiàn)該接口的類是可以逐個元素進行遍歷的,具體如下: List 的官方文檔 站在數(shù)據(jù)結(jié)構(gòu)的角度來看, List就是一

    2024年02月12日
    瀏覽(21)
  • 浙大數(shù)據(jù)結(jié)構(gòu)第二周之02-線性結(jié)構(gòu)3 Reversing Linked List

    Given a constant?K?and a singly linked list?L, you are supposed to reverse the links of every?K?elements on?L. For example, given?L?being 1→2→3→4→5→6, if?K=3, then you must output 3→2→1→6→5→4; if?K=4, you must output 4→3→2→1→5→6. Input Specification: Each input file contains one test case. For each case, the first line

    2024年02月15日
    瀏覽(24)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包