LinkedList与ArrayList通过Iterator迭代器源码效率探究

前言先来点废话,最近发现每日总结效益太低,不适合博文,因此以后不写每日总结,多写一些干货和学习记录,个人感觉这样更适合我。
最近在学习《数据结构与算法分析-java语言描述》这本书,书的3.3.4小节探索对于remove()方法而言ArrayList和LinkedList的区别

ArrayList

Collection接口源码

public interface Collection extends Iterable

可以看到,Collection接口继承了Iterable迭代器接口,因此作为其实现类中的两个成员:ArrayList和LinkedList应该都有迭代器相关api的重写

ArrayList的源码

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 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本身没有重写迭代器的构造方法,但是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 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,需额外花费一个线性时间
在我的博客查看本文:沙琪玛的博客

你可能感兴趣的:(java源码,数据结构与算法分析,数据结构,java源码)