您的位置:首页 > 编程语言 > Java开发

java8 ArrayList源码阅读【2】- 总结

2017-04-16 13:33 513 查看
上一篇文章 java8 ArrayList源码阅读已经分析了ArrayList源码,现对ArrayList做个小结。

ArrayList一个动态数组,其本质也是用数组实现的,它具有:随机访问速度快,插入和移除性能较差(数组的特点);支持null元素;有顺序;元素可以重复;线程不安全;

ArrayList实现了List接口以及list相关的所有方法,它允许所有元素的插入,包括null。另外,ArrayList和Vector除了线程不同步之外,大致相等。

ArrayList继承AbstractList并实现了List接口,RandomAccess接口,Cloneable接口,Serializable接口,因此它支持快速访问,可复制,可序列化。

public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable


ArrayList 默认初始化大小为0(不是10)

//在以前的版本,ArrayList 默认初始化大小为10,其默认构造函数为
public ArrayList() {
this(10);
}

//jdk8版本,默认构造方法,使用空数组初始化,容量大小默认为0
public ArrayList() {
super();
this.elementData = EMPTY_ELEMENTDATA; //EMPTY_ELEMENTDATA={}
}


ArrayList内部数组的扩容:

当向ArrayList添加新元素前,会调用ensureCapacityInternal(int)方法来保证内部数组空间足够。

//添加新元素时使用,使用指定参数设定最小容量,minCapacity为size+1
private void ensureCapacityInternal(int minCapacity) {
//如果数组为空,使用默认值和参数中较大者作为容量预设值
if (elementData == EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}

//根据指定参数增加数组容量
private void ensureExplicitCapacity(int minCapacity) {
modCount++; //上面提到,只要涉及数组结构的改变(这里是数组大小改变),该变量改变

//如果参数大于数组容量,就增加数组容量
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}

//增加容量,以确保它可以至少持有由参数指定的元素的数目
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;

//新容量为原来的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);

//如果仍比参数小,则新容量大小取参数的值
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//若新容量大于默认的最大值,则检查是否溢出
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);

elementData = Arrays.copyOf(elementData, newCapacity);
}


从grow()方法可以看出,如果内部数组空间不够,ArrayList每次扩容都是扩1.5倍,然后调用Arrays类的copyOf方法,把元素重新拷贝到一个新的数组中去。扩1.5倍的原因也好理解,因为扩容本质最后是将元素全部重新拷贝到一个新的数组,是耗时的,所以如果每次add都需要扩容一次,那效率是非常低的。所以预存一定空间以供add使用。

ArrayList内部数组的收缩:

利用trimToSize()方法,由于数组扩容时是扩大1.5倍,因此通常会出现容量大于实际有效元素数量,可以将数组占用内存大小压缩到数组中元素个数,可以在内存紧张时调用。同样也是通过Arrays.copyof方法将元素拷贝到一片新的内寸空间,老的空间由gc回收。

//释放数组空余的空间,容量为数组实际元素数量(因为容量通常会大于实际元素数量,
public void trimToSize() {
modCount++;     //涉及数组的改变,modCount都会改变,留意该变量,后面会频繁出现
if (size < elementData.length) {    //如果数组实际元素数量比数组容量小,则重新拷贝到一片新的内寸空间
elementData = Arrays.copyOf(elementData, size);
}
}


indexOf(Object o)

该方法会根据是否为null使用不同方式判断。如果是元素为null,则直接比较地址,否则使用equals的方法比较,加快比较效率。lastIndexOf(Object o) 同理。

//返回特定元素在数组首次出现的位置(遍历的方式),会根据是否为null使用不同方式判断。不存在就返回-1
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;  //不存在返回-1
}


clone() 浅拷贝

//返回副本,浅拷贝
//复制过程数组发生改变会抛出异常
public Object clone() {
try {
//java.lang.Object.clone()只是一种浅复制,所以,v的elementData引用的还是当前ArrayList的elementData的引用
java.util.ArrayList<?> v = (java.util.ArrayList<?>) super.clone();
//对原来ArrayList的elementData进行一个数组拷贝,然后赋值给v的elementData
//这样,v的elementData就是原先ArrayList的elementData的一个副本,不再是内存中同一块数组
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);
}
}


