首先,我们看一下相关的变量:
private static final int DEFAULT_CAPACITY = 10;//当不设置长度时默认长度为10
// ArrayList(int initialCapacity) 当initialCapacity = 0时使用
private static final Object[] EMPTY_ELEMENTDATA = {};//
// 用于空参构造
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData; // non-private to simplify nested class access
private int size;
/**
* 在add元素的时候并不是加一个扩容一个长度,而是会预先扩容一定的倍数(oldCapacity +
*(oldCapacity >> 1) = 1.5倍)。比如加入了100个元素
*但是现在的长度是150,这个方法的目的就是删除剩下的50个位置
*/
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
/**
*当我们向list中add元素的时候,如果增加的元素很多,事先调用ensureCapacity预先设置list的大小,可以
*提高效率。minExpand的意思是如果现在list中已经存在元素,那么
*
*/
public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
// any size if not default element table
? 0
// larger than default for default empty table. It's already
// supposed to be at default size.
: DEFAULT_CAPACITY;
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
/*
*如果需要的最小长度大于现在list中元素的长度,就进行扩容
*
*/
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
/*
*首先将list扩容到原来的1.5倍,如果还是小于需要的,那么直接让newCapacity = minCapacity;如果需要的
*长度大于了数组的最大长度(Integer.MAX_VALUE - 8),那么就执行hugeCapacity,那么hugeCapacity的
*作用是什么呢?就是让她等于Integer.MAX_VALUE相当于给了一个底线吧;如果在
*范围内,直接将list扩容成minCapacity大小。
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
/*
*list中遍历存照是否o存在,主要注意一下当o==null的情况
*/
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;
}
/*
*这里的clone属于浅拷贝。
*/
public Object clone() {
try {
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);
}
}
/*
*这里提供了两个将list转成数组的toArray方法。具体区别可以看一下这篇文章:
*https://www.cnblogs.com/goloving/p/7693388.html
*/
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}
public <T> T[] toArray(T[] a) {
if (a.length < size)
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
/**
*向list增加一个元素时,先调用ensureCapacityInternal,
*1:如果elementData长度为0,则扩容至Math.max(DEFAULT_CAPACITY, size+1);
*2:如果elementData长度不为0,但是小于(size+1),则扩容到(size+1)
*3:如果elementData长度不为0,并且大于等于(size+1)时说明不用扩容,可以装下
*这里说的扩容到(size+1)之类的都是说调用grow()的传入参数,实际上应该是(size+1)*1.5等不同的方
*案具体看上文中对grow方法的解析
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
/*
*和上面的方法类似都会调用ensureCapacityInternal,不同指出在于会调用System.arraycopy将index之后的*元素全部朝后挪,并且将新的元素插入到index位置
*/
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
/*
*batchRemove方法有两个参数,第一个表示一个集合,第二个表示表示第一个集合中的元素是要删除还是保留。
*有点拗口:c.contains(elementData[r])这句中,遍历list中的所有元素,
*1: complement=true,从list中删除所有非list与C交集的元素。
*2: complement=false,从list中删除list与C的所有交集。
*/
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, false);
}
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
int r = 0, w = 0;
boolean modified = false;
try {
for (; r < size; r++)
if (c.contains(elementData[r]) == complement)
elementData[w++] = elementData[r];
} finally {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
//这里主要是担心contains抛出异常。还需要注意的是,这里的contains底层是使用equals来实现的,所以需要
//重写list中类的equals()和hashcode()方法
if (r != size) {
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
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;
}
这里简单说明一下DEFAULTCAPACITY_EMPTY_ELEMENTDATA和EMPTY_ELEMENTDATA的区别。按理说两者都代表了一个空的数组,那么为什么还需要两个表示呢?首先我们先关注一下ArrayLIst的构造函数:
1:首先是无参构造函数:this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
2:传入特定长度的构造函数:
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
}
3:传入一个集合的构造函数:
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
从代码中可以看出,如果传入的长度大于0, 那么就构造一个对应长度的数组。如果等于0,那么就让她等于 EMPTY_ELEMENTDATA。至此两个变量都用上了。总结一下:无参构造使用DEFAULTCAPACITY_EMPTY_ELEMENTDATA,有参构造使用EMPTY_ELEMENTDATA。
为什么会这样呢?我们来看一下add方法:
不管是public boolean add(E e)还是public void add(int index, E element),都会调用ensureCapacityInternal方法,而这个方法:
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
会判断是不是等于DEFAULTCAPACITY_EMPTY_ELEMENTDATA,如果是的,那么就调用ensureExplicitCapacity进行扩容;如果不是,就直接扩容成minCapacity大小。这里注意一下,这代表这两个add元素时的策略:
1:如果使用的是无参构造,且是第一次添加一个元素时,就默认扩容到10。当然如果调用的是addAll()方法时,可能第一次添加的就不止10个,就会扩容到minCapacity;
2:如果使用的是有参构造,添加第一个元素时,就不会使用默认值10进行扩容,而是需要多少个就给多少个。
怎么解释这个原理呢?如果选用无参构造,那就说明我不确定需要多少个,为了尽量少的进行复制扩容,那么我就先给你十个;但是如果选用的;如果有参构造,就说明我知道我需要多少个,所以无需自动帮我扩容到默认的10个。
还有一个问题就是ArrayList中有一个变量modCount,这个变量记录了list被改变的次数:比如调用remove()方法会modCount++;
那么记录修改次数有什么用呢?仔细想一下,如果我们获取到Arraylist的迭代器itrator进行遍历的中途,这个list发生了改变,那么就会出现线程安全问题。例如下面的代码:
ArrayList<Integer> list = new ArrayList<>();
for (int i = 0; i < 20; i++) {
list.add(i);
}
Iterator it = list.iterator();
while(it.hasNext()){
int i = (int) it.next();
if (i == 10){
list.remove(i);
}
System.out.println(i);
}
结果:0
1
2
3
4
5
6
7
8
9
10
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
at java.util.ArrayList$Itr.next(ArrayList.java:851)
at ExceptionTest.main(ExceptionTest.java:35)
运行会出现java.util.ConcurrentModificationException问题。为什么会出现这个问题呢?我们看调用list.iterator()返回的是new Itr();而Itr类中有一个变量expectedModCount,这个值等于创建Itr时的modCount。在每次进行遍历的时候都会比较这两个值的大小,如果两个不相等,说明list已经被修改,那么就已经出现了数据不一致的问题。
fast-fail 机制
大概意思是:在系统设计中,快速失效系统一种可以立即报告任何可能表明故障的情况的系统。快速失效系统通常设计用于停止正常操作,而不是试图继续可能存在缺陷的过程。这种设计通常会在操作中的多个点检查系统的状态,因此可以及早检测到任何故障。快速失败模块的职责是检测错误,然后让系统的下一个最高级别处理错误。
其实就是在做系统设计的时候先考虑异常情况,一旦发生异常,直接停止并上报,比如下面的这个简单的例子
//这里的代码是一个对两个整数做除法的方法,在fast_fail_method方法中,我们对被除数做了个简单的检查,如果其值为0,那么就直接抛出一个异常,并明确提示异常原因。这其实就是fail-fast理念的实际应用。
public int fast_fail_method(int arg1,int arg2){
if(arg2 == 0){
throw new RuntimeException("can't be zero");
}
return arg1/arg2;
}
在Java集合类中很多地方都用到了该机制进行设计,一旦使用不当,触发fail-fast机制设计的代码,就会发生非预期情况。我们通常说的Java中的fail-fast机制,默认指的是Java集合的一种错误检测机制。当多个线程对部分集合进行结构上的改变的操作时,有可能会触发该机制时,之后就会抛出并发修改异常**ConcurrentModificationException
**.当然如果不在多线程环境下,如果在foreach遍历的时候使用add/remove方法,也可能会抛出该异常。
之所以会抛出ConcurrentModificationException异常,是因为我们的代码中使用了增强for循环,而在增强for循环中,集合遍历是通过iterator进行的,但是元素的add/remove却是直接使用的集合类自己的方法。这就导致iterator在遍历的时候,会发现有一个元素在自己不知不觉的情况下就被删除/添加了,就会抛出一个异常,用来提示可能发生了并发修改!所以,在使用Java的集合类的时候,如果发生ConcurrentModificationException,优先考虑fail-fast有关的情况,实际上这可能并没有真的发生并发,只是Iterator使用了fail-fast的保护机制,只要他发现有某一次修改是未经过自己进行的,那么就会抛出异常。