Collection.List.ArrayList详解
简介:
ArrayList底层是基于数组存储的。它继承了AbstractList类。实现了RandomAccess,Cloneable和java.io.serializable接口。通过上三个接口它实现了快速访问,赋值和序列化的功能。
成员变量
- serialVersionUID:
适用于java序列化机制中,用来验证版本一致性,一致则可以进行反序列化。 - DEFAULT_CAPACITY:
默认容量,值是10. - EMPTY_ELEMENTDATA:
空数据,当有参构造方法的参数为 “0” 时使用该变量给elementData赋值。 - DEFAULTCAPACITY_EMPTY_ELEMENTDATA:
空数据,当无参构造方法使用时,使用该变量给elementData赋值。
两个空数据的区别:使用无参构造方法时会使用默认容量,两个空数据就是区分使用的那个构造方法构建的。以确定初次扩容的时候怎么扩容。
注意:arrayList中使用无参构造方法时,使用默认容量,但是,它创建的还是一个空数据,只有当第一次添加数据时才会使用默认容量,进行扩容。 - elementData:用于存放数据的数组。
- size:ArrayList的容量。
- MAX_ARRAY_SIZE:数组的最大容量,其值是int的最大值减8,那8个容量是用来存放数组中的length字段的。
- modcount:
因为arrayList是线程不安全的,为了保证在迭代器使用时不会出现越界而设置,因为如果迭代器开始使用后,其它线程将数组删除了几个元素就会出现越界。迭代器每次使用modcount都会加1,之后会进行对比,确保modcount没有被修改,只要不被修改就代表数组没有被修改。
方法
构造方法
- 无参构造方法
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
无参构造方法是将DEFAULTCAPACITY_EMPTY_ELEMENTDATA赋值给elementData。
- 有容量参数的构造方法:
/**
* Constructs an empty list with the specified initial capacity.
*
* @param initialCapacity the initial capacity of the list
* @throws IllegalArgumentException if the specified initial capacity
* is negative
*/
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
参数initialCapacity代表需要的容量,首先如果大于零创建一个指定大小的数组,如果等于零将EMPTY_ELEMENTDATA赋值给elementData,否则就是容量小于零,抛出异常。
- 以一个集合为参数的构造方法:
public ArrayList(Collection<? extends E> c) {
Object[] a = c.toArray();
if ((size = a.length) != 0) {
if (c.getClass() == ArrayList.class) {
elementData = a;
} else {
elementData = Arrays.copyOf(a, size, Object[].class);
}
} else {
// replace with empty array.
elementData = EMPTY_ELEMENTDATA;
}
}
讲解:该方法中首先使用传进来的集合的toArray方法将其转换为数组,然后如果a也就是c不为空就进入if内部。
之后判断c的Class对象和ArrayList的Class对象相同是否相同?
这个判断为什么会存在?因为所有继承Collection的类都可以重写toArray方法,所以他们的返回值就不一定是Object数组了,所以要进行判断。
如果相同之间赋值,不同就用copeof方法拷贝一份。
add方法
add方法有四种:
1、add(E e);
2、add(int index, E element);
3、addAll(Collection<? extends E> c);
4、addAll(int index, Collection<? extends E> c);
主要讲解2和4:
- 在指定位置添加一个元素。
/**
* Inserts the specified element at the specified position in this
* list. Shifts the element currently at that position (if any) and
* any subsequent elements to the right (adds one to their indices).
*
* @param index index at which the specified element is to be inserted
* @param element element to be inserted
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
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++;
}
讲解:该方法传入两个参数,一个位置,一个要添加的数据。
该方法首先会对要添加的位置进行检查,确定位置的合法性。
然后,对容量进行检查判断是否需要扩容,如果需要就扩容。
之后的System.arraycopy(),是将elementData从index位置开始复制,复制到elementData中从index+1开始粘贴,复制size-index位。
/**
* A version of rangeCheck used by add and addAll.
*/
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)//保证插入位置的正确性。
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}//calculateCapacity用来计算容量。
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);//该方法是用来扩容的,放在后面讲。
}
- 在指定位置添加整个集合
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
int numMoved = size - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
讲解:
经过上面几个方法的讲解相信大家基本一会自己分析了,
首先判断添加的位置合法性。
然后将集合转换为数组,
再获取要添加的长度,并判断是否需要扩容,
最后复制
grow方法
该方法用来扩容
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);
}
进入该方法首先将数组的长度存在oldCapacity中,之后进行扩容扩展到1.5倍,
但是扩展之后的容量可能还不够,如果不够就直接将需要的值赋给newCapacity,然后再判断newCapacity与数组最大容量的大小,如果newCapacity更大就直接将整数最大值赋给它。
如果还不够就没办法了。
get方法
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
因为ArrayList底层是数组实现的所以可以直接通过下标获取元素。
迭代器:iterator
迭代器与foreach非常相似。
迭代器里面定义了三个变量:
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
cursor是下一个要访问元素的位置
lastRet是上一个要访问元素的位置
expectedModCount:代表对 ArrayList 修改次数的期望值,初始值为 modCount。
其方法有hasNext,next,remove
next:
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
判断下标是否合法,如果合法cursor变为下一个要访问的元素的下表值,返回当前值并且将上一个要返回的值的下表lastRet加一。