上一次整理详细说了各种Set的区别,这次再来整理以下各List。
首先是LinkedList:
允许有null元素,主要用于创建链表数据结构
没有同步方法,如果多个线程同时访问一个List,则必须自己实现访问同步,解决方法是在创建List的时候构造一个同步的List:
List list = Collections.synchronizedList(new LinkedList<>());
LinkedList查找效率低。
为什么查找效率低呢?
我们来看看LinkedList的查找方法LinkedList.get(index i)的底层源码
public class Test{ public static void main(String[] args) { LinkedList<String> linkedList = new LinkedList<String>(); linkedList.add("a"); linkedList.add("b"); linkedList.add("c"); System.out.println(linkedList); System.out.println(linkedList.get(1)); } }
对于这段测试代码,输出结果是
[a, b, c]
b
使用IDEA对linkedList.add的源码进行查看,得到如下代码:
public E get(int index) { checkElementIndex(index); return node(index).item; }
这段代码里用到了node方法(即查找编号所对应的链表节点),我们再来看看这个node的代码:
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; } }
这里index<(size >> 1) >>指的是二进制右移一位,对于十进制数据来说,可以近似相当于对原始数据除以2。(整除,不要小数部分)
所以这里就是指如果先把整体对半切开,如果index在前半部分的(<size/2),那就从第一个节点开始向后查找;而如果index在后半部分的,那就从最后一个节点开始向前查找。查找的方式是利用循环迭代,一个个向后(向前)查找,这种查找方式便是查找缓慢的原因(对于一个较大的链表来说,这样的查找方式会导致循环很多次)。
然后是ArrayList:
该类也是实现了List的接口,实现了可变大小的数组,随机访问和遍历元素时,提供更好的性能。该类也是非同步的,在多线程的情况下不要使用。超出容量时ArrayList 增长当前长度的50%,插入删除效率低。
下面是ArrayList增长长度的源码
private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); } private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
可以看到是oldCapacity + (oldCapacity >> 1)(即old+old/2,增加50%)。
对于查找速度快,是因为ArrayList是以数组方式存储的,所以访问元素只需要根据元素的下表访问对应的元素即可。下面依旧列出源码:
public E get(int index) { rangeCheck(index); return elementData(index); }
E elementData(int index) { return (E) elementData[index]; }
可以看到是直接return对应元素(以数组方式存储的数据,地址连续,下标也就代表着对应指针增量,数组名为头指针,所以可以根据下标直接取对应元素,但相应得,扩容时,程序会开辟一块全新的存储空间,并将原始数据复制进新的地址空间里,这相比链表来说效率是更低的。)
至于插入删除为何效率低,在java集合框架(一)中已有说到,这里不再重复说明。
常用的List便是以上两种。