美文网首页Java工程师知识树
Java基础-集合类-迭代器

Java基础-集合类-迭代器

作者: HughJin | 来源:发表于2020-12-27 07:03 被阅读0次

Java工程师知识树 / Java基础


任何容器类,都必须有某种方式可以将东西放进去,然后由某种方式将东西取出来。毕竟,存放事物是容器最基本的工作。对于ArrayList,add()是插入对象的方法,而get()是取出元素的方式之一。ArrayList很灵活,可以随时选取任意的元素,或使用不同的下标一次选取多个元素。
如果从更高层的角度思考,会发现这里有一个缺点:要使用容器,必须知道其中元素的确切类型。初看起来这没有什么不好的,但是考虑如下情况:如果原本是ArrayList ,但是后来考虑到容器的特点,你想换用Set ,应该怎么做?或者你打算写通用的代码,它们只是使用容器,不知道或者说不关心容器的类型,那么如何才能不重写代码就可以应用于不同类型的容器?
所以迭代器(Iterator)的概念,也是出于一种设计模式就是为达成此目的而形成的。所以Collection不提供get()方法。如果要遍历Collection中的元素,就必须用Iterator。
迭代器(Iterator)本身就是一个对象,它的工作就是遍历并选择集合序列中的对象,而客户端的程序员不必知道或关心该序列底层的结构。此外,迭代器通常被称为“轻量级”对象,创建它的代价小。但是,它也有一些限制,例如,某些迭代器只能单向移动。


Collection 接口的 iterator() 方法返回一个 Iterator。Iterator 和Enumeration 接口类似。使用 Iterator 接口方法,您可以从头至尾遍历集合,并安全的从底层 Collection 中除去元素。

Enumeration

Enumeration 接口早在 JDK 1.0 时就推出了,当时比较早的容器比如 Hashtable, Vector 都使用它作为遍历工具。
Enumeration是一个接口,它的源码如下:

public interface Enumeration<E> {

/**
 * 是否还有元素
 * @return {@code true} if there are more elements, {@code false} otherwise.
 * @see #nextElement
    */
    public boolean hasMoreElements();

/**

 * 返回下一个元素
 * @return the next element..
 * @throws NoSuchElementException
 * if there are no more elements.
 * @see #hasMoreElements
    */
    public E nextElement();
    }

Iterator 是一个集合上的迭代器,用来替代 Enumeration 进行遍历、迭代。
Iterator也是一个接口,它的源码如下:

package java.util;
public interface Iterator<E> {
    boolean hasNext();//是否还有元素
    E next();//返回下一个元素
    void remove();//移除元素
}

之间的区别。

  1. 函数接口不同

    Enumeration只有2个函数接口。通过Enumeration,我们只能读取集合的数据,而不能对数据进行修改。
    Iterator只有3个函数接口。Iterator除了能读取集合的数据之外,也能数据进行删除操作。

  2. 方法名称得到了改进

迭代器的简单使用:

public class IteratorDemo {
    public static void main(String[] args) {
        Collection collection = new ArrayList();
        collection.add("s1");
        collection.add("s2");
        collection.add("s3");
        Iterator iterator = collection.iterator();//得到一个迭代器
        while (iterator.hasNext()) {//遍历
            Object element = iterator.next();
            System.out.println("iterator = " + element);
        }
        if(collection.isEmpty())
            System.out.println("collection is Empty!");
        else
            System.out.println("collection is not Empty! size="+collection.size());
        Iterator iterator2 = collection.iterator();
        while (iterator2.hasNext()) {//移除元素
            Object element = iterator2.next();
            System.out.println("remove: "+element);
            iterator2.remove();
        }       
        Iterator iterator3 = collection.iterator();
        if (!iterator3.hasNext()) {//察看是否还有元素
            System.out.println("还有元素");
        }   
        if(collection.isEmpty())
            System.out.println("collection is Empty!");
        //使用collection.isEmpty()方法来判断
    }
}
程序的运行结果为:
iterator = s1
iterator = s2
iterator = s3
collection is not Empty! size=3
remove: s1
remove: s2
remove: s3
还有元素
collection is Empty!

可以看到,Java的Collection的Iterator 能够用来:

  1. 使用方法 iterator() 要求容器返回一个Iterator .第一次调用Iterator 的next() 方法时,它返回集合序列的第一个元素。
  2. 使用next() 获得集合序列的中的下一个元素。
  3. 使用hasNext()检查序列中是否元素。
  4. 使用remove()将迭代器新返回的元素删除。

需要注意的是:方法删除由next方法返回的最后一个元素,在每次调用next时,remove方法只能被调用一次 。

扩展:

ListIterator

