AVt天堂网 手机版,亚洲va久久久噜噜噜久久4399,天天综合亚洲色在线精品,亚洲一级Av无码毛片久久精品

當前位置:首頁 > 科技  > 軟件

小小ArrayList,居然這么多坑?!

來源: 責編: 時間:2024-04-02 17:19:01 175觀看
導讀今天,我來和你說說 List 列表操作有哪些坑。Java 的集合類包括 Map 和 Collection 兩大類。Collection 包括 List、Set 和 Queue 三個小類,其中 List 列表集合是最重要也是所有業務代碼都會用到的。今天,我們就從把數組

今天,我來和你說說 List 列表操作有哪些坑。3xd28資訊網——每日最新資訊28at.com

Java 的集合類包括 Map 和 Collection 兩大類。Collection 包括 List、Set 和 Queue 三個小類,其中 List 列表集合是最重要也是所有業務代碼都會用到的。今天,我們就從把數組轉換為 List 集合、對 List 進行切片操作、List 搜索的性能問題等幾個方面著手,來聊聊其中最可能遇到的一些坑。3xd28資訊網——每日最新資訊28at.com

使用 Arrays.asList 把數據轉換為 List 的三個坑

Java 8 中 Stream 流式處理的各種功能,大大減少了集合類各種操作(投影、過濾、轉換)的代碼量。所以,在業務開發中,我們常常會把原始的數組轉換為 List 類數據結構,來繼續展開各種 Stream 操作。3xd28資訊網——每日最新資訊28at.com

你可能也想到了,使用 Arrays.asList 方法可以把數組一鍵轉換為 List,但其實沒這么簡單。接下來,就讓我們看看其中的緣由,以及使用 Arrays.asList 把數組轉換為 List 的幾個坑。3xd28資訊網——每日最新資訊28at.com

在如下代碼中,我們初始化三個數字的 int[]數組,然后使用 Arrays.asList 把數組轉換為 List:3xd28資訊網——每日最新資訊28at.com

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

但,這樣初始化的 List 并不是我們期望的包含 3 個數字的 List。通過日志可以發現,這個 List 包含的其實是一個 int 數組,整個 List 的元素個數是 1,元素類型是整數數組。3xd28資訊網——每日最新資訊28at.com

12:50:39.445 [main] INFO AsListApplication - list:[[I@1c53fd30] size:1 class:class [I

其原因是,只能是把 int 裝箱為 Integer,不可能把 int 數組裝箱為 Integer 數組。我們知道,Arrays.asList 方法傳入的是一個泛型 T 類型可變參數,最終 int 數組整體作為了一個對象成為了泛型類型 T:3xd28資訊網——每日最新資訊28at.com

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

直接遍歷這樣的 List 必然會出現 Bug,修復方式有兩種,如果使用 Java8 以上版本可以使用 Arrays.stream 方法來轉換,否則可以把 int 數組聲明為包裝類型 Integer 數組:3xd28資訊網——每日最新資訊28at.com

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());

修復后的代碼得到如下日志,可以看到 List 具有三個元素,元素類型是 Integer:3xd28資訊網——每日最新資訊28at.com

13:10:57.373 [main] INFO AsListApplication - list:[1, 2, 3] size:3 class:class java.lang.Integer

可以看到第一個坑是,不能直接使用 Arrays.asList 來轉換基本類型數組。 那么,我們獲得了正確的 List,是不是就可以像普通的 List 那樣使用了呢?我們繼續往下看。3xd28資訊網——每日最新資訊28at.com

把三個字符串 1、2、3 構成的字符串數組,使用 Arrays.asList 轉換為 List 后,將原始字符串數組的第二個字符修改為 4,然后為 List 增加一個字符串 5,最后數組和 List 會是怎樣呢?3xd28資訊網——每日最新資訊28at.com

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 的操作失敗了,而且把原始數組的第二個元素從 2 修改為 4 后,asList 獲得的 List 中的第二個元素也被修改為 4 了:3xd28資訊網——每日最新資訊28at.com

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

這里,又引出了兩個坑。3xd28資訊網——每日最新資訊28at.com

第二個坑,Arrays.asList 返回的 List 不支持增刪操作。 Arrays.asList 返回的 List 并不是我們期望的 java.util.ArrayList,而是 Arrays 的內部類 ArrayList。ArrayList 內部類繼承自 AbstractList 類,并沒有覆寫父類的 add 方法,而父類中 add 方法的實現,就是拋出 UnsupportedOperationException。相關源碼如下所示:3xd28資訊網——每日最新資訊28at.com

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();    }}

