ArrayList与LinkedList


ArrayList

简介

底层由数组,初始化数据为0,容量动态增长,在add的时候容量默认为10,每次扩容为原来的1.5倍,把原来的数组复制到新的数组,再把原来的地址指向新的地址

特性

查询速度比较快,增删效率比较低,增删都需要进行数组复制以及移动

扩容机制

JDK1.8中,如果通过无参构造的话,初始数组容量为0,当真正对数组进行添加的时候,才开始分配容量,默认为10;当容量不足,则以1.5倍扩容;

add操作

执行add方法,先判断ArrayList当前容量是否满足size+1的容量; 在判断是否满足size+1容量时,先判断ArrayList是否为空;如果为空,初始化ArrayList为10,再判断初始容量是否满足最低要求;如果不为空,则直接判断初始容量是否满足最低要求;若满足最低容量要求,直接添加;若不满足,则先扩容,再添加;

LinkedList

基础

底层带有头节点和尾节点的双向链表,所以包含头插法与尾插法,所以适合增删的场景,由于是链表查询,需要一个个按顺序遍历对比,查询速度较慢

两者的对比

  1. ArrayList基于动态数组,LinkedList基于链表
  2. 对于随机访问的get与set,ArrayList优于LinkedList,因为LinkedList需要移动指针
  3. 对于增删,如果数据比较大,LinkedList优于ArrayList,因为ArrayList需要移动数据
  4. 都不保证线程安全
  5. ArrayList在空间的浪费主要体现在list列表的结尾都会预留一定容量的空间,LinkedList空间的花费则体现在每个数据都需要消耗比ArrayList更多的空间(需要存放直接前驱与后继)

当操作是在一列数据的后面添加数据而不是在前面或中间,并且需要随机地访问其中的元素时,使用ArrayList会有更好的性能;当操作是在一列数据的前面或中间添加或删除数据,并且按照顺序访问其中的元素时,就应该使用LinkedList了


常见面试题

1. 数组越界

创建ArrayList后直接调用了add(int index, E element)的方法,结果返回了一个索引越界的异常

public static void main(String[] args) { 
   List<String> list = new ArrayList<String>(10); 
   list.add(2, "1");         
       System.out.println(list.get(0));
}

Exception in thread “main” java.lang.IndexOutOfBoundsException: Index: 2, Size: 0 at java.util.ArrayList.rangeCheckForAdd(ArrayList.java:665) at java.util.ArrayList.add(ArrayList.java:477) at org.itstack.interview.test.ApiTest.main(ApiTest.java:13)

解答: ArrayList的一个方法

/**
 * A version of rangeCheck used by add and addAll. 
 */
 private void rangeCheckForAdd(int index) {
    if (index > size || index < 0)
        throw new  IndexOutOfBoundsException(outOfBoundsMsg(index));
 }

rangeCheckForAdd函数中的判断是index > size || index < 0 就会抛出下标越界异常,再来看看size的定义

/**
 * The size of the ArrayList (the number of elements it contains). * * @serial
 */
private int size;

/**
 * Returns the number of elements in this list. * * @return the number of elements in this list
 */
 public int size() {
    return size;
}

从size定义的注释和size()函数就能很明显的看出,size代表的是ArrayList中有多少个元素。只有当调用ArrayList的add()和remove()方法时,才会对size进行操作(size++, –size),所以当我们新建ArrayList时,size=0,此时调用add(E element)直接添加元素是没有问题的,而此时调用add(int index, E element),若index > 0就会抛出下标越界异常

补充知识

面试常问:快速失败 (fail-fast) 和安全失败 (fail-safe) 的区别是什么?

-快速失败:快速失败的迭代器会抛出ConcurrentModificationException异常。当迭代一个集合的时候,如果有另外一个线程在修改这个集合,就会抛出ConcurrentModification异常。不能在多线程下发生并发修改。 安全失败:java.util.concurrent包下面的所有的类都是安全失败的。Iterator的安全失败是基于对底层集合做拷贝,因此,它不受源集合上修改的影响。采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。




Enjoy Reading This Article?

Here are some more articles you might like to read next:

  • 2379. Minimum Recolors to Get K Consecutive Black Blocks
  • 2471. Minimum Number of Operations to Sort a Binary Tree by Level
  • 1387. Sort Integers by The Power Value
  • 2090. K Radius Subarray Averages
  • 2545. Sort the Students by Their Kth Score