ArrayList源码学习笔记

1.简介:


public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
  1. 继承了AbstractList,提供相关的修改、删除、遍历等功能
  2. 实现了RandomAccess接口,提供了随机访问访问功能,即可以通过元素序号访问元素
  3. 实习Cloneable接口,可以克隆
  4. 实现了Serializable接口,实现了序列化的功能

   其他:ArrayList不是线程安全的,多线程可以考虑使用Vector或者CopyOnWriteArrayList

   特点:
     

       1.  不是线程安全的,不是线程同步的。

         2.允许null在内的所有元素。

         3.ArrayList中存放顺序和添加顺序是一致的。并且可重复元素。

         4. ArrayList实现了可变大小的数组。


 

2.ArrayList属性:

  

  private static final long serialVersionUID = 8683452581122892189L;

    //数组的初始容量

    private static final int DEFAULT_CAPACITY = 10;
    //当用户指定容量为0时
    //elementData=EMPTY_ELEMENTDATA

    private static final Object[] EMPTY_ELEMENTDATA = {};

    //当用户未指定容量时
    //elementData=DEFAULTCAPACITY_EMPTY_ELEMENTDATA
    //当用户第一次添加元素时,会扩容为10
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    
    //transient修饰代表不会被序列化
    //ArrayList用该数组保存数据

    transient Object[] elementData; // non-private to simplify nested class access


    private int size;

 3.构造函数

       (1) 由用户指定容量的构造函数

  public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

      当 initialCapacity=0时,elementData = EMPTY_ELEMENTDATA;

   (2)无参构造函数

     

 public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

 3.重要方法:

     1.add()

      

 public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

    线程安全:我们注意到elementData[size++] = e;这条语句其实就是添加元素的关键,其实这条语句可以拆成两条语句来执行也就是elementData[size] = e 和 size++,试想一下,如果在多线程访问同一个list的时候,线程A执行到elementData[size] = e时,时间片到了让出CPU,此时线程B执行,当线程B执行到elementData[size] = e此时size还没有加一,(假设此时size=10)也就是说线程A在list[10]位置上添加的元素被线程B添加的元素覆盖了,然后A,B两个线程都执行size++;这样造成的影响就是size变成12了,但是在第11个位置上却没有元素也就是说list[11]=null
 

      add方法首先调用ensureCapacityInternal

 private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

    可以看出,如果ArrayList是通过无参构造方法初始且第一次add,此时minCapacity=1,然后minCapacity变为10,然后调用

    ensureExplicitCapacity(10)

    

 private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

   如果minCapacity>当前数组的长度才进行扩容

  

private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

           Arrays.copyof调用的是本地方法 System.arraycopy() ;
           Arrays.copyOf()方法返回的数组是新的数组对象,原数组对象仍是原数组对象,不变,该拷贝不会影响原来的数组

           System.arraycopy() 源码如下:

public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);

参数说明:
src:源对象
srcPos:源数组中的起始位置
dest:目标数组对象
destPos:目标数据中的起始位置
length:要拷贝的数组元素的数量

 

        grow分析:先把新容量变为旧容量1.5倍,如果新容量还是小于指定容量,则新容量=指定容量,如果新容量大于最大容量,则看minCapacity与MAX_ARRAY_SIZE的大小,如果minCapacity>MAX_ARRAY_SIZE则新容量为Integer.MAX_VALUE否则新容量为MAX_ARRAY_SIZE

    注意:如果无参构造初始的ArrayList,则在第一次添加元素时就进行扩容,第一次扩容至10,直到容量大于10才进行1.5倍扩容,如果不是无参构造初始的ArrayList,

      总结:add方法: add-> ensureCapacityInternal(是否时无参构造初始的ArrayList)->ensureExplicitCapacity(所需容量是否大于当前容量)->grow(扩容)

  2.remove方法:

   

 public E remove(int index) {
        rangeCheck(index);

        modCount++;
        E oldValue = elementData(index);

        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

        return oldValue;
    }

        remove 中主要是将之后的元素都向前一位移动,然后将最后一位的值设置为空。最后,返回已经删除的值。

 3.indexof方法

.indexOf方法--查找下标
/**
 * 查找下标, 如果为null,直接和null比较,返回下标
 */
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;
}
/**
 * 查找最后出现的下标,从大往下循环查找
 */
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;
}

4.总结:

    与LinkedList区别
           1. ArrayList的实现是基于数组,LinkedList的实现是基于双向链表。 
          2. 对于随机访问,ArrayList优于LinkedList,ArrayList可以根据下标以O(1)时间复杂度对元素进行随机访问。而LinkedList的每一个元素都依靠地址指针和它后一个元素连接在一起,在这种情况下,查找某个元素的时间复杂度是O(n)
          3. 对于插入和删除操作,LinkedList优于ArrayList,因为当元素被添加到LinkedList任意位置的时候,不需要像ArrayList那样重新计算大小或者是更新索引。  
      4. LinkedList比ArrayList更占内存,因为LinkedList的节点除了存储数据,还存储了两个引用,一个指向前一个元素,一个指向后一个元素。

     å¨è¿éæå¥å¾çæè¿°

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章