举个例子说明:

public class Main {
static class A {
int a;

A(int a) {
this.a = a;
}

@Override
public String toString() {
return super.toString() + " : " + a;
}
}

public static void main(String[] args) {
ArrayList<A> a1=new ArrayList<>();
a1.add(new A(1));
a1.add(new A(2));
ArrayList<A> a2= (ArrayList<A>) a1.clone();
System.out.println(a1);
System.out.println("---");
System.out.println(a1);
System.out.println("---");
a2.get(1).a=100;
System.out.println(a1);
System.out.println("---");
System.out.println(a1);
System.out.println("---");
}
}
/*
* 输出结果:
[Main$A@1b6d3586 : 1, Main$A@4554617c : 2]
---
[Main$A@1b6d3586 : 1, Main$A@4554617c : 2]
---
[Main$A@1b6d3586 : 1, Main$A@4554617c : 100]
---
[Main$A@1b6d3586 : 1, Main$A@4554617c : 100]
---
*/


从上述输出结果可以看出,尽管克隆的数组不是跟原数组同一块内存,但内容引用是同样的,指向同一个地址,可以从输出结果看出,因此改变克隆后数组的元素内容,相应的原数组内容发生相应变化,因此clone()方法是浅拷贝。

(注:对于基本类型,如int,则不会发生这种情况。)

add(),addAll(),remove(),removeRange()

关于对数组元素的添加或删除,其实现均是用System.arraycopy()来移动的。

void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);

removeAll 和 retainAll

removeAll 是删除指定集合的元素,retainAll则是仅保留指定集合的元素。

//部分应用场景:
//并集
list1.addAll(list2);
//交集
list1.retainAll(list2);
//差集
list1.removeAll(list2);
//无重复并集
list2.removeAll(list1);
list1.addAll(list2);


而它们背后的实现则比较巧妙。

//删除指定集合的元素
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);  //检查集合是否为空
return batchRemove(c, false);   //调用batchRemove,complement为false
}

//与removeAll相反,仅保留指定集合的元素
public boolean retainAll(Collection<?> c) {
Objects.requireNonNull(c);  //检查集合是否为空
return batchRemove(c, true);    //调用batchRemove,complement为true
}

//complement true时从数组保留指定集合中元素的值,为false时从数组删除指定集合中元素的值。
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
int r = 0, w = 0;   //w为数组更新后的大小
boolean modified = false;
try {
//遍历数组,并检查元素是否在指定集合中,根据complement的值保留特定值到数组
//若complement为true即保留,则将相同元素移动到数组前端
//若complement为false即删除,则将不同元素移动到数组前端
for (; r < size; r++)
if (c.contains(elementData[r]) == complement)
elementData[w++] = elementData[r];
} finally {
//如果r!=size则说明c.contains(elementData[r])抛出异常
if (r != size) {
//将数组未遍历的部分添加
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
//如果w!=size说明进行了删除操作,故需将删除的值赋为null
if (w != size) {
// clear to let GC do its work
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;   //更新数组容量
modified = true;
}
}
return modified;
}


可以看出它们的实现类似于jvm中垃圾回收机制中的整理-清除方法,即先将需要保留的元素紧凑地移到一块儿,剩下的即为可以删除的元素,然后剩下的元素置null。

modCount变量[fail-fast机制]

记录了数组的修改次数,在ArrayList的所有涉及结构变化的方法中都增加modCount的值,包括:add()、remove()、addAll()、removeRange()及clear()方法。这些方法每调用一次,modCount的值就加1。

该变量迭代器等方面体现。

//返回普通迭代器
public Iterator<E> iterator() {
return new java.util.ArrayList.Itr();
}


java.util.ArrayList.Itr()是ArrayList的一个内部类,其包含了一个int型的属性:expectedModCount,这个属性在Itr类初始化时被赋予ArrayList对象的modCount属性的值。该内部类的每个方法都涉及到checkForComodification()方法。

//检查数组是否修改,根据expectedModCount和modCount判断
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}


因此在使用迭代器迭代过程中,不允许对数组结构做修改,否则会抛出异常 java.util.ConcurrentModificationException。

