LinkedList没什么可说的,内部就是一个双向链表,涉及不到扩容,增删数据就是处理每个节点的前驱和后继指针指向的对象。这里只记一下get(index)和Iterator。
1、get(index)
public E get(int index) {
checkElementIndex(index); 检验下标合法性,不合法抛出IndexOutOfBoundsException
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;
}
}
node方法中的if else作用是判断传入的下标是在链表的前半部分还是后半部分,如果是前半部分就从链表头部开始循环查找,否则就从尾部开始循环查找,这样能略微提升一点效率。可以看到获取指定下标元素的实现方式就是从头部(或者尾部)第一个元素开始for循环,直到循环到指定下标停止,此时的节点就是所需节点。由于查询指定下标需要把指定下标前面(或后面)的节点全部遍历一遍,所以效率偏低,查询的下标越靠近中部效率越低。
2、Iterator
LinkedList的iterator方法返回的是其内部类ListItr,如下:
private class ListItr implements ListIterator<E> {
private Node<E> lastReturned;
private Node<E> 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(); 检查 modCount,作用与ArrayList里一样,只不过在LinkedList里抽取成了方法
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<E> 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();
}
}
可以看到实现原理很简单,next变量记录了当前节点,在构造方法中通过node方法获取到初始节点并赋值给next,后续通过next或者previous方法获取元素只是访问当前节点的前驱或后继节点,所以整个使用Iterator过程只需在初始化的时候调用一次node方法即可。
3、总结
主要理解为什么遍历LinkedList,使用Iterator比使用普通for循环效率高。因为使用普通for循环遍历时,每次循环都要调用一次linkedList.get方法,也就是node方法,上面讲了node方法比较耗时。而使用Iterator只需在初始化时调用了一次node方法,所以效率高。增强型for循环内部实现也是使用的Iterator,所以用增强型for循环遍历也比普通for循环效率高。
所以遍历LinkedList时除非需要使用index,否则一定不要使用普通for循环遍历,尤其在数据量大的时候,普通for循环遍历效率比Iterator效率差距非常巨大。