【Java】ArrayList、LinkedList原理及相关面试题


ArrayList、LinkedList都是 List<T>的实现类。

一、数据结构

ArrayList是数组实现的,LinkedList是链表。

ArrayList内部有个Object[]类型的数组elementData保存数据,同时有个整型size表示列表的大小。如果没有特别设置,elementData数组初始化长度为10,随着使用可能会进行扩容。

LinkedList内部有个Node类,代表一个链表节点。同时使用整型size来保存链表的长度。

二、增删改查

1. ArrayList

ArrayList 增

  1. 扩容:
    往ArrayList添加数据时,会先检查是否需要扩容。
    size == elementData.length时,表示数据数量已经超过了数组容量,需要进行扩容;扩容后的新数组长度为原数组长度的1.5倍
  2. 复制:
    当扩容检查完毕后,如果待添加的条目不是数组中最后一项,则将序号在其后面的所有元素通过 System.arraycopy 往后移一位。
  3. 赋值:
    将新值赋给数组中的对应元素,并将 size++

ArrayList 删

将序号为i的元素删除时,ArrayList会将elementData中序号i + 1 .. size - 1的元素移动到i .. size - 2,即将右侧所有元素往左移动一位;这一步的时间复杂度为O(n)。

ArrayList 改

直接修改数组元素。

ArrayList 查

直接查找数组下标。

2. LinkedList

LinkedList 增

往LinkedList中添加数据时,会创建一个新的Node节点,直接添加到链表尾部。

LinkedList 删

在链表中删除这个节点。

LinkedList 改

通过链表查找找到这个元素,并修改。

LinkedList 查

循环调用 iNode.next 找到对应元素。这一步的时间复杂度为O(n)。

5. 小结

ArrayList与LinkedList效率对比:

  • ArrayList查找元素快,而增删元素慢;
  • LinkedList查找元素慢,而增删元素快。

三、相关面试题

1. ArrayList添加元素和删除元素的效率如何?时间复杂度是多少?ArrayList和LinkedList如何选择?

添加元素
如果添加元素到List末尾,即直接调用add(E)方法,且不用扩容的话,那么时间复杂度为O(1);如果要扩容的话,时间复杂度为O(n);平均时间为O(1);

如果添加元素到List中间,即调用add(index, E)方法,需要将该位置右侧所有的元素向右移动一位。所以时间复杂度决定于插入的位置,越往右越快,最好时间复杂度为O(1),最坏为O(n),平均O(n)。

删除元素
删除元素会使数组中右侧的所有元素往左移动一位,用时取决于删除元素的位置,越往右越快;最好时间复杂度为O(1),最坏为O(n),平均为O(n)。

事实上,ArrayList扩容消耗的时间占总时间比例是很小的;主要耗时的问题出在往数组添加或删除元素,并且这个耗时取决于元素在数组中的位置,序号越小耗时越长。
由于LinkedList中的元素需要额外包裹Node节点,并且LinkedList在指定序号增删元素的时间复杂度也是O(n)(需要先查找),所以绝大多数情况下优先采用ArrayList,除非经常在奇怪的位置插入元素,比如一会儿头插,一会儿尾插,这种情况用LinkedList比较好。如果插入和删除操作都在List的末尾,使用ArrayList即可。

2. ArrayList线程安全吗?为什么?如何解决多线程问题?

ArrayList线程不安全,因为方法没有同步,操作没有原子性,在多线程环境下会出现变量读写异常。比如size++这一步是非原子的,如果两个线程同时执行,可能两个线程分别读了size的值,再分别执行size++,最后size的值变成了size+1而非size+2

多线程环境使用CopyOnWriteArrayList保证线程安全,或者使用Collections.synchronizedList(list),或者给有多线程的操作加锁,或者使用Vector。

3. ArrayList与Vector的区别?ArrayList效率高于Vector吗?

Vector是线程安全版的ArrayList,两者原理相同,不过Vector的方法使用synchronized修饰。另外,Vector扩容是2倍,ArrayList是1.5倍。

理论上,单线程下Vector的效率是低于ArrayList的,因为使用synchronized加锁必定影响效率。
然而实际情况下,两者的效率相差无几,这是因为JVM对synchronized加锁进行了优化,在单线程的条件下其对效率的影响可以忽略不计(【Java】锁升级)。
甚至,由于Vector扩容更快的缘故,以下代码Vector运行时间比ArrayList更短:

        List<Integer> arrList = new ArrayList<>();
        List<Integer> vector = new Vector<>();

        long start = System.nanoTime();
        for (int i = 0; i < 10000000; i++) {
            arrList.add(1);
        }
        System.out.println("arrlist use " + (System.nanoTime() - start));
        start = System.nanoTime();
        for (int i = 0; i < 10000000; i++) {
            vector.add(1);
        }
        System.out.println("vector use " + (System.nanoTime() - start));
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值