举例如下:

public class Main {
static class A {
int a;

A(int a) {
this.a = a;
}

@Override
public String toString() {
return super.toString() + " : " + a;
}
}

public static void main(String[] args) {
ArrayList<A> a1=new ArrayList<>();
a1.add(new A(1));
a1.add(new A(2));
Iterator i=a1.iterator();
System.out.println(i.next()+"");
a1.get(1).a=100;    //ok
a1.add(new A(3));   //error
System.out.println(i.next()+"");
}
}
/*
* 输出为:
Main$A@1b6d3586 : 1
Exception in thread "main" java.util.ConcurrentModificationException
*/


forEach() java8新增方法

//遍历每个元素做指定操作,java8新增方法
@Override
public void forEach(Consumer<? super E> action) {
Objects.requireNonNull(action);  //检查action是否为空
final int expectedModCount = modCount;
@SuppressWarnings("unchecked")
final E[] elementData = (E[]) this.elementData;
final int size = this.size;
for (int i = 0; modCount == expectedModCount && i < size; i++) {
action.accept(elementData[i]);
}
//检查数组结构是否修改
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}


java8前,遍历集合通常使用for循环来遍历,比如遍历输出:

for(A a:aList){
System.out.println(a);
}


现在可以采用

// lambda
aList.foreach(a -> System.out.println(a);
// or
aList.forEach(new Consumer<A>() {
@Override
public void accept(A a) {
System.out.println(a);
}
});


spliterator() java8新增方法

类似Iiterator,用于多线程,仍需学习。

removeIf() java8新增方法

其实现有点类似batchRemove()的实现方式,先将需要保留的元素紧凑地移到一块儿,剩下的即为可以删除的元素。同时还利用了BitSet来记录要删除元素的下标。

//判断条件是否满足,满足则删除,java8新增方法
@Override
public boolean removeIf(Predicate<? super E> filter) {
Objects.requireNonNull(filter);     //检查filter是否为空
int removeCount = 0;    //记录要删除的数目
final BitSet removeSet = new BitSet(size);  //利用BitSet来记录删除元素的下标
final int expectedModCount = modCount;
final int size = this.size;
for (int i = 0; modCount == expectedModCount && i < size; i++) {
@SuppressWarnings("unchecked")
final E element = (E) elementData[i];
//如果当前元素符合条件,则BitSet标记当前下标
if (filter.test(element)) {
removeSet.set(i);
removeCount++;
}
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}

final boolean anyToRemove = removeCount > 0;    //根据removeCount变量判断是否有满足条件的元素
if (anyToRemove) {
final int newSize = size - removeCount; //删除后数组元素的个数
//遍历数组,将不满足的元素前移到数组前端
for (int i = 0, j = 0; (i < size) && (j < newSize); i++, j++) {
i = removeSet.nextClearBit(i);
elementData[j] = elementData[i];
}
//将更新后的数组没用的元素置null
for (int k = newSize; k < size; k++) {
elementData[k] = null;  // Let gc do its work
}
this.size = newSize;
//检查数组是否修改
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
modCount++;
}

return anyToRemove;
}


replaceAll() java8新增方法

//根据operator进行替换,java8新增方法
@Override
@SuppressWarnings("unchecked")
public void replaceAll(UnaryOperator<E> operator) {
Objects.requireNonNull(operator);
final int expectedModCount = modCount;
final int size = this.size;
for (int i = 0; modCount == expectedModCount && i < size; i++) {
elementData[i] = operator.apply((E) elementData[i]);
}
//检查数组是否修改
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
modCount++;
}


sort() java8新增方法

//根据Comparator排序,java8新增方法
@Override
@SuppressWarnings("unchecked")
public void sort(Comparator<? super E> c) {
final int expectedModCount = modCount;
Arrays.sort((E[]) elementData, 0, size, c);
//检查数组是否修改
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
modCount++;
}


java8之前,集合排序要用Collections.sort()方法,现在可以直接用自带的sort方法了。

// lambda
aList.sort((a,b)->(a.a-b.b));
// or
a1.sort(new Comparator<A>() {
@Override
public int compare(A o1, A o2) {
return o1.a-o2.a;
}
});
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息