第三個坑,對原始數組的修改會影響到我們獲得的那個 List。 看一下 ArrayList 的實現,可以發現 ArrayList 其實是直接使用了原始的數組。所以,我們要特別小心,把通過 Arrays.asList 獲得的 List 交給其他方法處理,很容易因為共享了數組,相互修改產生 Bug。3xd28資訊網——每日最新資訊28at.com

修復方式比較簡單,重新 new 一個 ArrayList 初始化 Arrays.asList 返回的 List 即可:3xd28資訊網——每日最新資訊28at.com

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

修改后的代碼實現了原始數組和 List 的“解耦”,不再相互影響。同時,因為操作的是真正的 ArrayList,add 也不再出錯:3xd28資訊網——每日最新資訊28at.com

13:34:50.829 [main] INFO AsListApplication - arr:[1, 4, 3] list:[1, 2, 3, 5]

使用 List.subList 進行切片操作居然會導致 OOM?

業務開發時常常要對 List 做切片處理,即取出其中部分元素構成一個新的 List,我們通常會想到使用 List.subList 方法。但,和 Arrays.asList 的問題類似,List.subList 返回的子 List 不是一個普通的 ArrayList。這個子 List 可以認為是原始 List 的視圖,會和原始 List 相互影響。如果不注意,很可能會因此產生 OOM 問題。接下來,我們就一起分析下其中的坑。3xd28資訊網——每日最新資訊28at.com

如下代碼所示,定義一個名為 data 的靜態 List 來存放 Integer 的 List,也就是說 data 的成員本身是包含了多個數字的 List。循環 1000 次,每次都從一個具有 10 萬個 Integer 的 List 中,使用 subList 方法獲得一個只包含一個數字的子 List,并把這個子 List 加入 data 變量:3xd28資訊網——每日最新資訊28at.com

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,不會占用很大空間,但程序運行不久就出現了 OOM:3xd28資訊網——每日最新資訊28at.com

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)

出現 OOM 的原因是,循環中的 1000 個具有 10 萬個元素的 List 始終得不到回收,因為它始終被 subList 方法返回的 List 強引用。那么,返回的子 List 為什么會強引用原始的 List,它們又有什么關系呢?我們再繼續做實驗觀察一下這個子 List 的特性。3xd28資訊網——每日最新資訊28at.com

首先初始化一個包含數字 1 到 10 的 ArrayList,然后通過調用 subList 方法取出 2、3、4;隨后刪除這個 SubList 中的元素數字 3,并打印原始的 ArrayList;最后為原始的 ArrayList 增加一個元素數字 0,遍歷 SubList 輸出所有元素:3xd28資訊網——每日最新資訊28at.com

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();}

代碼運行后得到如下輸出:3xd28資訊網——每日最新資訊28at.com

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

可以看到兩個現象:3xd28資訊網——每日最新資訊28at.com

原始 List 中數字 3 被刪除了,說明刪除子 List 中的元素影響到了原始 List;3xd28資訊網——每日最新資訊28at.com

嘗試為原始 List 增加數字 0 之后再遍歷子 List,會出現 ConcurrentModificationException。3xd28資訊網——每日最新資訊28at.com

