公子姓王

导航

Java容器解析系列(3) List AbstractList ListIterator RandomAccess fail-fast机制 详解

做为数据结构学习的常规,肯定是先学习线性表,也就是Java中的List,开始
Java中List相关的类关系图如下:

此篇作为对Java中相关类的开篇.从上图中可以看出,List和AbstractList是表的具体实现类的抽象.

首先我们来看一下List接口:

/**
   list 表示一个序列,与Set不同,list通常允许重复元素和null;
   list还提供了一个特别的迭代器,ListIterator,其允许对元素进行插入和替换,并且允许双向的查询;
   因为可以向list中添加自身,这时就需要注意小心重写equals()方法和hashCode()方法;
 * @since 1.2
 */
public interface List<E> extends Collection<E> {

    int size();
    boolean isEmpty();
    boolean contains(Object o);
    Iterator<E> iterator();
    Object[] toArray();
    <T> T[] toArray(T[] a);
    boolean add(E e);
    boolean remove(Object o);
    boolean containsAll(Collection<?> c);
    boolean addAll(Collection<? extends E> c);
    boolean addAll(int index, Collection<? extends E> c);
    boolean removeAll(Collection<?> c);
    boolean retainAll(Collection<?> c);
    void clear();
    boolean equals(Object o);
    int hashCode();

    E get(int index);
    E set(int index, E element);
    void add(int index, E element);
    E remove(int index);
    int indexOf(Object o);
    int lastIndexOf(Object o);

    ListIterator<E> listIterator();
    ListIterator<E> listIterator(int index);

    // 返回index在[fromIndex,toIndex)区间元素的一个视图(View),对返回的list进行的非结构性修改(non-structural changes)将会反映在当前list中,反过来,对当前list的非结构性修改也将会反映在返回的list中;
    // 返回的list将支持所有当前list支持的操作;
    // 结构性修改(Structural modifications):会导致list大小被改变的修改,或在迭代过程中,以其他方式扰乱迭代过程的修改;
    List<E> subList(int fromIndex, int toIndex);
}

List接口继承自Collection接口,其在Collection接口上增加了一些List的特有功能
所有增加的方法都与索引有关

  1. 在指定索引位置添加元素;
  2. 删除指定索引位置元素;
  3. 修改指定索引位置元素;
  4. 依据索引查询元素;
  5. 找到指定元素的索引;
  6. 返回处于指定两个索引之间的元素视图;

除此之外,还提供了一个新的迭代器,listIterator()返回一个ListIterator对象
先来看ListIterator的源码:

/**
   ListIterator允许:双向遍历list,迭代过程中修改返回的元素,获取迭代器的当前位置.
   一个ListIterator没有当前的元素这个概念,它的游标(cursor)位置永远处在会被previous()返回的元素和next()返回的元素之间;
  可能的游标位置:
                       Element(0)   Element(1)   Element(2)   ... Element(n-1)
  cursor positions:  ^            ^            ^            ^                  ^
   注意remove()方法和set()方法不是针对游标位置定义的,而是针对上一次通过previous()或next()返回的元素进行操作;
   上述对于游标位置的理解也适用于Iterator;
 * @since   1.2
 */
public interface ListIterator<E> extends Iterator<E> {

    /* 这里继承了Iterator,但是还是把Iterator里面定义的方法重新写了一遍,至于为什么,I have no idea */
    boolean hasNext();
    E next();
    void remove();

    boolean hasPrevious();
    E previous();

    // 返回调用next()方会返回的元素的index,如果此时游标位于末尾,返回list的大小
    int nextIndex();

    // previous()会返回的元素的index,如果此时游标位于最前,返回-1
    int previousIndex();

    // 替换最近一次由previous()或next()返回的元素;
    // 该操作只有在previous()或next()后没有调用过remove()/add()才有效
    void set(E e);

    // 插入指定元素到next()将会返回的元素之前,previous()将会返回的元素之后;
    // 换言之,插入到游标位置前;
    void add(E e);
}

从上面的源码可以看出,ListIterator本身也是一个Iterator,不过其功能比Iterator更多,两种主要的区别如下:

  1. ListIterator可以双向遍历,Iterator只能单向遍历,即游标只能增大,不能变小;
  2. ListIterator可以添加/修改/删除元素,Iterator只能移除元素;
  3. ListIterator可以获取游标的位置,Iterator不能;

具体关于ListIterator和Iterator的实现内容,还需看AbstractList的源码:

