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

Collection探究之ArrayList

2015-10-24 14:02 375 查看
ArrayList是我们在平时使用的最多的集合类之一,其本质原理为由数组组成的链表,也就是动态数组。由数组的特性我们可以得知在查找数据时,可以根据数组的下标来快速定位我们想查询的数据,效率非常高。但是也具有相应的缺点,由于数组是连续的,所以在插入数据时会牵扯到数组的复制与扩容且在多线程环境下是不安全的,因此ArrayList适合查询多,数据插入少的场合。下面我们来对ArrayList的源码进行探究,俗话说的好,知己知彼百战百胜,只有弄清楚其深层的原理,才能在使用时提高效率,不出差错。

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


我们看到这是ArrayLsit的定义,它继承自AbstractList,实现了List,RandomAccess,Cloneable,java.o.Serializable类。

List:ArrayList类实现了List类中定义的基本方法。

RandomAccess:为标记接口,它表示ArrayList能够对其内部的数据进行快速访问,也就是通过下标访问。

Cloneable:它并没有类的定义,ArrayList类实现了Cloneable接口后,表示ArrayList是可以被复制的,在接下来的源码分析中我们可以看到Cloneable的真正作用。

Serializable:表明ArrayList能够进行序列化传输。

接下来我们看一下ArrayList中定义的属性:

//DEFAULT_CAPACITY表明了ArrayList容器的默认长度为10
private static final int DEFAULT_CAPACITY = 10;

private static final Object[] EMPTY_ELEMENTDATA = {};

//表示默认初始化时为空
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

//elementData为ArrayList中存储的元素,而其类型为Object数组,transient表示在持久化时,elementData并不会被包含入Serializable中,也就是不会被持久化
transient Object[] elementData;

//size为ArrayLsit容器的大小
private int size;


接下来看一下ArrayList的构造函数,有以下三个

public ArrayList(int initialCapacity) {
//initialCapacity为ArrayList容器初始化时的容量,若大于0则生成一个大小为initialCapacity的数组
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
//若initialCapacity为0则生成一个空数组
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+                              initialCapacity);
}
}

//无参构造函数
public ArrayList() {
//生成一个默认长度的数组
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

//若传入的是一个集合,则调用toArray()方法将该集合的元素转换成为数组返还给elementData
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// 若传入的集合长度不为空,且elementData中的元素不为Object
if (elementData.getClass() != Object[].class)
//将其元素转换成为Object
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {

this.elementData = EMPTY_ELEMENTDATA;
}
}


说完构造函数,我们在看一下ArrayList中的常用方法,以便对ArrayList内部原理有更好的理解。

//size()方法用于返回ArrayList的实际长度。
public int size() {
return size;
}

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

//判断元素o是否属于该Arraylist
public boolean contains(Object o) {
return indexOf(o) >= 0;
}

public int indexOf(Object o) {
//若元素为空,那么就查找出Arraylist中第一个为null的下标并返回
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
//若不为null,那么就找出Arraylist中第一个与该元素相等的下标
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
//没有找到就返回-1
return -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 {
ArrayList<?> v = (ArrayList<?>) super.clone();
//浅复制,只复制对象的字段值。
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {

throw new InternalError(e);
}
}

//转换成为一个数组
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}


下面,我们来看一下ArrayList中的各类操作方法。

1.增加元素

//set方法是一种覆盖方法,它先确定该下标是否超出Arraylist的长度,若没有,则把index下标的元素用element覆盖掉,并返回原先Arraylist中index位置的元素。
public E set(int index, E element) {
rangeCheck(index);

E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}

//add()方法是我们使用最多的方法,用它我们能向Arraylist中增加元素。
public boolean add(E e) {
//增加元素前先判断Arraylist的容量,并再此基础上扩容。
ensureCapacityInternal(size + 1);
//在Arraylist的末尾添加元素e
elementData[size++] = e;
return true;
}

//向指定的下标index位置处,插入元素
public void add(int index, E element) {
//先确定该index是否超出Arraylist的容量
rangeCheckForAdd(index);
//用于增加Arraylist的容量
ensureCapacityInternal(size + 1);
//将原来的元素从数组中的从index位置的元素复制到index+1的位置,共移动size-index个元素,也就是说将index后的元素都向后移动一格。
System.arraycopy(elementData, index, elementData, index + 1,size - index);
elementData[index] = element;
size++;
}


下面看一下添加方法中用到的扩容方法:

public void ensureCapacity(int minCapacity) {
//先判断是否为数组是否为空。
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
? 0
: DEFAULT_CAPACITY;
//若需要的最小容量大于arraylist本来的容量,则进行扩容
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
//该方法用于确保内部容量。
private void ensureCapacityInternal(int minCapacity) {
//若数组为空,那么最小需求容量为默认容量与最小需求容量数大的值
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}

ensureExplicitCapacity(minCapacity);
}
//minCapacity为需求的容量最小值
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
//用于判断是否需要扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}

//grow方法用于扩容的真正方法。
private void grow(int minCapacity) {
//原数组的长度
int oldCapacity = elementData.length;
//新数组的长度为在原数组长度的基础上增加原数组长度上增加原数组长度的一半
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);
}


2.删除方法

//删除传入的index下标的元素
public E remove(int index) {

rangeCheck(index);

modCount++;
//先保存index下标的元素值
E oldValue = elementData(index);
//把index+1位置之后的元素都复制到index位置,也就是index+1之后的numMoved都向前移动一位
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,numMoved);
//释放最后一位的空间
elementData[--size] = null; // clear to let GC do its work
//返回原index位置的值
return oldValue;
}

//用于删除指定元素o
public boolean remove(Object o) {
//array中允许放入null元素。若元素为null,则把第一个null给移除
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
//若o不为null,那么就把第一个与o相等的元素移除
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}

//是Arraylist中的内部删除方法,无需检查是否超出arraylist容量,直接删除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; // clear to let GC do its work
}


//用于清除将Arraylist中的元素都置为null
public void clear() {
modCount++;

// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;

size = 0;
}


3.获取元素

//该方法比较简单,返回index位置的值
public E get(int index) {
rangeCheck(index);

return elementData(index);
}


4.一些特殊方法

//用于向array list中添加集合
public boolean addAll(Collection<? extends E> c) {
//先把需要添加的集合转变为数组
Object[] a = c.toArray();
//获取数组长度
int numNew = a.length;
//对arraylist进行扩容
ensureCapacityInternal(size + numNew);
//将集合元素追加到需要添加入的arraylist尾部
System.arraycopy(a, 0, elementData, size, numNew);
//更新arraylist长度
size += numNew;
return numNew != 0;
}

//用于判断index是否超出array list的容量
private void rangeCheck(int index) {
if (index >= size)
throw new         IndexOutOfBoundsException(outOfBoundsMsg(index));
}

//用于移除一定范围内的元素
protected void removeRange(int fromIndex, int toIndex) {
modCount++;
//获取需要移除的部分数组的长度,并将toindex之后的元素按次序移动到fromIndex位置。
int numMoved = size - toIndex;
System.arraycopy(elementData, toIndex, elementData, fromIndex,numMoved);

int newSize = size - (toIndex-fromIndex);
for (int i = newSize; i < size; i++) {
elementData[i] = null;
}
size = newSize;
}


以上为Arraylist中的一些主要方法,还有一些比较不常用的方法就不列举出来了,在学习collection的过程中最好还是阅读源码,这能够从源码中了解到该集合方法所表达的思想。本文中都为作者自身的理解,如果有误的地方也请大家指正。

$(".MathJax").remove();
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  集合 arraylist java