ArrayList和LinkedList的区别(面试必会)

一.ArrrayList和LinkedList的区别

1.时间复杂度方面

     ArrayList是基于索引的数据接口,它的底层是动态数组,允许所有元素,包括null。ArrayList是非同步的。查询的操作为O(1)时间复杂度,删除、增加操作的时间复杂度为O(n),size,isEmpty,get,set方法的时间复杂度为O(1)。

LinkedList是以链表的形式存储它的数据,实现了List接口,允许null元素。LinkedList还提供额外的get,remove,insert方法在LinkedList的首部或尾部。这些操作使LinkedList可被用作堆栈(stack)、队列(queue)或双向队列(deque)。
注意:LinkedList没有同步方法。如果多个线程同时访问一个List,则必须自己实现访问同步。
一种解决方法是在创建List时构造一个同步的List:
List list = Collections.synchronizedList(new LinkedList(…));

2.空间复杂度方面

(1)ArrayList

ArrayList使用一个内置的数组来存储元素,数组的起始容量是10。当数组需要增长时,
新容量按如下公式获得:新容量=(旧容量*3)/2+1,
新容量即为原来的1.5倍。

PS:Arraylist源码中最大的数组容量是Integer.MAX_VALUE-8,

对于空出的8位,目前解释是 :
①存储Headerwords;
②避免一些机器内存溢出,减少出错几率,所以少分配
③最大还是能支持Integer.MAX_VALUE
当Integer.MAX_VALUE-8依旧无法满足需求时)

(2)LinkedList
LinkedList比ArrayList更占内存,因为LinkedList为每一个节点存储了两个引用,一个指向前一个元素,一个指向下一个元素。
LinkedList中的私有内部类,定义如下:

private static class Entry {
Object element;
Entry next;
Entry previous;
}

PS:如果我们知道一个ArrayList将会有多少个元素,我们可以通过构造方法来指定容量。

当需要插入大量元素时,在插入前可以调用ensureCapacity方法来增加ArrayList的容量以提高插入效率。还可以通过trimToSize方法在ArrayList分配完毕之后去掉浪费掉的空间。

如果涉及到堆栈,队列等操作,应该考虑用List,对于需要快速插入,删除元素,应该使用LinkedList,如果需要快速随机访问元素,应该使用ArrayList。

扩展:ArrayList实现RandomAccess接口,Linked并没有实现RandomAccess接口。但是RandomAccess接口里面是空的!
RandomAccess接口是一个标志接口(Marker)
List集合实现这个接口,就能支持快速随机访问
Collections类中的binarySearch()方法,源码如下:

public static <T>
    int binarySearch(List<? extends Comparable<? super T>> list, T key) {
        if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)
            return Collections.indexedBinarySearch(list, key);
        else
            return Collections.iteratorBinarySearch(list, key);
    } 

indexedBinarySerach(list,key)源码:

  private static <T>
    int indexedBinarySearch(List<? extends Comparable<? super T>> list, T key) {
        int low = 0;
        int high = list.size()-1;

        while (low <= high) {
            int mid = (low + high) >>> 1;
            Comparable<? super T> midVal = list.get(mid);
            int cmp = midVal.compareTo(key);

            if (cmp < 0)
                low = mid + 1;
            else if (cmp > 0)
                high = mid - 1;
            else
                return mid; // key found
        }
        return -(low + 1);  // key not found
    }

iteratorBinarySerach(list,key)源码:

private static <T>
    int iteratorBinarySearch(List<? extends Comparable<? super T>> list, T key)
    {
        int low = 0;
        int high = list.size()-1;
        ListIterator<? extends Comparable<? super T>> i = list.listIterator();

        while (low <= high) {
            int mid = (low + high) >>> 1;
            Comparable<? super T> midVal = get(i, mid);
            int cmp = midVal.compareTo(key);

            if (cmp < 0)
                low = mid + 1;
            else if (cmp > 0)
                high = mid - 1;
            else
                return mid; // key found
        }
        return -(low + 1);  // key not found
    }

通过查看源代码,发现实现RandomAccess接口的List集合采用一般的for循环遍历,而未实现这接口则采用迭代器。

ArrayList用for循环遍历比iterator迭代器遍历快,LinkedList用iterator迭代器遍历比for循环遍历快,所以说,当我们在做项目时,应该考虑到List集合的不同子类采用不同的遍历方式,能够提高性能!

然而有人发出疑问了,那怎么判断出接收的List子类是ArrayList还是LinkedList呢?
这时就需要用instanceof来判断List集合子类是否实现RandomAccess接口!

要求:能够编写代码实现动态数组、明确二者时间复杂度+空间复杂度区别、清楚何时使用合适的数据结构

猜你喜欢

转载自blog.csdn.net/DIDI___/article/details/104016253