一般 for 循环遍历和迭代器遍历 LinkedList 区别

先说区别:

  • 普通for循环:每次遍历一个索引的元素之前,都要访问之前所有的索引。

  • 每次访问一个元素后,都会用游标记录当前访问元素的位置,遍历一个元素,记录一个位置。

普通 for 循环

普通 for 循环遍历方式如下:

LinkedList<String> linkedList = new LinkedList<>();
linkedList.add("A");
linkedList.add("B");
linkedList.add("C");
linkedList.add("D");
for(int i = 0 ; i < linkedList.size() ; i++){
    System.out.print(linkedList.get(i)+" ");  
}

需要注意的是, get(int index) 方法每次都要遍历该索引之前的所有元素,这句话这么理解:

比如上面的一个 LinkedList 集合,放入了 A、B、C、D 四个元素。总共需要四次遍历:

  • 第一次遍历打印 A:只需遍历一次。
  • 第二次遍历打印 B:需要先找到 A,然后再找到 B 打印。
  • 第三次遍历打印 C:需要先找到 A,然后找到 B,最后找到 C 打印。
  • 第四次遍历打印 D:需要先找到 A,然后找到 B,然后找到 C,最后找到 D。

也就是说每次调用 get(int index) 方法时,都是从最前面开始依次往后遍历。这样的话如果集合元素很多,越查找到后面(当然此处的get方法进行了优化,查找前半部分从前面开始遍历,查找后半部分从后面开始遍历,但是需要的时间还是很多)花费的时间越多。那么如何改进呢?

迭代器

迭代器遍历如下:

LinkedList<String> linkedList = new LinkedList<>();
linkedList.add("A");
linkedList.add("B");
linkedList.add("C");
linkedList.add("D");

// 从前往后打印
Iterator<String> listIt = linkedList.listIterator();
while(listIt.hasNext()){
    System.out.print(listIt.next()+" ");  // A B C D
}

// 从后往前打印
// 通过适配器模式实现的接口,作用是倒叙打印链表
Iterator<String> it = linkedList.descendingIterator(); 
while(it.hasNext()){
    System.out.print(it.next()+" ");       // D C B A
}

在 LinkedList 集合中也有一个内部类 ListItr,方法实现大体上也差不多,通过移动游标指向每一次要遍历的元素,不用在遍历某个元素之前都要从头开始。其方法实现也比较简单:

public ListIterator<E> listIterator(int index) {
    checkPositionIndex(index);
    return new ListItr(index);
}

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();
        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();
    }
}

这里需要重点注意的是 modCount 字段,前面在增加和删除元素的时候,都会进行自增操作 modCount,这是因为如果想一边迭代,一边用集合自带的方法进行删除或者新增操作,都会抛出异常。(使用迭代器的增删方法不会抛异常)

final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

比如:

LinkedList<String> linkedList = new LinkedList<>();
linkedList.add("A");
linkedList.add("B");
linkedList.add("C");
linkedList.add("D");

Iterator<String> listIt = linkedList.listIterator();
while(listIt.hasNext()){
    System.out.print(listIt.next()+" ");   // A B C D
    // linkedList.remove();    // 此处会抛出异常
    listIt.remove();           // 这样可以进行删除操作
}

迭代器的另一种形式就是使用 foreach 循环,底层实现也是使用的迭代器:

LinkedList<String> linkedList = new LinkedList<>();
linkedList.add("A");
linkedList.add("B");
linkedList.add("C");
linkedList.add("D");
for(String str : linkedList){
    System.out.print(str + "");
}

迭代器和 for 循环性能差异测试

LinkedList<Integer> linkedList = new LinkedList<>();
for(int i = 0 ; i < 10000 ; i++){        // 向链表中添加一万个元素
    linkedList.add(i);
}

long beginTimeFor = System.currentTimeMillis();
for(int i = 0 ; i < 10000 ; i++){
    System.out.print(linkedList.get(i));
}
long endTimeFor = System.currentTimeMillis();
System.out.println("使用普通for循环遍历10000个元素需要的时间:"+ (endTimeFor - beginTimeFor));

long beginTimeIte = System.currentTimeMillis();
Iterator<Integer> it = linkedList.listIterator();
while(it.hasNext()){
    System.out.print(it.next()+" ");
}
long endTimeIte = System.currentTimeMillis();
System.out.println("使用迭代器遍历10000个元素需要的时间:"+ (endTimeIte - beginTimeIte));

打印结果:

使用普通for循环遍历10000个元素需要的时间:100
使用迭代器遍历10000个元素需要的时间:24

可以看出当打印10000个元素时,两者的时间大约相差3倍,当元素个数更多时,性能差距将会更大。

你可能感兴趣的:(Java源码,java,集合,linkedlist)