/**
 * @since 1.2
 */
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {

    protected AbstractList() { }

    public boolean add(E e) {
        add(size(), e);
        return true;
    }

    public void add(int index, E element) {
        throw new UnsupportedOperationException();
    }

    abstract public E get(int index);

    public E set(int index, E element) {
        throw new UnsupportedOperationException();
    }

    public E remove(int index) {
        throw new UnsupportedOperationException();
    }

    public int indexOf(Object o) {
        ListIterator<E> it = listIterator();
        if (o == null) {
            while (it.hasNext())
                if (it.next() == null)
                    return it.previousIndex();
        } else {
            while (it.hasNext())
                if (o.equals(it.next()))
                    return it.previousIndex();
        }
        return -1;
    }

    public int lastIndexOf(Object o) {
        ListIterator<E> it = listIterator(size());
        if (o == null) {
            while (it.hasPrevious())
                if (it.previous() == null)
                    return it.nextIndex();
        } else {
            while (it.hasPrevious())
                if (o.equals(it.previous()))
                    return it.nextIndex();
        }
        return -1;
    }

    public void clear() {
        removeRange(0, size());
    }

    protected void removeRange(int fromIndex, int toIndex) {
        ListIterator<E> it = listIterator(fromIndex);
        for (int i = 0, n = toIndex - fromIndex; i < n; i++) {
            it.next();
            it.remove();
        }
    }

    public boolean addAll(int index, Collection<? extends E> c) {
        rangeCheckForAdd(index);
        boolean modified = false;
        for (E e : c) {
            add(index++, e);
            modified = true;
        }
        return modified;
    }

    public Iterator<E> iterator() {
        return new Itr();
    }

    public ListIterator<E> listIterator() {
        return listIterator(0);
    }

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

    /**
     * 该list被结构性修改(structurally modified)的次数
     * modCount被iterator()/listIterator()方法返回的Iterator/ListIterator对象使用.
     * 如果modCount的值被预料之外的(unexpectedly)修改,那么在迭代过程中的next()/
     * remove()/previous()/set()/add()方法调用会抛出ConcurrentModificationException,
     * 也就是,提供快速失效机制(fast-fail),以避免在并发情况下的不确定行为(non-deterministic behavior)
     *  如果子类 需
     * 实现fast-fail迭代器,那么需要在add()和remove(int)方法(还有其他会导致结构性修改的方法)中将该值自增1
     */
    protected transient int modCount = 0;

    private class Itr implements Iterator<E> {

        int cursor = 0;
        int expectedModCount = modCount;
        // 最近一次调用nex()或previous()返回的元素的索引
        // 每次调用remove后将该值置为-1
        int lastRet = -1;

        public boolean hasNext() {
            return cursor != size();
        }

        public E next() {
            checkForComodification();
            try {
                int i = cursor;
                E next = get(i);
                lastRet = i;
                cursor = i + 1;
                return next;
            } catch (IndexOutOfBoundsException e) {
                checkForComodification();
                throw new NoSuchElementException();
            }
        }

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();
            try {
                AbstractList.this.remove(lastRet);
                if (lastRet < cursor)
                    cursor--;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException e) {
                throw new ConcurrentModificationException();
            }
        }

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

    private class ListItr extends Itr implements ListIterator<E> {
        ListItr(int index) {
            cursor = index;
        }

        public boolean hasPrevious() {
            return cursor != 0;
        }

        public E previous() {
            checkForComodification();
            try {
                int i = cursor - 1;
                E previous = get(i);
                lastRet = cursor = i;
                return previous;
            } catch (IndexOutOfBoundsException e) {
                checkForComodification();
                throw new NoSuchElementException();
            }
        }

        public int nextIndex() {
            return cursor;
        }

        public int previousIndex() {
            return cursor - 1;
        }

        public void set(E e) {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                AbstractList.this.set(lastRet, e);
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        public void add(E e) {
            checkForComodification();

            try {
                int i = cursor;
                AbstractList.this.add(i, e);
                lastRet = -1;
                cursor = i + 1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
    }

    // 返回一个当前对象的视图(View),该视图中的元素范围为[fromIndex,toIndex)区间
    // 该视图的所有实际操作,实际都被委托到当前对象来处理
    public List<E> subList(int fromIndex, int toIndex) {
        return (this instanceof RandomAccess ? new RandomAccessSubList<>(this, fromIndex, toIndex)
                : new SubList<>(this, fromIndex, toIndex));
    }

    public boolean equals(Object o) {
        if (o == this)
            return true;
        if (!(o instanceof List))
            return false;

        ListIterator<E> e1 = listIterator();
        ListIterator e2 = ((List) o).listIterator();
        while (e1.hasNext() && e2.hasNext()) {
            E o1 = e1.next();
            Object o2 = e2.next();
            if (!(o1 == null ? o2 == null : o1.equals(o2)))
                return false;
        }
        return !(e1.hasNext() || e2.hasNext());
    }

    public int hashCode() {
        int hashCode = 1;
        for (E e : this)
            hashCode = 31 * hashCode + (e == null ? 0 : e.hashCode());
        return hashCode;
    }

    private void rangeCheckForAdd(int index) {
        if (index < 0 || index > size())
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

    private String outOfBoundsMsg(int index) {
        return "Index: " + index + ", Size: " + size();
    }
}

class SubList<E> extends AbstractList<E> {
    private final AbstractList<E> l;
    private final int offset;
    private int size;

    SubList(AbstractList<E> list, int fromIndex, int toIndex) {
        if (fromIndex < 0)
            throw new IndexOutOfBoundsException("fromIndex = " + fromIndex);
        if (toIndex > list.size())
            throw new IndexOutOfBoundsException("toIndex = " + toIndex);
        if (fromIndex > toIndex)
            throw new IllegalArgumentException("fromIndex(" + fromIndex + ") > toIndex(" + toIndex + ")");
        l = list;
        offset = fromIndex;
        size = toIndex - fromIndex;
        this.modCount = l.modCount;
    }

    public int size() {
        checkForComodification();
        return size;
    }

    //  .....省略
    // 所有方法都是通过调用 成员变量l的同名方法 来实现,自身只处理界限检查之类的工作;
}

class RandomAccessSubList<E> extends SubList<E> implements RandomAccess {
    RandomAccessSubList(AbstractList<E> list, int fromIndex, int toIndex) {
        super(list, fromIndex, toIndex);
    }

    public List<E> subList(int fromIndex, int toIndex) {
        return new RandomAccessSubList<>(this, fromIndex, toIndex);
    }
}

限于篇幅,上述代码省略了AbstractList.SubList的部分方法,其中拥有的方法如下所示:

AbstractList:

  1. 提供对List接口的基本骨架实现,这一点和AbstractCollection为Collection提供给了骨架实现一样;
  2. 为实现"随机存储"实现的List提供了一些公用方法,如果是顺序存储,应使用AbstractSequentialList;
  3. 如果是要实现一个不可变的List,只需要继承该类并实现get()和size();
  4. 如果是要实现一个可变的List,还需要重写set(int,E),如果是大小可变的,那么还需要重写add(int, E)和remove(int);

而且,从上面可以看出:
AbstractList实现的功能,都是通过listiterator()返回的ListIterator进行遍历查找来实现,而ListIterator实现功能需要的支持;

除此之外,这其中有几个点需要特别注意的:

  1. Iterator和ListIterator的功能都是通过AbstractList.add()/remove()/set()/get()/size()来实现的;所以这几个方法,要么是可选操作,默认抛出UnsupportedOperationException,要么为抽象方法,子类必须实现;
  2. fail-fast机制

根据AbstractList的源码注释,我们大概给出fail-fast机制的定义:在遍历过程中,不允许存在多个线程同时对同一个AbstractList进行结构性的修改,以避免在并发情况下的不确定行为(non-deterministic behavior),如果迭代器检查到了这种修改,那么抛出ConcurrentModificationException;

fail-fast机制没有触发并不能保证没有发生并发修改;其应当仅仅被用于检测bugs.

实现方式:

    1. 在AbstractList中有一个成员变量:modCount,表示当前对象的结构性修改次数;
    2. 如果需要实现fail-fast迭代器,那么需要在add()和remove(int)方法(还有其他会导致结构性修改的方法)中将该值自增1;
    3. AbstractList的迭代器也有一个成员变量:expectedModCount,在实例化的时候,将赋值为当前AbstractList.modCount;
    4. 在迭代过程中的next()/remove()/previous()/set()/add()方法调用时,先比较expectedModCount和modCount的值,如果不同(证明之前出现过结构性修改),抛出异常;
    5. 在迭代器的;add()/remove()方法调用的最后,将expectedModCount = modCount;

关于fail-fast机制,推荐另一篇写得很好的博客:Java提高篇----fail-fast机制

  1. 迭代器操作还有一个逻辑:在调用next()/previous()方法返回一个元素后,只能调用一次remove()方法,且在调用remove()方法后,不能调用set()方法
    我们来看一下它的实现方式:
    1. 在Iterator方法中有一个成员变量:int lastRet = -1;其默认值为-1;
    2. 在调用next()/previous()方法时,会将lastRet置为返回的元素的游标位置值;
    3. 在调用remove()方法时,会将lastRet = -1;
    4. 每次调用迭代器的remove()/set()方法时,会先检查lastRet < 0,如果成立,抛出IllegalStateException;
  1. subList()实现,返回的是一个AbstractList的子类AbstractList.Sublist,返回的SubList对象中存在当前AbstractList对象的引用,且其中的所有操作都是通过调用当前AbstractList的同名方法来实现,而其自身只处理界限检查之类的工作;jdk中描述其实际为一个视图(View)可以说是很贴切了;
    如果当前AbstractList实现了RandomAccess接口,那么返回的对象为RandomAccessSubList类型,其继承了AbstractList.SubList,并且实现了RandomAccess接口;

  2. RandomAccess:

/**
 * @since 1.4
 */
public interface RandomAccess {
}

RandomAccess 是一个标记接口,表示一个List可以被随机访问,且使用get(int index)直接访问效率更高;
不推荐使用迭代器来进行访问,迭代器只能按顺序访问list中的元素;
也就是说,如果一个list实现了RandomAccess接口,那么代码:

for (int i=0, n=list.size(); i < n; i++)
  list.get(i);

比下面的代码运行快:

for (Iterator i=list.iterator(); i.hasNext();)
    i.next();

posted on 2018-10-12 16:53  公子姓王  阅读(183)  评论(0编辑  收藏  举报