我們分析下 ArrayList 的源碼,看看為什么會是這樣。3xd28資訊網——每日最新資訊28at.com

public class ArrayList<E> extends AbstractList<E>        implements List<E>, RandomAccess, Cloneable, java.io.Serializable {    protected transient int modCount = 0;  private void ensureExplicitCapacity(int minCapacity) {        modCount++;        // overflow-conscious code        if (minCapacity - elementData.length > 0)            grow(minCapacity);    }  public void add(int index, E element) {    rangeCheckForAdd(index);    ensureCapacityInternal(size + 1);  // Increments modCount!!    System.arraycopy(elementData, index, elementData, index + 1,                     size - index);    elementData[index] = element;    size++;  }  public List<E> subList(int fromIndex, int toIndex) {    subListRangeCheck(fromIndex, toIndex, size);    return new SubList(this, offset, 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;    }        public E set(int index, E element) {            rangeCheck(index);            checkForComodification();            return l.set(index+offset, element);        }    public ListIterator<E> listIterator(final int index) {                checkForComodification();                ...    }    private void checkForComodification() {        if (ArrayList.this.modCount != this.modCount)            throw new ConcurrentModificationException();    }    ...  }}

第一,ArrayList 維護了一個叫作 modCount 的字段,表示集合結構性修改的次數。所謂結構性修改,指的是影響 List 大小的修改,所以 add 操作必然會改變 modCount 的值。3xd28資訊網——每日最新資訊28at.com

第二,分析第 21 到 24 行的 subList 方法可以看到,獲得的 List 其實是內部類 SubList,并不是普通的 ArrayList,在初始化的時候傳入了 this。3xd28資訊網——每日最新資訊28at.com

第三,分析第 26 到 39 行代碼可以發現,這個 SubList 中的 parent 字段就是原始的 List。SubList 初始化的時候,并沒有把原始 List 中的元素復制到獨立的變量中保存。我們可以認為 SubList 是原始 List 的視圖,并不是獨立的 List。雙方對元素的修改會相互影響,而且 SubList 強引用了原始的 List,所以大量保存這樣的 SubList 會導致 OOM。3xd28資訊網——每日最新資訊28at.com

第四,分析第 47 到 55 行代碼可以發現,遍歷 SubList 的時候會先獲得迭代器,比較原始 ArrayList modCount 的值和 SubList 當前 modCount 的值。獲得了 SubList 后,我們為原始 List 新增了一個元素修改了其 modCount,所以判等失敗拋出 ConcurrentModificationException 異常。3xd28資訊網——每日最新資訊28at.com

既然 SubList 相當于原始 List 的視圖,那么避免相互影響的修復方式有兩種:3xd28資訊網——每日最新資訊28at.com

一種是,不直接使用 subList 方法返回的 SubList,而是重新使用 new ArrayList,在構造方法傳入 SubList,來構建一個獨立的 ArrayList;3xd28資訊網——每日最新資訊28at.com

另一種是,對于 Java 8 使用 Stream 的 skip 和 limit API 來跳過流中的元素,以及限制流中元素的個數,同樣可以達到 SubList 切片的目的。3xd28資訊網——每日最新資訊28at.com

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

修復后代碼輸出如下:3xd28資訊網——每日最新資訊28at.com

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

可以看到,刪除 SubList 的元素不再影響原始 List,而對原始 List 的修改也不會再出現 List 迭代異常。3xd28資訊網——每日最新資訊28at.com

一定要讓合適的數據結構做合適的事情

在使用 List 集合類的時候,不注意使用場景也會遇見兩個常見誤區。3xd28資訊網——每日最新資訊28at.com

第一個誤區是,使用數據結構不考慮平衡時間和空間。3xd28資訊網——每日最新資訊28at.com

首先,定義一個只有一個 int 類型訂單號字段的 Order 類:3xd28資訊網——每日最新資訊28at.com

@Data@NoArgsConstructor@AllArgsConstructorstatic class Order {    private int orderId;}

然后,定義一個包含 elementCount 和 loopCount 兩個參數的 listSearch 方法,初始化一個具有 elementCount 個訂單對象的 ArrayList,循環 loopCount 次搜索這個 ArrayList,每次隨機搜索一個訂單號:3xd28資訊網——每日最新資訊28at.com

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 中循環 loopCount 次查找隨機訂單號。Map 的 Key 是訂單號,Value 是訂單對象:3xd28資訊網——每日最新資訊28at.com

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 的時間復雜度是 O(n),而 HashMap 的 get 操作的時間復雜度是 O(1)。所以,要對大 List 進行單值搜索的話,可以考慮使用 HashMap,其中 Key 是要搜索的值,Value 是原始對象,會比使用 ArrayList 有非常明顯的性能優勢。3xd28資訊網——每日最新資訊28at.com

如下代碼所示,對 100 萬個元素的 ArrayList 和 HashMap,分別調用 listSearch 和 mapSearch 方法進行 1000 次搜索:3xd28資訊網——每日最新資訊28at.com

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 毫秒。3xd28資訊網——每日最新資訊28at.com

即使我們要搜索的不是單值而是條件區間,也可以嘗試使用 HashMap 來進行“搜索性能優化”。如果你的條件區間是固定的話,可以提前把 HashMap 按照條件區間進行分組,Key 就是不同的區間。3xd28資訊網——每日最新資訊28at.com

的確,如果業務代碼中有頻繁的大 ArrayList 搜索,使用 HashMap 性能會好很多。類似,如果要對大 ArrayList 進行去重操作,也不建議使用 contains 方法,而是可以考慮使用 HashSet 進行去重。說到這里,還有一個問題,使用 HashMap 是否會犧牲空間呢?3xd28資訊網——每日最新資訊28at.com

為此,我們使用 ObjectSizeCalculator 工具打印 ArrayList 和 HashMap 的內存占用,可以看到 ArrayList 占用內存 21M,而 HashMap 占用的內存達到了 72M,是 List 的三倍多。進一步使用 MAT 工具分析堆可以再次證明,ArrayList 在內存占用上性價比很高,77% 是實際的數據(如第 1 個圖所示,16000000/20861992),而 HashMap 的“含金量”只有 22%。3xd28資訊網——每日最新資訊28at.com

所以,在應用內存吃緊的情況下,我們需要考慮是否值得使用更多的內存消耗來換取更高的性能。這里我們看到的是平衡的藝術,空間換時間,還是時間換空間,只考慮任何一個方面都是不對的。3xd28資訊網——每日最新資訊28at.com

第二個誤區是,過于迷信教科書的大 O 時間復雜度。3xd28資訊網——每日最新資訊28at.com

數據結構中要實現一個列表,有基于連續存儲的數組和基于指針串聯的鏈表兩種方式。在 Java 中,有代表性的實現是 ArrayList 和 LinkedList,前者背后的數據結構是數組,后者則是(雙向)鏈表。3xd28資訊網——每日最新資訊28at.com

在選擇數據結構的時候,我們通常會考慮每種數據結構不同操作的時間復雜度,以及使用場景兩個因素。查看這里,你可以看到數組和鏈表大 O 時間復雜度的顯著差異:3xd28資訊網——每日最新資訊28at.com

對于數組,隨機元素訪問的時間復雜度是 O(1),元素插入操作是 O(n);3xd28資訊網——每日最新資訊28at.com

對于鏈表,隨機元素訪問的時間復雜度是 O(n),元素插入操作是 O(1)。3xd28資訊網——每日最新資訊28at.com

那么,在大量的元素插入、很少的隨機訪問的業務場景下,是不是就應該使用 LinkedList 呢?接下來,我們寫一段代碼測試下兩者隨機訪問和插入的性能吧。3xd28資訊網——每日最新資訊28at.com

定義四個參數一致的方法,分別對元素個數為 elementCount 的 LinkedList 和 ArrayList,循環 loopCount 次,進行隨機訪問和增加元素到隨機位置的操作:3xd28資訊網——每日最新資訊28at.com

//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 萬個元素,循環 10 萬次:3xd28資訊網——每日最新資訊28at.com

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());

運行結果可能會讓你大跌眼鏡。在隨機訪問方面,我們看到了 ArrayList 的絕對優勢,耗時只有 11 毫秒,而 LinkedList 耗時 6.6 秒,這符合上面我們所說的時間復雜度;但,隨機插入操作居然也是 LinkedList 落敗,耗時 9.3 秒,ArrayList 只要 1.5 秒:3xd28資訊網——每日最新資訊28at.com

---------------------------------------------ns         %     Task name---------------------------------------------6604199591  100%  linkedListGet011494583  000%  arrayListGetStopWatch '': running time = 10729378832 ns---------------------------------------------ns         %     Task name---------------------------------------------9253355484  086%  linkedListAdd1476023348  014%  arrayListAdd

翻看 LinkedList 源碼發現,插入操作的時間復雜度是 O(1) 的前提是,你已經有了那個要插入節點的指針。但,在實現的時候,我們需要先通過循環獲取到那個節點的 Node,然后再執行插入操作。前者也是有開銷的,不可能只考慮插入操作本身的代價:3xd28資訊網——每日最新資訊28at.com

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 的時間復雜度其實也是 O(n)。繼續做更多實驗的話你會發現,在各種常用場景下,LinkedList 幾乎都不能在性能上勝出 ArrayList。3xd28資訊網——每日最新資訊28at.com

諷刺的是,LinkedList 的作者約書亞 · 布洛克(Josh Bloch),在其推特上回復別人時說,雖然 LinkedList 是我寫的但我從來不用,有誰會真的用嗎?3xd28資訊網——每日最新資訊28at.com

圖片圖片3xd28資訊網——每日最新資訊28at.com

這告訴我們,任何東西理論上和實際上是有差距的,請勿迷信教科書的理論,最好在下定論之前實際測試一下。拋開算法層面不談,由于 CPU 緩存、內存連續性等問題,鏈表這種數據結構的實現方式對性能并不友好,即使在它最擅長的場景都不一定可以發揮威力。3xd28資訊網——每日最新資訊28at.com

小結

今天,我分享了若干和 List 列表相關的錯誤案例,基本都是由“想當然”導致的。3xd28資訊網——每日最新資訊28at.com

第一,想當然認為,Arrays.asList 和 List.subList 得到的 List 是普通的、獨立的 ArrayList,在使用時出現各種奇怪的問題。3xd28資訊網——每日最新資訊28at.com

Arrays.asList 得到的是 Arrays 的內部類 ArrayList,List.subList 得到的是 ArrayList 的內部類 SubList,不能把這兩個內部類轉換為 ArrayList 使用。3xd28資訊網——每日最新資訊28at.com

Arrays.asList 直接使用了原始數組,可以認為是共享“存儲”,而且不支持增刪元素;List.subList 直接引用了原始的 List,也可以認為是共享“存儲”,而且對原始 List 直接進行結構性修改會導致 SubList 出現異常。3xd28資訊網——每日最新資訊28at.com

對 Arrays.asList 和 List.subList 容易忽略的是,新的 List 持有了原始數據的引用,可能會導致原始數據也無法 GC 的問題,最終導致 OOM。3xd28資訊網——每日最新資訊28at.com

第二,想當然認為,Arrays.asList 一定可以把所有數組轉換為正確的 List。當傳入基本類型數組的時候,List 的元素是數組本身,而不是數組中的元素。3xd28資訊網——每日最新資訊28at.com

第三,想當然認為,內存中任何集合的搜索都是很快的,結果在搜索超大 ArrayList 的時候遇到性能問題。我們考慮利用 HashMap 哈希表隨機查找的時間復雜度為 O(1) 這個特性來優化性能,不過也要考慮 HashMap 存儲空間上的代價,要平衡時間和空間。3xd28資訊網——每日最新資訊28at.com

第四,想當然認為,鏈表適合元素增刪的場景,選用 LinkedList 作為數據結構。在真實場景中讀寫增刪一般是平衡的,而且增刪不可能只是對頭尾對象進行操作,可能在 90% 的情況下都得不到性能增益,建議使用之前通過性能測試評估一下。3xd28資訊網——每日最新資訊28at.com

本文鏈接:http://www.tebozhan.com/showinfo-26-80842-0.html小小ArrayList,居然這么多坑?!

聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com

上一篇: Vue3 中有些場景,真不想用 Pinia !

下一篇: 2024年最受歡迎的十個 Vue.js UI 庫

標簽:
  • 熱門焦點
  • K60至尊版剛預熱 一加Ace2 Pro正面硬剛

