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

第三章 JAVA集合之ArrayList源码浅析

2016-09-09 21:48 471 查看

         一.概括

               ArrayList集合是我们工作中最常用的集合之一。ArrayList等同于一个动态的数组,动态的数组顾名思义就是可以自动扩容的数组,而不需要我们手动的去调整数组的大小。ArrayList是对数组进行了封装,而且还对增加了一些对这个数组进行操作的方法。

         

         二.ArrayList源码解析

         1.ArrayList实现接口和属性               

public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
private static final long serialVersionUID = 8683452581122892189L;

/**
* ArrayList用来存取数值的数组,
* 对ArrayList的操作其实就是对这个数据的操作
*/
private transient Object[] elementData;

/**
* 代表ArrayList存储元素的个数,注意size不等于elementData的长度
*
* @serial
*/
private int size;
        ArrayList实现了RandomAccess接口,RandomAccess接口里面是没有任何方法的,实现RandomAccess支持快速随机访问,这样ArrayList使用for循环遍历元素要比使用迭代器遍历元素要快。ArrayList实现了Cloneable接口,ArrayList支持浅拷贝。ArrayList实现了Serializable接口,Serializable支持序列化。

       

        2.ArrayList构造方法       

/**
*带有初始值的构造方法,
*initialCapacity这个值是设置数组的大小
*
*/
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
}

/**
* 不带参数的构造方法,
* ArrayList在初始化的时候会给数组的长度设为10
*/
public ArrayList() {
this(10);
}

/**
* 带有集合参数的构造方法
* ArrayList会将集合类型的参数转变成数组
* 再将数组中的元素复制到新的数组中,
* 再让elementData指向新的数据
*/
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
size = elementData.length;
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
}
       

       3.ArrayList的其他常用方法

       3.1ArrayList扩容方法:ensureCapacity     

/**
*
*
* 预先设置ArrayList的容量大小,如果传入的参数minCapacity小于
* ArrayList中elementData数组的长度就不做改变
* 如果minCapacity参数大于elementData数组的长度,就会对
* 将elementData的大小设置为原来的1.5倍,从新设置的数组长度如果还小于参数minCapacity
* 那么数组长度就直接变为minCapacity
*/
public void ensureCapacity(int minCapacity) {
if (minCapacity > 0)
ensureCapacityInternal(minCapacity);
}

private void ensureCapacityInternal(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

private void grow(int minCapacity) {
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);
}

private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}<em>
</em>

    在要对ArrayList进行插入操作的时候,一般先要调用ensureCapacity方法,确定ArrayList是否能需要扩容。

    3.2ArrayList的添加和删除方法

/**
* 向ArrayList添加元素,在添加元素之前会先判断ArrayList是否需要扩容
* 元素e将被添加在ArrayList的最末尾的位置
*/
public boolean add(E e) {
//将会用size + 1和elementData.length比较大小,如果size + 1大于elementData.length
//将会对数组elementData进行扩容
ensureCapacityInternal(size + 1);
//这句话可以分解为elementData[size] = e;size++这两步
elementData[size++] = e;
return true;
}

/**
*在指定位置添加元素
*/
public void add(int index, E element) {
//判断index是否小于0或大于size,如果满足将抛出异常
rangeCheckForAdd(index);
//判断ArrayList是否需要扩容
ensureCapacityInternal(size + 1);
/*
*将数组elementData从下标index开始的元素,长度为size - index(即index到size之间的元素)复制到
*数组elementData以index+1开始的位置,再将element放在数组index的位置
*/
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}

/**
*按下标移除元素
*/
public E remove(int index) {
//先判断index是否大于等于size,如果大于等于将报异常
rangeCheck(index);

modCount++;
//防止数组index下标位置所指向的内存在移动元素的时候被占用
E oldValue = elementData(index);
//需要在elementData数组中移动元素的长度
int numMoved = size - index - 1;
if (numMoved > 0)
//将elementData从下标index+1开始的元素到,长度为numMoved,移除到elementData的下标为index开始的地方
//这样就能将elementData中下标为index的元素覆盖掉
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // Let gc do its work

return oldValue;
}

/**
* 按元素内容移除
* 先找到元素在数组中的下标,再按下标移除
*
*/
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
/*
*按下标移除元素,为什么不直接调用remove,而要调用fastRemove方法
*fastRemove方法和remove方法比起来少了边界的判断和旧元素的赋值,更快速
*/
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}

