JAVA集合源码学习——ArrayList

JAVA集合源码学习——ArrayList

ArrayList概述

1)ArrayList是基于长度可动态增长的数组实现List接口的java集合类
2)ArrayList类内部维护者一个动态可再分配的Object[]数组,每一个类对象都有一个capacity属性,表示所封装的Object[]对象数组的长度,但往ArrayList添加元素时,该属性值会自动增加,体现了动态的特点
3)如果要往ArrayList里一次添加大量元素可以调用ensureCapacity方法传入满足添加元素的最终容量,减少ArrayList每次自动重分配的次数从而提高性能
4)ArrayList和vector区别是:ArrayList是异步的即线程不安全,当有多线程访问该集合时,程序要手动保证集合的同步;相反vector则是同步,线程安全的
5)ArrayList和Collection关系:
ArrayList继承关系图

ArrayList数据结构

arrayList的数据结构:
arryList数据结构图arryList底层的数据结构就是数组,所有对ArrayList的操作都是基于对数组的操作

ArrayList源码分析

1、继承结构图
ArrayList继承图其继承结构:
ArrayList extends AbstractList
AbstractList extends AbstractCollection
分析:
1)ArrayList为什么要继承abstractList而不直接实现list接口?其实采用的就是适配器的思想,方便ArrayList的代码更简洁
2)ArrayList实现的接口
ArrayList实现接口
RandomAccess接口:标记性接口,用于快速存取,即实现了该接口后用for循环遍历比使用迭代器迭代的效率更高
3)Cloneable接口:可以使用Object.Clone()方法
4)Serializable接口:实现序列化接口,可以实现序列化功能保存
5)类中属性
ArrayList内部参数6)构造函数
arryList构造函数
1、无参构造函数

Constructs an empty list with an initial capacity of ten.  这里就说明了默认会给10的大小,所以说一开始arrayList的容量是10.ArrayList中储存数据的其实就是一个数组,这个数组就是elementData
private transient Object[] elementData;
public ArrayList() {  
    super();        //调用父类中的无参构造方法,父类中的是个空的构造方法
    this.elementData = EMPTY_ELEMENTDATA;//EMPTY_ELEMENTDATA:
    是个空的Object[], 将elementData初始化,elementData也是个Object[]类型。
    空的Object[]会给默认大小10,等会会解释什么时候赋值的。
}

2、有参构造函数

 * 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) {
    super(); //父类中空的构造方法
    if (initialCapacity < 0)    //判断如果自定义大小的容量小于0,则报下面这个非法数据异常
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    this.elementData = new Object[initialCapacity]; //将自定义的容量大小当成初始化elementData的大小
}

7)核心方法
1、boolean add(E);//默认直接在末尾加元素

 *Appends the specified element to the end of this list.添加一个特定的元素到list的末尾。
 *@param e element to be appended to this list
 *@return <tt>true</tt> (as specified by {@link Collection#add}
 */
public boolean add(E e) {    
//确定内部容量是否够了,size是数组中数据的个数,因为要添加一个元素,所以size+1,先判断size+1的这个个数数组能否放得下,就在这个方法中去判断是否数组.length是否够用了。
    ensureCapacityInternal(size + 1);  // Increments modCount!!
 //在数据中正确的位置上放上元素e,并且size++
    elementData[size++] = e;
    return true;
}

分析:
ensureCapacityInternal(xxx);确定内部容量的方法

*
*@param
*/
private void ensureCapacityInternal(int minCapacity) {
    if (elementData == EMPTY_ELEMENTDATA) {//看,判断初始化的elementData是不是空的数组,也就是没有长度
//因为如果是空的话,minCapacity=size+1;其实就是等于1,空的数组没有长度就存放不了,所以就将minCapacity变成10,也就是默认大小,但是带这里,还没有真正的初始化这个elementData的大小。
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
//确认实际的容量,上面只是将minCapacity=10,这个方法就是真正的判断elementData是否够用
    ensureExplicitCapacity(minCapacity);
}

ensureExplicitCapacity(xxx);

 *
 *@param
*/
 private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    // overflow-conscious codeminCapacity如果大于了实际elementData的长度,那么就说
    elementData数组的长度不够用,不够用那么就要增加elementData的length。
    minCapacity到底是什么呢,这里给你们分析一下
    第一种情况:由于elementData初始化时是空的数组,那么第一次add的时候,minCapacity=size+1;也就minCapacity=1,在上一个方法(确定内部容量ensureCapacityInternal)就会判断出是空的数组,就会给将minCapacity=10,到这一步为止,还没有改变elementData的大小
    第二种情况:elementData不是空的数组了,那么在add的时候,minCapacity=size+1;也就是minCapacity代表着elementData中增加之后的实际数据个数,拿着它判断elementData的length是否够用,如果lengt不够用,那么肯定要扩大容量,不然增加的这个元素就会溢出。 if (minCapacity - elementData.length > 0)
//arrayList能自动扩展大小的关键方法就在这里了
        grow(minCapacity);
}

grow(minCapacity)

*
 *@param
 */
 private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;  //将扩充前的elementData大小给oldCapacity
    int newCapacity = oldCapacity + (oldCapacity >> 1);//newCapacity就是1.5倍的oldCapacity
    if (newCapacity - minCapacity < 0)//这句话就是适应于elementData就空数组的时候,length=0,那么oldCapacity=0,newCapacity=0,所以这个判断成立,在这里就是真正的初始化elementData的大小了,就是为10.前面的工作都是准备工作。
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)//如果newCapacity超过了最大的容量限制,就调用hugeCapacity,也就是将能给的最大值给newCapacity
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
//新的容量大小已经确定好了,就copy数组,改变容量大小咯。
    elementData = Arrays.copyOf(elementData, newCapacity);
}