ListIterator

ListIterator 有以下功能:

  • 允许我们向前、向后两个方向遍历 List;
  • 在遍历时修改 List 的元素;
  • 遍历时获取迭代器当前游标所在位置。

注意,迭代器 没有当前所在元素一说,它只有一个游标( cursor )的概念,这个游标总是在元素之间,比如这样:

image.png

初始时它在第 0 个元素之前,调用 next() 游标后移一位:

image.png

调用 previous() 游标就会回到之前位置。

当向后遍历完元素,游标就会在元素 N 的后面:

image.png

也就是说长度为 N 的集合会有 N+1 个游标的位置。

ListIterator 继承自 Iterator 接口,在 Iterator 的基础上增加了 6 个方法:
这里写图片描述

image.png

介绍一下新来的几个方法:

void hasPrevious() 判断游标前面是否有元素;

Object previous() 返回游标前面的元素,同时游标前移一位。游标前没有元素就报 java.util.NoSuchElementException 的错,所以使用前最好判断一下;

int nextIndex() 返回游标后边元素的索引位置,初始为 0 ;遍历 N 个元素结束时为 N;

int previousIndex() 返回游标前面元素的位置,初始时为 -1,同时报 java.util.NoSuchElementException 错;

void add(E) 在游标 前面 插入一个元素 注意,是前面

void set(E) 更新迭代器最后一次操作的元素为 E,也就是更新最后一次调用 next() 或者 previous() 返回的元素。注意,当没有迭代,也就是没有调用 next() 或者 previous() 直接调用 set 时会报 java.lang.IllegalStateException 错;

void remove() 删除迭代器最后一次操作的元素,注意事项和 set 一样。

ListIterator 有两种获取方式

  • List.listIterator()
  • List.listIterator(int location)

Iterator 和 Enumeration

import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.Random;

/*
 * 测试分别通过 Iterator 和 Enumeration 去遍历Hashtable
 * @author skywang
 */
public class IteratorEnumeration {

    public static void main(String[] args) {
        int val;
        Random r = new Random();
        Hashtable table = new Hashtable();
        for (int i=0; i<100000; i++) {
            // 随机获取一个[0,100)之间的数字
            val = r.nextInt(100);
            table.put(String.valueOf(i), val);
        }

        // 通过Iterator遍历Hashtable
        iterateHashtable(table) ;

        // 通过Enumeration遍历Hashtable
        enumHashtable(table);
    }
    
    /*
     * 通过Iterator遍历Hashtable
     */
    private static void iterateHashtable(Hashtable table) {
        long startTime = System.currentTimeMillis();

        Iterator iter = table.entrySet().iterator();
        while(iter.hasNext()) {
            //System.out.println("iter:"+iter.next());
            iter.next();
        }

        long endTime = System.currentTimeMillis();
        countTime(startTime, endTime);
    }
    
    /*
     * 通过Enumeration遍历Hashtable
     */
    private static void enumHashtable(Hashtable table) {
        long startTime = System.currentTimeMillis();

        Enumeration enu = table.elements();
        while(enu.hasMoreElements()) {
            //System.out.println("enu:"+enu.nextElement());
            enu.nextElement();
        }

        long endTime = System.currentTimeMillis();
        countTime(startTime, endTime);
    }

    private static void countTime(long start, long end) {
        System.out.println("time: "+(end-start)+"ms");
    }
}
//运行结果如下:
time: 9ms
time: 5ms

从中,我们可以看出。Enumeration 比 Iterator 的遍历速度更快。为什么呢?
这是因为,Hashtable中Iterator是通过Enumeration去实现的,而且Iterator添加了对fail-fast机制的支持;所以,执行的操作自然要多一些。

SimpleListIterator

private class SimpleListIterator implements Iterator<E> {
    //游标的位置,初始为 -1
    int pos = -1;
    //用来判断是否 fail-fast 的变量
    int expectedModCount;
    //记录上次迭代的位置
    int lastPosition = -1;
 
    SimpleListIterator() {
        expectedModCount = modCount;
    }
 
    //当游标没有跑到最后一个元素后面时 hasNext 返回 true
    public boolean hasNext() {
        return pos + 1 < size();
    }
 
    //获取下一个元素
    public E next() {
        if (expectedModCount == modCount) {
            try {
                //获取游标后面的元素,具体子类有具体实现
                E result = get(pos + 1);
                //更新
                lastPosition = ++pos;
                return result;
            } catch (IndexOutOfBoundsException e) {
                throw new NoSuchElementException();
            }
        }
        //当迭代时修改元素,就会报这个错,上篇文章介绍过解决办法~
        throw new ConcurrentModificationException();
    }
 
