Java集合学习:ArrayList的实现原理

1.概述
        ArrayList是List接口的可变数组的实现,其实现了所有可选列表操作,允许包括null在内的所有元素。基本上等同于Vector,但它只对writeObject()和readObject()进行了同步。所以建议在单线程中才使用ArrayList,而在多线程中可以选择Vector或者CopyOnWriteArrayList。
2.接口
        首先看一下ArrayList的定义:
   
   
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
        可以看出,ArrayList是支持泛型的,它继承自AbstractList,实现了List、RandomAccess、Cloneable、java.io.Serializable接口。
        AbstractList提供了List接口的默认实现,个别方法是作为了抽象方法,例如get方法;RandomAccess是一个标记接口,用来表明其支持快速随机访问,该接口的主要目的是允许一般的算法更改其行为,从而在将其应用到随机或者连续访问列表时能提供良好的性能(关于该接口的更详细描述,可以参考 关于RandomAccess接口的研究 );实现了 Cloneable接口的类,可以调用 Object.clone方法返回该对象的浅拷贝;实现了 java.io.Serializable 接口以启用序列化功能,该接口仅用于标识可序列号的语义。
3.实现
        我们通过分析ArrayList的源代码来更好的理解其原理。
属性:
ArrayList只定义了两个私有属性:
   
   
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer.
*/
private transient Object[] elementData;
 
/**
* The size of the ArrayList (the number of elements it contains).
*
* @serial
*/
private int size;
       elementData用来存储ArrayList内的元素,size表示包含的元素的数量。另外,这边有一个关键字可能需要解释一下: transient Java的serialization提供了一种持久化对象实例的机制。当持久化对象时,可能有一个特殊的对象数据成员,我们不想   用serialization机制来保存它。为了在一个特定对象的一个域上关闭serialization,可以在这个域前加上关键字transient。 transient是Java语言的关键字,用来表示一个域不是该对象串行化的一部分。当一个对象被串行化的时候,transient型变量的值不包括在串行化的表示中,非transient型的变量是被包括进去的。  
构造方法:
       提供了三种方式的构造器,可以构造指定初始容量的,也可以指定默认初始容量为10的,也可构造一个包含指定collection的元素的列表,其元素是按照collection的迭代器返回的顺序排列。
   
   
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
}
 
public ArrayList() {
this(10);
}
 
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);
}
添加元素:
       ArrayList提供了set(int index, E element)、add(E e)、add(int index, E element)、addAll(Collection<? extends E> c)、addAll(int index, Collection<? extends E> c)这些添加元素的方法,下面我们按照源码来分析具体方法的实现。
首先是 add(E e)方法:
   
   
public boolean add(E e) {
ensureCapacity(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
public void ensureCapacity(int minCapacity) {
modCount++;
int oldCapacity = elementData.length;
if (minCapacity > oldCapacity) {
Object oldData[] = elementData;
int newCapacity = (oldCapacity * 3)/2 + 1;
if (newCapacity < minCapacity)
newCapacity = minCapacity;
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
}
       我们看到add(E e)方法中先调用了 ensureCapacity方法,该方法是用来确保容量的。在注释中有“ Increments modCount!! ”,看来是增加modCount的值。下面我们分析一下这个方法:
       当modCount++之后,判断minCapacity(即size+1)是否大于oldCapacity(即elementData.length),若大于,则调整容量为(oldCapacity*3)/2+1和minCapacity中较大的一个,最后调用Arrays的copyOf方法,返回一个内容为原数组元素,大小为新容量的数组赋给elementData;否则不进行操作。 所以调用ensureCapacity能够保证elementData[size]不会出现越界的情况。但扩展容量会执行数组内容的复制,所以若能提前大致判断list的长度,将有效的提高运行速度。
接下来我们分析add(int index,E element)方法,该方法是在指定位置插入元素:
    
    
/**
* 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) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(
"Index: "+index+", Size: "+size);
 
ensureCapacity(size+1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
       该方法同样会调用ensureCapacity来调整容量,然后调用System.arraycopy方法,将elementData数组从index开始的(size-index)个元素复制到从index+1开始的位置(通俗的将就是将index开始的元素都向后移动一个位置),然后将index位置复制为element。
       接下来是addAll(Collection<? extends E> c)方法,该方法比较简单:
    
    
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacity(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
       先通过toArray方法转化为数组,然后通过System.arraycopy方法进行复制。
       最后还有一个 set(int index,E element)方法:
    
    
public E set(int index, E element) {
RangeCheck(index);
 
E oldValue = (E) elementData[index];
elementData[index] = element;
return oldValue;
}
       该方法是用新元素来代替旧元素,比较简单,就不详细描述了。
读取元素
get(int index):
    
    
public E get(int index) {
RangeCheck(index);
 
return (E) elementData[index];
}
       就是返回elementData[index]就是了,但前边有个RangeCheck(index)方法,该方法就是检查一下index是不是超出数组界限而已。
删除
remove(int index):
    
    
public E remove(int index) {
RangeCheck(index);
 
modCount++;
E oldValue = (E) elementData[index];
 
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // Let gc do its work
 
return oldValue;
}
       首先是查看index是否越界,然后modCount++,保留要被移除的元素,然后将移除位置之后的元素向前挪动,然后将末尾的元素置空,最后返回被移除的元素就ok了。
remove(Object o):
    
    
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移除对象,该方法基本和remove一致,只不过它跳过了判断边界的处理,而且并不返回被移除的元素。
       另外还有像size,toArray等方法,这些方法实现起来都比较简单,其作用通过方法名也很容易理解。
4.总结
       建议大家去阅读一下Java的源码,并且可以通过EA工具将源码转为类图进行分析。ArrayList总体来说是比较简单的,而且实现也不复杂。但其作用也是不容忽视的。
//TODO一下,有时间会补充分析关于Fail-Fast机制和removeRange方法的内容

请大家关注一下本系列的其他文章:Java集合学习:HashMap的实现原理和工作原理


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值