/*
* 快速移除元素,是一个私有方法,和remove方法比起来少了对index的判断
*
*/
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // Let gc do its work
}

/**
* 清空ArrayList,将elementData中的所有元素设置为null
*
*/
public void clear() {
modCount++;

// Let gc do its work
for (int i = 0; i < size; i++)
elementData[i] = null;

size = 0;
}

/**
* 向ArrayList增加一个集合
* 先将集合转变为数组,在将数组中的元素复制到elementData中
*/
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew);
//将a中所有的元素复制到elementData中size之后的位置
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
       ArrayList中元素的添加和删除操作都得依赖 System的arraycopy方法, System.arraycopy(a, 0, elementData, size, numNew);的意思是将数组a中从下标0开始,到0+numNew=numNew之间的元素移动到elementData数组中,但存放的位置是在size之后的,这样说应该能懂这个方法是干什么的了,这个操作是比较花时间的,所以对于添加和删除操作比较的频繁的时候,LinkedList是要好于ArrayList的,所以平时不要一味的依赖ArrayList。

    3.3ArrayList一些简易方法    

/**
*
*获取ArrayList的大小,即获取size属性就可以了
*/
public int size() {
return size;
}

/**
* 判断ArrayList是否为空
*/
public boolean isEmpty() {
return size == 0;
}

/**
*判断ArrayList是否包含某一个元素
*/
public boolean contains(Object o) {
return indexOf(o) >= 0;
}

/**
* 返回元素在ArrayList中的首位置,
* 如果ArrayList中没有这个元素,就返回-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;
}

/**
* 返回元素在ArrayList中的最后出现的位置,
* 如果ArrayList中没有这个元素,就返回-1
*/
public int lastIndexOf(Object o) {
if (o == null) {
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
for (int i = size-1; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}

/**
* 对ArrayList进行浅拷贝
*/
public Object clone() {
try {
@SuppressWarnings("unchecked")
ArrayList<E> v = (ArrayList<E>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
throw new InternalError();
}
}

/**
* 将ArrayList转变为数组
*
*/
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}

/**
* 将ArrayList转变为参数数组a并返回
*/
@SuppressWarnings("unchecked")
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;
}

// Positional Access Operations

@SuppressWarnings("unchecked")
E elementData(int index) {
return (E) elementData[index];
}

/**
*按下标返回ArrayList中的元素
*/
public E get(int index) {
//检查index是否大于等于size,如果大于等于将报数组越界异常
rangeCheck(index);

return elementData(index);
}

/**
*按下标设置元素
*/
public E set(int index, E element) {
//检查index是否大于等于size,如果大于等于将报数组越界异常
rangeCheck(index);

E oldValue = elementData(index);
//下标index将指向新的元素
elementData[index] = element;
return oldValue;
}

       还有一些其他的方法就不在这里一一列出来了,大家感兴趣的可以自己去看看

      三.总结

           1.如果在声明ArrayList的时候不设初始值,那么ArrayList会默认设置一个容量为10的数组,但是ArrayList的大小还是为0的

           2.ArrayList可以看成是一个动态的数组,相比较与数组来说,ArrayList可以用ensureCapacityInternal方法自动扩容,在向ArrayList添加元素的时候,ArrayList会先检测现在数组的容量是否足够,若不够,ArrayList会将数组的容量扩大为原来的2.5倍,如果还不够,就用传进来的参数作为数组的容量。如果我们在知道存储元素多少的时候,尽量给ArrayList设定一个初始容量,这样就可以减少ArrayList的自动扩容,减少数组元素的移动来提高程序的性能。

           3.ArrayList在增加或删除元素的时候,都需要将数组里面的元素移动到另一个数组中,这是非常耗时间的,所以遇到需要对元素进行频繁的删除和添加的集合时,这时候选用LinkedList要比ArrayList好很多,如果遇到在集合中查找元素比较多的操作时,ArrayList又是一个不错的选择,因为ArrayList直接可以用下标就可以获取到元素。

          4.在研读ArrayList源码的时候要注意ensureCapacityInternal扩容方法和System.arraycopy(original, 0, copy, 0,length)方法。

       参考文章:http://www.cnblogs.com/ITtangtang/p/3948555.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: