ArrayList和LinkedList相信大家都不陌生,是List的两个实现类。
ArrayList底层是由数组实现,LinedList底层则是由链表实现。
对于ArrayList,说他是一个链表,我更倾向于叫他可变数组,它维护了一个数组elementData来保存数据,初始化时,可以给这个数组赋值初始长度,如果不给他赋值,这个数组会在第一次add的时候初始化为默认长度10,此后,每当数组满的时候,就会扩容为之前的1.5倍。
private void add(E e, Object[] elementData, int s) {
if (s == elementData.length)
elementData = grow();
elementData[s] = e;
size = s + 1;
}
private Object[] grow(int minCapacity) {
int oldCapacity = elementData.length;
if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
int newCapacity = ArraysSupport.newLength(oldCapacity,
minCapacity - oldCapacity, /* minimum growth */
oldCapacity >> 1 /* preferred growth */);
return elementData = Arrays.copyOf(elementData, newCapacity);
} else {
return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
}
}
而我们每次在ArrayList中获取元素的时候,就可以直接在elementData这个数组中找,所以无论是顺序访问还是随机访问都很快。
public E get(int index) {
Objects.checkIndex(index, size);
return elementData(index);
}
E elementData(int index) {
return (E) elementData[index];
}
而当我们删除元素时,会将elementData从i + 1位置的数据,依次复制到从 i 位置开始的位置。所以花费的时间会很多。增加元素也是类似的。不赘述。
public E remove(int index) {
Objects.checkIndex(index, size);
final Object[] es = elementData;
@SuppressWarnings("unchecked") E oldValue = (E) es[index];
fastRemove(es, index);
return oldValue;
}
private void fastRemove(Object[] es, int i) {
modCount++;
final int newSize;
if ((newSize = size - 1) > i)
System.arraycopy(es, i + 1, es, i, newSize - i);
es[size = newSize] = null;
}
对于LinkedList而言,他的底层是由链表组成,关键部分就是Node。每次新增元素,他会新建一个Node实例。然后依次将新Node实例的left指向上一个Node,right指向下一个Node。删除则是直接删掉,然后将上一个Node的right指向下一个Node,下一个Node的left指向上一个Node。这样增删就比ArrayList快上许多。
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
public boolean add(E e) {
linkLast(e);
return true;
}
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
然而链表在读取上却要慢上很多,这里的读取指的是随机访问读取,因为链表的地址是不连续的,所以每次读取的话,都要从fisrtNode依次遍历到对应的Index的Node。极其耗时
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
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;
}
}
但是若是顺序遍历,则LinkedList和ArrayList其实差距不大。
但是需要注意的是,LinkedList通过get方法的遍历,都是随机访问,所以要想对LinkedList进行顺序遍历,需要用到迭代器。
下面贴一些测试实例。
public static void main(String[] args) {
List<Integer> arrayList = new ArrayList<>();
List<Integer> linkedList = new LinkedList<>();
int size = 100000;
System.currentTimeMillis();
System.out.println("====================================");
long time1 = System.currentTimeMillis();
System.out.println("arrayList insert start time:" + time1);
for (int i = 0; i < size; i++) {
arrayList.add(i);
}
System.out.println("arrayList insert end time:" + (System.currentTimeMillis() - time1));
System.out.println("====================================");
long time2 = System.currentTimeMillis();
System.out.println("linkedList insert start time:" + time2);
for (int i = 0; i < size; i++) {
linkedList.add(i);
}
System.out.println("linkedList insert end time:" + (System.currentTimeMillis() - time2));
System.out.println("====================================");
long time3 = System.currentTimeMillis();
System.out.println("arrayList read start time:" + time3);
for (int i = 0; i < size; i++) {
arrayList.get(i);
}
System.out.println("arrayList read end time:" + (System.currentTimeMillis() - time3));
System.out.println("====================================");
long time4 = System.currentTimeMillis();
System.out.println("linkedList read start time:" + time4);
for (int i = 0; i < size; i++) {
linkedList.get(i);
}
System.out.println("linkedList read end time:" + (System.currentTimeMillis() - time4));
long time5 = System.currentTimeMillis();
System.out.println("linkedList read start time:" + time4);
for (Integer i : linkedList) {
int i1= i;
}
System.out.println("linkedList read end time:" + (System.currentTimeMillis() - time5));
}
====================================
arrayList insert start time:1697182808522
arrayList insert end time:10
====================================
linkedList insert start time:1697182808533
linkedList insert end time:3
====================================
arrayList read start time:1697182808536
arrayList read end time:1
====================================
linkedList read start time:1697182808537
linkedList read end time:3523
linkedList read start time:1697182808537
linkedList read end time:2
由此我们可以总结出实用场景:
ArrayList: 适用于随机访问比较多,增删不怎么多的场景。
LinkedList: 适用于随机访问较少,增删频繁的场景。
由于ArrayList,增删是复制删除位置到结尾的数组,所以,如果是在数组最后面添加或删除,则会很快,所以如果是这种情况,可以无脑使用Array List。