    Redmi這邊剛如火如荼的宣傳了K60 Ultra的各種技術和硬件配置,作為競品的一加也坐不住了。一加中國區總裁李杰發布了兩條微博,表示在自家的一加Ace2上早就已經采用了和PixelWo
  • 三言兩語說透設計模式的藝術-簡單工廠模式

    一、寫在前面工廠模式是最常見的一種創建型設計模式,通常說的工廠模式指的是工廠方法模式,是使用頻率最高的工廠模式。簡單工廠模式又稱為靜態工廠方法模式,不屬于GoF 23種設計
  • K6:面向開發人員的現代負載測試工具

    K6 是一個開源負載測試工具,可以輕松編寫、運行和分析性能測試。它建立在 Go 和 JavaScript 之上,它被設計為功能強大、可擴展且易于使用。k6 可用于測試各種應用程序,包括 Web
  • 把LangChain跑起來的三個方法

    使用LangChain開發LLM應用時,需要機器進行GLM部署,好多同學第一步就被勸退了,那么如何繞過這個步驟先學習LLM模型的應用,對Langchain進行快速上手?本片講解3個把LangChain跑起來
  • 不容錯過的MSBuild技巧,必備用法詳解和實踐指南

    一、MSBuild簡介MSBuild是一種基于XML的構建引擎,用于在.NET Framework和.NET Core應用程序中自動化構建過程。它是Visual Studio的構建引擎,可在命令行或其他構建工具中使用
  • 10天營收超1億美元,《星鐵》比《原神》差在哪?

    來源:伯虎財經作者:陳平安即便你沒玩過《原神》,你一定聽說過的它的大名。恨它的人把《原神》開服那天稱作是中國游戲史上最黑暗的一天,有粉絲因為索尼在PS平臺上線《原神》,怒而
  • 阿里瓴羊One推出背后,零售企業迎數字化新解

    作者:劉曠近年來隨著數字經濟的高速發展,各式各樣的SaaS應用服務更是層出不窮,但本質上SaaS大多局限于單一業務流層面,對用戶核心關切的增長問題等則沒有提供更好的解法。在Saa
  • 聯想YOGA 16s 2022筆記本將要推出,屏幕支持觸控功能

    聯想此前宣布,將于11月2日19:30召開聯想秋季輕薄新品發布會,推出聯想 YOGA 16s 2022 筆記本等新品。官方稱,YOGA 16s 2022 筆記本將搭載 16 英寸屏幕,并且是一
  • SN570 NVMe SSD固態硬盤 價格與性能兼具

    SN570 NVMe SSD固態硬盤是西部數據發布的最新一代WD Blue系列的固態硬盤,不僅閃存技術更為精進,性能也得到了進一步的躍升。WD Blue SN570 NVMe SSD的包裝外
Top