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的特有功能
所有增加的方法都与索引有关
- 在指定索引位置添加元素;
- 删除指定索引位置元素;
- 修改指定索引位置元素;
- 依据索引查询元素;
- 找到指定元素的索引;
- 返回处于指定两个索引之间的元素视图;
除此之外,还提供了一个新的迭代器,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更多,两种主要的区别如下:
- ListIterator可以双向遍历,Iterator只能单向遍历,即游标只能增大,不能变小;
- ListIterator可以添加/修改/删除元素,Iterator只能移除元素;
- 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:
- 提供对List接口的基本骨架实现,这一点和AbstractCollection为Collection提供给了骨架实现一样;
- 为实现"随机存储"实现的List提供了一些公用方法,如果是顺序存储,应使用AbstractSequentialList;
- 如果是要实现一个不可变的List,只需要继承该类并实现get()和size();
- 如果是要实现一个可变的List,还需要重写set(int,E),如果是大小可变的,那么还需要重写add(int, E)和remove(int);
而且,从上面可以看出:
AbstractList实现的功能,都是通过listiterator()返回的ListIterator进行遍历查找来实现,而ListIterator实现功能需要的支持;
除此之外,这其中有几个点需要特别注意的:
- Iterator和ListIterator的功能都是通过AbstractList.add()/remove()/set()/get()/size()来实现的;所以这几个方法,要么是可选操作,默认抛出UnsupportedOperationException,要么为抽象方法,子类必须实现;
- 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机制
- 迭代器操作还有一个逻辑:在调用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;
subList()实现,返回的是一个AbstractList的子类AbstractList.Sublist,返回的SubList对象中存在当前AbstractList对象的引用,且其中的所有操作都是通过调用当前AbstractList的同名方法来实现,而其自身只处理界限检查之类的工作;jdk中描述其实际为一个视图(View)可以说是很贴切了;
如果当前AbstractList实现了RandomAccess接口,那么返回的对象为RandomAccessSubList类型,其继承了AbstractList.SubList,并且实现了RandomAccess接口;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();
Java容器解析系列(3) List AbstractList ListIterator RandomAccess fail-fast机制 详解的更多相关文章
- Java容器解析系列(0) 开篇
最近刚好学习完成数据结构与算法相关内容: Data-Structures-and-Algorithm-Analysis 想结合Java中的容器类加深一下理解,因为之前对Java的容器类理解不是很深刻, ...
- Java容器解析系列(11) HashMap 详解
本篇我们来介绍一个最常用的Map结构--HashMap 关于HashMap,关于其基本原理,网上对其进行讲解的博客非常多,且很多都写的比较好,所以.... 这里直接贴上地址: 关于hash算法: Ha ...
- Java容器解析系列(4) ArrayList Vector Stack 详解
ArrayList 这里关于ArrayList本来都读了一遍源码,并且写了一些了,突然在原来的笔记里面发现了收藏的有相关博客,大致看了一下,这些就是我要写的(╹▽╹),而且估计我还写不到博主的水平,这 ...
- Java容器解析系列(1) 迭代的进化——从Enumeration到Iterator
在Java中,对于所有的Collection,都有一个特性,可以通过迭代器来遍历和删除其中的元素,因为Collection接口继承自Iterable接口. public interface Colle ...
- Java容器解析系列(5) AbstractSequentialList LinkedList 详解
AbstractSequentialList为顺序访问的list提供了一个骨架实现,使实现顺序访问的list变得简单; 我们来看源码: /** AbstractSequentialList 继承自 A ...
- Java容器解析系列(13) WeakHashMap详解
关于WeakHashMap其实没有太多可说的,其与HashMap大致相同,区别就在于: 对每个key的引用方式为弱引用; 关于java4种引用方式,参考java Reference 网上很多说 弱引用 ...
- Java容器解析系列(10) Map AbstractMap 详解
前面介绍了List和Queue相关源码,这篇开始,我们先来学习一种java集合中的除Collection外的另一个分支------Map,这一分支的类图结构如下: 这里为什么不先介绍Set相关:因为很 ...
- Java容器解析系列(7) ArrayDeque 详解
ArrayDeque,从名字上就可以看出来,其是通过数组实现的双端队列,我们先来看其源码: /** 有自动扩容机制; 不是线程安全的; 不允许添加null; 作为栈使用时比java.util.Stac ...
- Java容器解析系列(8) Comparable Comparator
Comparable和Comparator接口是两个用于对对象进行大小比较的接口,在java集合相关类中,也被经常地使用到. 关于其使用,可以参考网络上的其他博客(没什么好说的);这里阐述关于这两个接 ...
随机推荐
- C# 调用C++的dll 那些事
之前从来没搞过C++,最近被安排的任务需要调用C++的接口,对于一个没用过 Dependency 的小白来说,原本以为像平时的Http接口那样,协议,端口一定义,方法参数一写就没事,结果踩了无数的坑. ...
- 安装Joomla!3
在日常测试中搭建一个web 站点进行进行linux 相关功能测试,只是不喜欢httpd 或者nginx 的默认界面: 安装Joomla!3: 系统:centos 7 软件: 连接: https:// ...
- 手游折扣app排行榜前10名_2018哪个折扣app最低最好
2018游戏圈白皮书发布,PC端游的份额继续下降,页游的比例也在下降,但手游的比例持续3年上升.以渠道为阵营,逐渐小的平台和公会被逐渐淘汰.流量集中在少数几个大的平台.但是这样带来的问题是,平台越来越 ...
- git的一些补充点
git rm和 rm的区别 git rm是删除文件, 同时加入到git的跟踪管理中,做一个登记,那么在git commit的时候, 会把这次删除作为一次修改提交上去, 否则, 在 git log中你就 ...
- Learning-MySQL【3】:数据类型和运算符
一.数据类型 1.数值类型 为每张表的每个字段选择合适的数据类型是数据库设计过程中一个重要的步骤. 数字分为整数和小数.其中整数用整数类型表示,小数用浮点数类型和定点数类型表示. 浮点数类型包括单精度 ...
- CSS基础学习(一) 之 line-height 与 height 属性区别
官方定义: height:定义了了元素的高度.默认情况下,该属性订了 content area(内容区域) 的高度.如果box-sizing属性设置为 border-box,那么height就表示bo ...
- vue的环境配置
在vue越来越火的情况下,本人也开始加入到大军当中. 首先,列举下我们需要的东西: node.js 环境(npm包管理器) vue-cli 脚手架构建工具 cnpm npm的淘宝镜像 安装node.j ...
- 数组toString()方法,数组常用操作
int[] arr ={1,2,3,4,5}; String arrString = Arrays.toString(arr); //输出[I@7150bd4d System.out.println( ...
- python 字符串与16进制 转化
def str_to_hex(s): return r"/x"+r'/x'.join([hex(ord(c)).replace('0x', '') for c in s]) def ...
- springCloud全家桶
Spring Cloud 入门教程(一): 服务注册 eureka是一个高可用的组件,它没有后端缓存,每一个实例注册之后需要向注册中心发送心跳,在默认情况下erureka server也是一个eure ...