前言先来点废话,最近发现每日总结效益太低,不适合博文,因此以后不写每日总结,多写一些干货和学习记录,个人感觉这样更适合我。
最近在学习《数据结构与算法分析-java语言描述》这本书,书的3.3.4小节探索对于remove()方法而言ArrayList和LinkedList的区别
public interface Collection extends Iterable
可以看到,Collection接口继承了Iterable迭代器接口,因此作为其实现类中的两个成员:ArrayList和LinkedList应该都有迭代器相关api的重写
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
public Iterator iterator() {
return new Itr();
}
....
private class Itr implements Iterator {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
Itr() {}
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer super E> consumer) {
Objects.requireNonNull(consumer);
final int size = ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
// update once at end of iteration to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
从源码中我们可以看到,ArrayList重写了迭代器的构造方法,返回了一个内部的迭代器子类,这个子类实现了迭代器的相关API,这里我们着重关注这个迭代器的remove()方法中的一行:
ArrayList.this.remove(lastRet);
可以看出,对于ArrayList而言,利用iterator进行remove本质上还是调用了ArrayList的remove()方法。
我们进一步关注到ArrayList的remove()方法中的一行:
System.arraycopy(elementData, index+1, elementData, index, numMoved);
关于System.arraycopy()这个方法,笔者就不展开赘述了,这里放出一些他的用法:
将元数据复制到目标数组中
public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
//代码解释:
Object src : 原数组
int srcPos : 从元数据的起始位置开始
Object dest : 目标数组
int destPos : 目标数组的开始起始位置
int length : 要copy的数组的长度
关于System.arraycopy方法,有个native标记,代表使用C实现,这里笔者猜测一下估计也是循环挨个修改指针指向,所以认为这个方法需要消耗一个线性时间
最终,我们认为,通过迭代器/ArrayList的remove()方法时间复杂度为O(n)
顺带一提,从上述代码也可以看出,ArrayList底层是封装了一个Object[]的数组去实现。
LinkedList本身没有重写迭代器的构造方法,但是LinkedList的抽象父类AbstractSequentialList重写了迭代器的构造方法:
public Iterator iterator() {
return listIterator();
}
其中listIterator()方法来源于AbstractSequentialList的抽象父类AbstractList:
public ListIterator listIterator() {
return listIterator(0);
}
....
public ListIterator listIterator(final int index) {
rangeCheckForAdd(index);
return new ListItr(index);
}
其中listIterator(int index)这个方法在LinkedList中得到重写:
public ListIterator listIterator(int index) {
checkPositionIndex(index);
return new ListItr(index);
}
....
private class ListItr implements ListIterator {
private Node lastReturned;
private Node next;
private int nextIndex;
private int expectedModCount = modCount;
ListItr(int index) {
// assert isPositionIndex(index);
next = (index == size) ? null : node(index);
nextIndex = index;
}
public boolean hasNext() {
return nextIndex < size;
}
public E next() {
checkForComodification();
if (!hasNext())
throw new NoSuchElementException();
lastReturned = next;
next = next.next;
nextIndex++;
return lastReturned.item;
}
public boolean hasPrevious() {
return nextIndex > 0;
}
public E previous() {
checkForComodification();
if (!hasPrevious())
throw new NoSuchElementException();
lastReturned = next = (next == null) ? last : next.prev;
nextIndex--;
return lastReturned.item;
}
public int nextIndex() {
return nextIndex;
}
public int previousIndex() {
return nextIndex - 1;
}
public void remove() {
checkForComodification();
if (lastReturned == null)
throw new IllegalStateException();
Node lastNext = lastReturned.next;
unlink(lastReturned);
if (next == lastReturned)
next = lastNext;
else
nextIndex--;
lastReturned = null;
expectedModCount++;
}
public void set(E e) {
if (lastReturned == null)
throw new IllegalStateException();
checkForComodification();
lastReturned.item = e;
}
public void add(E e) {
checkForComodification();
lastReturned = null;
if (next == null)
linkLast(e);
else
linkBefore(e, next);
nextIndex++;
expectedModCount++;
}
public void forEachRemaining(Consumer super E> action) {
Objects.requireNonNull(action);
while (modCount == expectedModCount && nextIndex < size) {
action.accept(next.item);
lastReturned = next;
next = next.next;
nextIndex++;
}
checkForComodification();
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
....
private static class Node {
E item;
Node next;
Node prev;
Node(Node prev, E element, Node next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
....
E unlink(Node x) {
// assert x != null;
final E element = x.item;
final Node next = x.next;
final Node prev = x.prev;
if (prev == null) {
first = next;
} else {
prev.next = next;
x.prev = null;
}
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
....
Node node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) {
Node x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
我们重点关注重写的迭代器remove()方法中一行代码:
unlink(lastReturned);
其中Node是一个双向链表结构,对于迭代器的remove()方法,核心是调用unlink()方法进行解除关联,通过对源码中unlink()方法的探索,我们发现unlink方法本质上就是通过移动前驱元和后继元进行删除,消耗常数时间。
除此以外,我们可以发现LinkedList本身的remove方法也是调用的unlink()方法,但是相比迭代器的remove(),LinkedList的效率相对较低,这是因为LinkedList的remove()方法参数是int,LinkedList首先要将int index调用Node的构造方法转化为Node对象再调用unlink()方法,从源码中我们看到这是通过一个循环实现的查找。总之,LinkedList.remove(int index)这个方法需要消耗线性时间,时间复杂度为O(n)
同时,还有一个额外结论:LinkedList底层是一个双向链表的实现
最终,LinkedList的迭代器remove方法时间复杂度为O(1)
public static void removeEvensVer3( List lst ) {
Iterator itr = lst.iterator();
while ( itr.hasNext() ) {
if( itr.next() % 2 == 0 ) {
itr.remove();
}
}
}
在这个源码中:
ArrayList调用的时间复杂度O( n 2 n^2 n2)
LinkedList调用的时间复杂度为O( n n n)
可以看出,通过迭代器的remove()方法(当然包括增强for),LinkedList具有效率上的优势
PS:需要注意一点,直接调用LinkedList.remove是没有这样的优势的,那个本质上还是要先进行一遍搜索,再进行remove,需额外花费一个线性时间
在我的博客查看本文:沙琪玛的博客