为什么推荐java程序员尽量用arrayList而不是linkedlist

背景

我们都知道linkedlist底层是基于链表实现,ArrayList是基于数组实现。
而使用他们的场合分别有顺序插入,随机插入,顺序读取,随机读取,删除五大常见场景。

五个场景的比较

  • 随机插入:从底层实现原理上,linkedList明显优于arrayList,但是实际场景中我们难以见到随机插入的情况。同时,在随机插入的结构上,我们有更好的选择,那就是map结构和set结构。都比linkedList性能要好。
  • 顺序读取,随机读取,毫无疑问,基于数组的ArrayList更优秀
  • 删除:这是linkedList唯一胜出的地方,那就是队列。只有做队列时,我们才会优先考虑LinkeList

接下来是顺序插入这个常见误区的比较了

两者插入的区别-扩容机制

因为我们知道数组是定长的,为了实现不定长的list。底层实现了一套动态扩容机制。

Java中的ArrayList在以下情况会扩容:

  1. 当通过add()方法添加元素,且当前数组容量(capacity)不足以容纳新的元素时。
  2. 执行addAll()方法添加多个元素,如果添加后总元素数量超过现有容量。

ArrayList的底层扩容机制如下:

  • 默认初始容量:当创建一个空的ArrayList时,默认容量为10。
  • 扩容检查:每次调用add()等增加元素的方法前,都会调用ensureCapacityInternal()方法来确保有足够的空间存放新元素。若当前容量不足,则会进行扩容操作。
  • 扩容策略:扩容时,ArrayList通常不是仅仅增加一单位容量,而是按照一定比例扩大容量。在JDK 1.8及之后版本中,扩容的具体实现是将容量翻倍(即新容量 = 原容量 * 2),然后向上取整到大于等于最小需要容量的数值。
  • 具体扩容过程:
    • 创建一个新的数组,大小为新计算出的容量。
    • 将原数组中的所有元素复制到新数组中。
    • 更新内部数据结构,使得ArrayList引用新的、更大容量的数组。

扩容是一个相对耗资源的操作,因为它涉及到内存分配和数组元素的拷贝。因此,在预知 ArrayList 大致规模的情况下,可以预先通过ensureCapacity(int minCapacity)方法手动指定一个初始容量或期望的容量,以减少扩容次数。

常见误区

而且因为arrayList底层是数组,那么因为数组是定长的。那么一旦达到阈值,就会让数组进行扩容。那么因为arrayList多了一个扩容的操作。许多人认为arraylist不适合顺序插入。事实正好相反。以下为测试代码

public void testList(List<Integer> list, String logPre, int loopCount) {
        long startMill = System.currentTimeMillis();
        for (int i = 0; i < loopCount; i++) {
            list.add(i);
        }
        long endMill = System.currentTimeMillis();
        double value = endMill - startMill;
        System.out.printf("%s loopCount:%s costSec:%s\n", logPre, loopCount, value / 1000);
    }

    @Test
    public void testLinkedList() {
        testList(new LinkedList<>(), "linkedList", 10);
        testList(new LinkedList<>(), "linkedList", 100);
        testList(new LinkedList<>(), "linkedList", 10000);
        testList(new LinkedList<>(), "linkedList", 1000000);
        testList(new LinkedList<>(), "linkedList", 100000000);
        testList(new LinkedList<>(), "linkedList", Integer.MAX_VALUE / 20);
    }

    @Test
    public void testArrayList() {
        testList(new ArrayList<>(), "arrayList", 10);
        testList(new ArrayList<>(), "arrayList", 100);
        testList(new ArrayList<>(), "arrayList", 10000);
        testList(new ArrayList<>(), "arrayList", 1000000);
        testList(new ArrayList<>(), "arrayList", 100000000);
        testList(new ArrayList<>(), "arrayList", Integer.MAX_VALUE / 20);
    }

运行结果

arrayList loopCount:10 costSec:0.0
arrayList loopCount:100 costSec:0.0
arrayList loopCount:10000 costSec:0.002
arrayList loopCount:1000000 costSec:0.259
arrayList loopCount:100000000 costSec:3.053
arrayList loopCount:107374182 costSec:3.397

linkedList loopCount:10 costSec:0.0
linkedList loopCount:100 costSec:0.0
linkedList loopCount:10000 costSec:0.0
linkedList loopCount:1000000 costSec:0.007
linkedList loopCount:100000000 costSec:4.817
linkedList loopCount:107374182 costSec:23.747

随着数量的增多。linkedList的插入越发的花费时间。

以上的结果让我很吃惊。只能说明动态扩容其实不怎么消耗所谓的时间,代价非常小。因为每次扩容都是批量式的开辟内存空间。而linkedList这是因为在内存中是分散的,每次都需要jvm重新开辟内存。

总结

由上面的测试可知,除了队列场景之外,都应该优先选择ArrayList来作为队列存储的实现。
如果涉及到大量的随机插入的情况,可以考虑使用树结构。

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值