JDK中ArrayList的实现
ArrayList底层是一个Object数组,通过维护这个数组结构来改变ArrayList的结构。
transient Object[] elementData;
ArrayList实现比较简单,就是对数组进行操作,ArrayList定义了以下常量和实例变量。
private final static int DEFAULT_CAPACITY = 10;//默认容量
private final static Object[] EMPTY_ELEMENTDATA = {};//定义的一个空Object数组
private final static Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};//默认容量的空数组
transient Object[] elementData;//存放数据的Object数组
private int size;//容量大小
private staitc final int MAX_ARRAY_SZIE = Integer.MAX_VALUE - 8;//最大数组容量 2^31 - 8
先来初始化一个ArrayList,调用了ArrayList的构造方法。
List<String> arrayList = new ArrayList<String>();
arrarList.add("a");
//ArryayList的构造方法,将elementData赋值成默认容量的空数组
public ArrayList(){
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
先来看看ArrayList的最常用的add方法具体是怎么实现,add方法有两个重载方法,一个是参数是一个对象的add(E e),一个c参数一个下标一个对象的add(int index, E e)。我们先看看add(E e)这个方法,另外一个方法和这个差不多,大同小异。核心方法是add里面的ensureCapacityInterval方法,这个方法是用来确保内部容量充足的。
public boolean add(E e){
ensureCapacityInternal(size+1);//确保容量充足
elementData[size++] = e;//将elementData数组赋值,下标对应值,并将size自增
return true;
}
确保容量充足ensureCapacityInternal方法:
public void ensureCapacityInternal(int minCapacity){
if(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA){//判断elementData是否是空数组
minCapacity = Math.max(DEFAULT_CAPACITY,minCapacity);//如果是空数组,赋值默认的容量大小10
}
ensureExplicitCapacity(minCapacity);//检查容量是否充足
}
//Math.max方法
public static int max(int a, int b){
return a > b? a : b;
}
检查容量是否充足方法ensureExplicitCapacity方法:
public void sensureExplicitCapacity(int minCapacity){
modCount++;//这个modCount是父类AbstractList的,用来记录ArrayList结构的变化次数,在迭代器中用到
if(minCapacity - elementData.length > 0){//如果容量超过了默认容量10,进行扩容操作
grow(minCapacity);
}
}
扩容方法grow:
public void grow(int minCapacity){
int oldCapacity = elementData.length;//原容量为elementData的length
int newCapacity = oldCapacity + (oldCapacity >> 1);//新容量 = 原容量 + 原容量右位移1位
if(newCapacity - minCapacity < 0 ){
newCapacity = minCapacity;
}
if(minCapacity - MAX_ARRAY_SIZE < 0){//
newCapacity = hugeCapacity(minCapacity);//当扩容容量大于最大容量时进行巨大容量扩容,最大为2^31
}
elementData = Arrays.copyOf(elementData, newCapacity);//通过Arrays.copyOf方法进行动态扩容
}
巨大容量hugeCapacity方法:
public int hugeCapacity(int minCapacity){
if(minCapacity < 0){
throw new OutOfMemoryError();
}
return (minCapacity > MAX_ARRAY_SIZE)? Integer.MAX_VALUE : MAX_ARRAY_SIZE
}
以上就是ArrayList的add(E e)方法的实现,另外的重载方法add(int index, E e)大同小异,这个方法表示从指定位置插入指定元素,都是先进行确保容量充足操作,然后再给elementData进行赋值和size自增,只不过就是赋值操作不一样。add(int index , E e)方法:
public void add(int index, E e){
rangeCheckForAdd(index);//检查下标是否越界
ensureCapacityInternal(size+1);//确保容量充足,和add(E e)是一样
System.arraycopy(elementData, index, elementData, index+1, size-index);//通过数组复制方法实现从 指定位置插入元素
elemtentData[index] = e;
size++;
}
public void rangeCheckForAdd(int index){
if(index > size || index < 0){
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
}
private String outOfBoundsMsg(int index) {
return "Index: "+index+", Size: "+size;
}
再来看看ArrayList的get(int index)方法,get方法用来获取指定位置的元素。get方法比较简单,就是将elementData数组的index下标的元素返回。
public E get(int index){
rangeCheck(index);//检查下标是否越界
return elementData[index];
}
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
再来看看ArrayList的set(int index, E e)方法,set方法是一个替换操作,将指定位置的元素替换成指定的元素。set方法也很简单,就是获取elementData数组index下标元素,然后赋值成指定元素,并将原元素返回。
public E set(int index, E element){
rangeCheck(index);//判断下标是否越界
E oldValue = elementData[index];
elementData[index] = element;
return oldValue;
}
再来看看ArrayList的remove方法,remove有两个重载方法,一个是remove(int index),一个是remove(Object o),前者表示删除指定位置的元素,后者表示删除指定元素,看完底层代码后你会发现,后者和前者实现是一样的,只不过后者是先获取指定元素的下标,然后通过下标进行删除元素,其实就是删除指定位置的元素。
先来看看remove(int index)方法,这个方法主要是通过数组复制实现元素的删除,删除后并将删除的元素返回。
public E remove(int index){
rangeCheck(index);//判断下标是否越界
modCount++;//记录ArrarList结构变化的次数
E oldValue = elementData[index];//需要删除的元素
int numMoved = size -index -1;//复制时复制的元素个数
if(numMoved > 0){
System.arraycopy(elementData, index+1, elementData, index, numMoved);
}
elementData[--size] = null;//交给GC去处理
return oldValue;
}
上面的System.copyof方法的作用就是,比如原ArrayList的是{1,2,3,4},你要删除index=1的元素就是2,删除完之后变成{1,3,4},这个方法执行完之后ArrayList就变成了{1,3,4,4},然后通过elementData[–size] = null,将最后一个元素赋值成null,ArrarList就成了{1,3,4}。
再看来来remove(Object o)方法,这个方法返回的是一个boolean值。
public boolean remove(Object o){
if(o == null){
for(int index = 0; index < size; index++){
if(elementData[index] == null){
fastRemove(index);
return true;
}
}
}else{
for(int index = 0; index < size; index++){
if(o.equals(elementData[index]){
fastRemove(index);
return true;
}
}
}
return false;
}
fastRemove(int index)方法:
public void fastRemove(int index){
modCount++;
int numMoved = size -index -1;
System.arraycopy(elementData, index+1, elementData, index, numMoved);
elementData[--size]=null;// clear to let GC do its work
}
说到了remove方法,要注意一个坑,先看下面一段代码。
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("c");
for(String str : list) {
if("a".equals(str)) {
list.remove(str);
}
}
}
这段代码会报一个java.util.ConcurrentModificationException的错误。
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)
为什么会报这个错误,新的for循环底层是通过迭代器实现的,这里就要说到了ArrayList的迭代器接口了,ArrayList中定义了一个内部类Itr,当调用iterarot()方法时,生成一个Itr对象返回。
//iterator方法
public Iterator<E> iterator() {
return new Itr();
}
//私有内部类
private class Itr implements Iterator<E> {
......
}
内部类Itr定义了expectedModCount这个属性,将modCount的值赋值给了expectedModCount,这里说到了modCount这个属性,这个属性就是记录ArrayList结构的变化次数比如add、remove操作时modCount就会自增。
int expectedModCount = modCount;
当创建一个迭代器时就会创建Iterator对象,expectedModCount就会被赋值。当调用迭代器的next()方法时,会调用checkForComodification()方法,进行对修改的同步检查。
public E next() {
checkForComodification();
......
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
再来看上面报错的代码,当调用ArrayList的remove方法时,modCount会自增,但是迭代器expectedModCount没有被重新赋值,这个时候modCount和expectedModCount是不一样的,所以当迭代器调用next()方法时,再调用checkForComodification()方法时就会报错了。
避免上面的错误,可以将代码修改成以下的样子。主要是两种方法,一种是通过下标删除,使用传统的for循环。一种是调用迭代器的remove()方法。迭代器的remove()方法会将expectedModCount重新赋值成modCount。
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
下面是修改后的代码:
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("c");
for(int i = 0; i < list.size(); i++ ) {
if("a".equals(list.get(i))) {
list.remove(i);
}
}
System.out.println(list);//输出[b, c]
}
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("c");
for (Iterator<String> iterator = list.iterator(); iterator.hasNext();) {
String string = iterator.next();
if("a".equals(string)) {
iterator.remove();
}
}
System.out.println(list);
}
以上就是ArrayList的常用方法的源码实现,其他的方法继续学习。