hugeCapacity()

 *@param
 */
 //这个就是上面用到的方法,很简单,就是用来赋最大值。
private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();//如果minCapacity都大于MAX_ARRAY_SIZE,那么Integer.MAX_VALUE返回,反之将MAX_ARRAY_SIZE返回。因为maxCapacity是三倍的minCapacity,可能扩充的太大了,就用minCapacity来判断了。//Integer.MAX_VALUE:2147483647MAX_ARRAY_SIZE:2147483639  
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}

2、void add(int , E);//在特定位置添加元素

 *
 */
 public void add(int index, E element) {
    rangeCheckForAdd(index);//检查index也就是插入的位置是否合理。//跟上面的分析一样,具体看上面
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    //这个方法就是用来在插入元素之后,要将index之后的元素都往后移一位,
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
                     //在目标位置上存放元素
    elementData[index] = element;
    size++;//size增加1
}

rangeCheckForAdd(index)
rangeCheckForAddSystem.arraycopy(xxx)就是把elementData在插入位置后的所有元素往后面移一位

小结:
正常情况下会扩容1.5倍,特殊情况下(新扩展数组大小已经达到了最大值)则只取最大值
当我们调用add方法时,实际的函数调用
add函数底层调用3)remove(int);//删除指定位置上的元素

 *@
 */
 public E remove(int index) {
    rangeCheck(index);//检查index的合理性

    modCount++;//这个作用很多,比如用来检测快速失败的一种标志。
    E oldValue = elementData(index);//通过索引直接找到该元素

    int numMoved = size - index - 1;//计算要移动的位数。
    if (numMoved > 0)
    //这个方法也已经解释过了,就是用来移动元素的。
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
                         //将--size上的位置赋值为null,让gc(垃圾回收机制)更快的回收它。
    elementData[--size] = null; // clear to let GC do its work
    //返回删除的元素。
    return oldValue;
}

4)remove(Object);//从这里可以看ArrayList是可以存放null值

 *
 */
 //感觉这个不怎么要分析吧,都看得懂,就是通过元素来删除该元素,就依次遍历,如果有这个元素,就将该元素的索引传给fastRemobe(index),使用这个方法来删除该元素,
 //fastRemove(index)方法的内部跟remove(index)的实现几乎一样,这里最主要是知道arrayList可以存储null值
   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;
}

5)clear();//将elementData中每个元素赋值为null,等待垃圾回收回收

 *@return <tt>true</tt> (as specified by {@link Collection#add}
 */
 public void clear() {
    modCount++;

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

    size = 0;
}

6)set()方法
set方法源码图
7)indexof方法

 *@
 // 从首开始查找数组里面是否存在指定元素
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;
}

8)get(index)方法
get方法图

总结

1)ArrayList可以存放null
2)ArrayList本质是一个elementData数组
3)ArrayList区别于一般数组是在于自动扩展大小,依赖的方法就是grow()方法
4)ArrayList由于数据结构是数组因此查询数据很快但删除和插入方面性能不高
5)ArrayList实现了RandomAccess,所以遍历时推荐使用for循环

猜你喜欢

转载自blog.csdn.net/CSDNadvancer/article/details/85998809