    //删除上次迭代操作的元素
    public void remove() {
        //还没进行迭代操作就会报这个错
        if (this.lastPosition == -1) {
            throw new IllegalStateException();
        }
 
        if (expectedModCount != modCount) {
            throw new ConcurrentModificationException();
        }
 
        try {
            //调用子类实现的删除操作
            AbstractList.this.remove(lastPosition);
        } catch (IndexOutOfBoundsException e) {
            throw new ConcurrentModificationException();
        }
 
        expectedModCount = modCount;
        if (pos == lastPosition) {
            pos--;
        }
        //每次删除后都会还原为 -1,也就是说我们迭代一次后只能 remove 一次,再 remove 就会报错
        lastPosition = -1;
    }
}

FullListIterator

private final class FullListIterator extends SimpleListIterator implements ListIterator<E> {
    //根据 start 指定游标位置
    FullListIterator(int start) {
        if (start >= 0 && start <= size()) {
            pos = start - 1;
        } else {
            throw new IndexOutOfBoundsException();
        }
    }
 
    //在游标前面添加元素
    public void add(E object) {
        if (expectedModCount == modCount) {
            try {
                //调用子类的添加操作,ArrayList, LinkedList,Vector 的添加操作实现有所不同
                AbstractList.this.add(pos + 1, object);
            } catch (IndexOutOfBoundsException e) {
                throw new NoSuchElementException();
            }
            //游标后移一位
            pos++;
            //!注意! 添加后 上次迭代位置又变回 -1 了,说明 add 后调用 remove, set 会有问题!
            lastPosition = -1;
            if (modCount != expectedModCount) {
                expectedModCount = modCount;
            }
        } else {
            throw new ConcurrentModificationException();
        }
    }
 
    //当游标不在初始位置(-1)时返回true
    public boolean hasPrevious() {
        return pos >= 0;
    }
 
    //游标后面的元素索引,就是游标 +1
    public int nextIndex() {
        return pos + 1;
    }
 
    //游标前面一个元素
    public E previous() {
        if (expectedModCount == modCount) {
            try {
                E result = get(pos);
                lastPosition = pos;
                pos--;
                return result;
            } catch (IndexOutOfBoundsException e) {
                throw new NoSuchElementException();
            }
        }
        throw new ConcurrentModificationException();
    }
 
    //游标前面元素的索引,就是游标的位置,有点晕的看开头那几张图
    public int previousIndex() {
        return pos;
    }
 
    //更新之前迭代的元素为 object
    public void set(E object) {
        if (expectedModCount == modCount) {
            try {
                //调用子类的set
                AbstractList.this.set(lastPosition, object);
            } catch (IndexOutOfBoundsException e) {
                throw new IllegalStateException();
            }
        } else {
            throw new ConcurrentModificationException();
        }
    }
}

相关文章

  • Java基础-集合类-迭代器

    Java工程师知识树[https://www.jianshu.com/p/db77d19a25f6] / Ja...

  • 设计模式之迭代器模式

    迭代器模式 迭代器接口 具体迭代器类 容器接口 具体容器类 客户端 个人理解 在java中的集合是迭代器模式的最好...

  • java集合分类

    java的集合主要分为List列表、Set集合、工具类(Iterator迭代器、Enumeration枚举类、Ar...

  • java成神之路---集合框架

    标签(空格分隔): java java集合类库的设计思想:“接口与实现分离” java类库中的集合接口和迭代器接口...

  • java集合类迭代器

    迭代器模式:就是提供一种方法对一个容器对象中的各个元素进行访问,而又不暴露该对象容器的内部细节。 概述 Java集...

  • JAVA简答(二)

    15 . Java集合类框架的基本接口有哪些? 什么是迭代器(Iterator)? 定义:迭代器是一种设计模式,它...

  • 被用到炉火纯清的迭代器模式

    0x01:迭代器模式简介 Java中可以说已经把迭代器模式用到了极致,每一个集合类都关联了一个迭代器类Iterat...

  • Iterator和Iterable的区别

    回顾迭代器模式 java提供了很多集合类,而迭代器模式就是提供一种方法,顺序访问聚集的对象。而又不暴露该集合的内部...

  • 设计模式-迭代器(Iterator)模式

    主要角色 集合类 迭代器 职责 使用集合类中的迭代器,迭代数据,不停地获取下一个。 角色关系 课程类 课程集合接口...

  • Iterator迭代器

    前言: Java中的Iterator迭代器是为了对集合进行迭代 迭代器的使用:

网友评论

    本文标题:Java基础-集合类-迭代器

    本文链接:https://www.haomeiwen.com/subject/vvzynktx.html