摘要:显然,开发人员认为通过下标遍历的数组结构更加高效。在进行分裂时,原始保留中部至末尾的元素,新的保留原起始位置到中部的元素。同样,也会进行重新赋值,使得在使用前与保持一致。在遍历时,会调用方法,将动作施加于元素之上。
java.util.ArrayList
ArrayList继承自AbstractList,AbstractList为随机访问数据的结构,如数组提供了基本实现,并且提供了Iterator。首先看AbstractList实现了什么方法。
AbstractList AbstractList里可以存储null吗null可以作为一项存储在ArrayList中。ListIterator是遍历访问AbstractList中元素较好的方式,需要注意获取元素序号的方法是previousIndex,而不是nextIndex,因为之前有next方法的调用。还有,这里判断两个元素是否相等采用的是equals方法,而不是==。
public int indexOf(Object o) {
ListIterator 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;
}
ListIterator可以逆序访问元素
从后向前遍历AbstractList,过程恰恰相反。
public int lastIndexOf(Object o) {
ListIterator 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;
}
Iterator遍历元素时,被其它线程修改了怎么办
如果在使用Iterator时,有其他线程尝试去修改List的大小,会被发现且立即抛出异常。
可以看class Itr implements Iterator
有两个游标分别记录当前指向的位置和上一次指向的位置。
/**
* Index of element to be returned by subsequent call to next.
*/
int cursor = 0;
/**
* Index of element returned by most recent call to next or
* previous. Reset to -1 if this element is deleted by a call
* to remove.
*/
int lastRet = -1;
如何处理遍历过程中,删除list中的元素导致的序号偏移
Iterator可以正确的删除AbstractList中的元素,并且保证访问的顺序的正确性。
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();
}
}
删除的元素是上一个next调用后的元素,并且连续调用两次remove会抛出异常,因为元素只能删除一次,上次指向的元素已经没有了。
ListIterator可以添加元素在class ListItr extends Itr implements ListIterator
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();
}
}
ArrayList
ArrayList用什么来存储元素
就是用了一个简单的数组。。。
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData; // non-private to simplify nested class access
如何节约ArrayList占用的空间
使用trimToSize函数,将原始数组copy到适合大小的数组中。
/**
* Trims the capacity of this ArrayList instance to be the
* list"s current size. An application can use this operation to minimize
* the storage of an ArrayList instance.
*/
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
用Iterator遍历ArrayList真的高效吗
不可否认,Iterator为遍历一个集合提供了统一的接口,用户可以忽略集合内部的具体实现,但是过多的封装会导致效率的降低。显然,Java开发人员认为通过下标遍历ArrayList的数组结构更加高效。所以重写了indexOf和lastIndexOf。
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
ArrayList的clone是浅copy
ArrayList只是新建了数组,copy了元素的引用,元素本身没有进行copy。clone方法为什么不new一个ArrayList对象,而是调用了Object类的clone?因为Object的clone函数是native的,更高效。
/**
* Returns a shallow copy of this ArrayList instance. (The
* elements themselves are not copied.)
*
* @return a clone of this ArrayList instance
*/
public Object clone() {
try {
//使用Object的clone获得一个对象
ArrayList> v = (ArrayList>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn"t happen, since we are Cloneable
throw new InternalError(e);
}
}
ArrayList存储空间的扩展策略
如果list初始没有元素,使用一个静态的空数组。元素增加时,空间扩展为默认的10。在后续的扩展过程中,容量会每次增加原始大小的1/2。
ArrayList实现了自己的iterator虽然AbstractList实现了iterator,但ArrayList似乎不太满意,又重新实现了一遍。主要区别就是在获取元素时,利用了数组结构的优势,可以直接通过下标获取元素,而不必通过调用方法。
用Spliterator分割遍历ArrayListArrayList理所当然的实现了自己的Spliterator,也就是ArrayListSpliterator。分割的策略简而言之为:二分+延迟初始化。
ArrayListSpliterator有如下属性:
this.list = list; // OK if null unless traversed 保存目标list this.index = origin; //起始位置 this.fence = fence; //终止位置 this.expectedModCount = expectedModCount; //期望修改次数,用来判断运行时是否有其它线程修改
每次从中间开始分裂。在进行分裂时,原始spliterator保留中部至末尾的元素,新的spliterator保留原起始位置到中部的元素。
public ArrayListSpliteratortrySplit() { int hi = getFence(), lo = index, mid = (lo + hi) >>> 1; return (lo >= mid) ? null : // divide range in half unless too small new ArrayListSpliterator (list, lo, index = mid, expectedModCount); }
在第一次创建spliterator时,fence被初始为-1,所以在实际使用spliterator时,getFence才会被调用,从而fence才会被赋值为数组大小。同样,expectedModCount也会进行重新赋值,使得spliterator在使用前与list保持一致。
private int getFence() { // initialize fence to size on first use
int hi; // (a specialized variant appears in method forEach)
ArrayList lst;
if ((hi = fence) < 0) {
if ((lst = list) == null)
hi = fence = 0;
else {
expectedModCount = lst.modCount;
hi = fence = lst.size;
}
}
return hi;
}
在遍历list时,会调用方法tryAdvance,将动作施加于元素之上。该方法会检查是否有其它线程的修改,在ArrayList中,有大量方法都会使用modCount来记录修改,或者判断是否在方法执行时有其它线程的修改,目前看来,用这个方法来检测是否有并行修改使得list结构变化是有效的,可以避免并行修改带来的一些问题。
public boolean tryAdvance(Consumer super E> action) {
if (action == null)
throw new NullPointerException();
int hi = getFence(), i = index;
if (i < hi) {
index = i + 1;
@SuppressWarnings("unchecked") E e = (E)list.elementData[i];
action.accept(e);
if (list.modCount != expectedModCount)
throw new ConcurrentModificationException();
return true;
}
return false;
}
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/67747.html
摘要:不同点是线程安全的,方法有关键字修饰。容量增长策略默认的增长策略是每次在原容量的基础上。的怎么做到线程安全的实现了自己的,为了保证并发线程安全的共享一个,开发者在等方法中也加入了。与类继承自,的实现不止一种方式,比如。 java.util.Vector Vector与ArrayList的异同 相同点:随机存取,可通过位置序号直接获取数据。都是通过一个数组来存放元素。 不同点:Vecto...
摘要:在中引入该方法,用来替换中的每个元素。的方法,只能在或之后使用,也就是确保当前指向了一个存在的项。这里使用了,该方法可以将非对象先映射为型,然后进行比较。存储的是有序的数组,所以划分时会按照顺序进行划分。 java.util.List replaceAll 在Java8中引入该方法,用来替换list中的每个元素。 default void replaceAll(UnaryOpe...
摘要:说到复盘基础,并不是所有的都会复盘,没那个时间更没那个必要。比如,一些基础的语法以及条件语句,极度简单。思前想后,我觉得整个计划应该从集合开始,而复盘的方式就是读源码。通常,队列不允许随机访问队列中的元素。 showImg(https://segmentfault.com/img/remote/1460000020029737?w=1080&h=711); 老读者都知道,我是自学转行...
摘要:集合类关系是和的父接口。相等必须是对称的,约定只能和其它相等,亦然。和接口在中引入,这个单词是和的合成,用来分割集合以给并行处理提供方便。这些并不立即执行,而是等到最后一个函数,统一执行。 集合类关系: Collection ├List │├LinkedList │├ArrayList │└Vector │ └Stack └Set Map ...
前言 声明,本文使用的是JDK1.8 从今天开始正式去学习Java基础中最重要的东西--->集合 无论在开发中,在面试中这个知识点都是非常非常重要的,因此,我在此花费的时间也是很多,得参阅挺多的资料,下面未必就做到日更了... 当然了,如果讲得有错的地方还请大家多多包涵并不吝在评论去指正~ 一、集合(Collection)介绍 1.1为什么需要Collection Java是一门面向对象的语言,...
阅读 4598·2021-11-22 09:34
阅读 1781·2021-11-04 16:10
阅读 1969·2021-10-11 10:59
阅读 3464·2019-08-30 15:44
阅读 2245·2019-08-30 13:17
阅读 3686·2019-08-30 11:05
阅读 939·2019-08-29 14:02
阅读 2827·2019-08-26 13:34