ArrayList源码详解

ArrayList源码解析

1.ArrayList介绍

  • ArrayList的继承关系

ArrayList是List接口的主要实现类,线程不安全,效率高,底层使用Object[] elementData存储

2.ArrayList分析

2.1 属性

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    /**
    * RandomAccess:标记接口,用于标明实现该接口的List支持快速随机访问,主要目的是使算法能够在随机和顺序访问的list中表现的更	 加高效
    * Cloneable:一个标记接口,只有实现这个接口后,类中重写Object中的clone方法,然后通过类调用clone方法才能克隆成功,如果不实现这个接口,则会抛出CloneNotSupportedException(克隆不被支持)异常
    * Serializable是启用其序列化功能的接口。支持对象序列化,进行对象传输
    */
    
	//初始容量
    private static final int DEFAULT_CAPACITY = 10;

 	//空集合,注意不是null,是表示没有数据
    private static final Object[] EMPTY_ELEMENTDATA = {};

 	//默认数组,当使用无参构造函数初始化时会赋值给elementData
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    //存储数据的数组
    transient Object[] elementData; 
        
 	//elementData中元素个数
    private int size;
        
    //数组最大容量
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
 }

2.2 构造函数:

ArrayList有三个构造函数: 无参 、参数-int、参数-collection

ArrayList a1 = new ArrayList<>();

ArrayList a2 = new ArrayList<>(12);

ArrayList a2 = new ArrayList<>(Arrays.asList(12,13,45));

  • 无参构造函数
    public ArrayList() {
        //ArrayList无参构造函数将elementData赋值为{}
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
  • 有参构造函数-参数为int
//创建一个指定大小的ArrayList    
public ArrayList(int initialCapacity) {
        //当传入的参数大于0,则创建指定容量大小的数组
        if (initialCapacity > 0) {        
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
        //当传入的参数等于0,则将elementData赋值为{}
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
        //当传入的参数小于0,则跑出非法参数异常    
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

  • 有参构造函数-参数为Collection
//创建一个与指定集合元素一样的Arraylist   
public ArrayList(Collection<? extends E> c) {
    	//当传入参数是一个集合时,首先调用toArray()将集合类中的数组赋值给elementData
        elementData = c.toArray();
    	/**更新size为elementData.length,并判断如果size!=0,继续判断
    	      -如果toArray()返回的数组类型不是Object[]类型时,会调用Arrays.copyOf()将原数组拷贝到新数组中去,而且类型还定义为Object类;
    		  -如果size=0,即传入的为空集合,则将elementData赋值为空
    	**/	  
        if ((size = elementData.length) != 0) {
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
        
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

    public Object[] toArray() {//数组拷贝
        return Arrays.copyOf(elementData, size);
        //Arrays.copyOf(Object[] o,int length);
        //实现数组的复制,并返回复制后的数组。参数是被复制的数组和复制的长度
    }

2.3 常用方法:

1) add(E e)
//添加元素    
public boolean add(E e) {
         //当前所需实际容量为size+1,通过ensureCapacityInternal确定elementData数组的容量是否充足,不够则扩容
        ensureCapacityInternal(size + 1); 
        //再将数据放入数组的size++位置上
        elementData[size++] = e;
        return true;
    }

  • ensureCapacityInternal():主要找到实际需要的最小容量,并传入ensureExplicitCapacity()中判断扩容与否
    private void ensureCapacityInternal(int minCapacity) {
        //首先验证属性elementData是否等于默认的初始化数组
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            //如果等于则表明集合刚被初始化,那么形参minCapacity赋值为10(DEFAULT_CAPACITY)
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        //拿到实际需要的最小容量后,通过ensureExplicitCapacity()验证minCapacity是否大于elementData的length,大于才扩容;
        ensureExplicitCapacity(minCapacity);
    }   
    
  • ensureExplicitCapacity():拿实际所需最小容量minCapacity验证elementData长度是否够,不够则用grow(minCapacity)扩容
   private void ensureExplicitCapacity(int minCapacity) {
       //modCount变量是从AbstractList继承下来的,用于记录对ArrayList对象操作的次数 
       modCount++;

        //如果形参minCapacity的数值比当前的elementData数大,则调用grow()扩容
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    
  • grow(int minCapacity):elementData数组扩容,取传递进来的minCapacity与newCapacity的最大值作为真正的下一个elementData的长度
   private void grow(int minCapacity) { 
        //elementData原始长度
        int oldCapacity = elementData.length;
       //先在原数组基础上计算扩容后的数组长度:newCapacity=当前数组长度*1.5
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //如果扩容后的数组长度newCapacity<实际所需最小容量minCapacity,则将扩容长度赋值为实际所需的minCapacity
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        //如果扩容的数组长度>数组最大容量,则用hugeCapacity()进行容量的判断,将其更新为int的最大值
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
            
        //利用Arrays.copyOf()把elementData复制到新数组中并赋值给elementData;
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    
    
  private static int hugeCapacity(int minCapacity) {
        // 如果minCapacity<0,则报栈溢出错误
        if (minCapacity < 0) 
            throw new OutOfMemoryError();
        //如果minCapacity大于最大数组容量,则返回数组扩容容量为Integer.MAX_VALUE,否则为 MAX_ARRAY_SIZE
        return (minCapacity > MAX_ARRAY_SIZE) ?
                Integer.MAX_VALUE :
                MAX_ARRAY_SIZE;
    }

扩容总结:调用Arraylist无参构造时,底层Object[] elementData初始化为{},当第一次add时,先确定数组容量是否充足,通过ensureCapacityInternal(size+1)将当前需要的最小容量计算出来【如果elementData等于默认的初始化数组,则表明集合刚被初始化,则minCapacity为10】,再通过ensureExplicitCapacity()形参minCapacity与elementData长度相比较是否够,不够则用grow(minCapacity)扩容,扩容规则为数组当前的容量+(数组当前的容量/2),取传递进来的minCapacity与newCapacity的最大值作为真正的下一个elementData的长度,如果扩容的长度大于elementData最大容量,则用hugeCapicity()进行处理,如果minCapacity大于最大数组容量,则返回数组扩容容量为Integer.MAX_VALUE,否则为 MAX_ARRAY_SIZE。

2) remove (int index)
    //移除指定位置的元素
	public E remove(int index) {
        //检查下标是否非法或越界
        rangeCheck(index);

        modCount++;
        //获取指定位置的元素
        E oldValue = elementData(index);
		//numMoved需要移除的元素个数
        int numMoved = size - index - 1;
        if (numMoved > 0)
            //如果需要移除的元素的个数>0,则将元素往前移动
            System.arraycopy(elementData, index+1, elementData, index,
                    numMoved);
        //将size位置元素置空并将size--
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }

    /**
     * public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
	 * 参数:
    * Object src : 原数组
     * int srcPos : 从元数据的起始位置开始
   * Object dest : 目标数组
    * int destPos : 目标数组的开始起始位置
   * int length  : 要copy的数组的长度
     *
     */

//检查形参索引是否不合法
private void rangeCheckForAdd(int index) {
    //如果形参index < 0 或大于数组长度,则报下标越界异常
    if (index > size || index < 0)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

3) remove(Object o)
public boolean remove(Object o) {//移除指定的元素
    //如果要移除的元素为空,则遍历整个数组找到为空的元素的下标,用fastRemove方法移除
    if (o == null) {
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index);
                return true;
            }
    } else {//如果不为空调用对象的equals()挨个与elemetData中的元素比较,如果相等则移除
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    return false;
}

//移除指定位置的元素
    private void fastRemove(int index) {
        modCount++;
        
        //需要移动的元素个数
        int numMoved = size - index - 1;
        //如果移动的元素个数>0,则将elementData数组中index+1之后的数字都往前移动一个单位
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                    numMoved);
        //将size位置元素置空并将size--
        elementData[--size] = null; // clear to let GC do its work
    }



    
4) set(int index, E element)
    public E set(int index, E element) {//修改指定位置元素
        //检查下标是否非法或越界
        rangeCheck(index);
        //获取指定位置的值并更新该位置的值,返回旧值
        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }
5) get(int index)
    public E get(int index) {//获取指定位置的元素
        //检查下标是否非法或越界
        rangeCheck(index);
        return elementData(index);
    }
6)add(int index, E element)
public void add(int index, E element) {//在指定位置插入元素
    //检查插入的索引是否越界或非法
    rangeCheckForAdd(index);

    //确定elementdata容量是否充足,不充足则扩容
    ensureCapacityInternal(size + 1);  

    //将elementData从index位置之后的元素往后移动一个位置
    System.arraycopy(elementData, index, elementData, index + 1,
            size - index);

    elementData[index] = element;
    size++;
}
7)size()
    public int size() {//获取集合元素个数
        return size;
    }
8)ArrayList的遍历
  • 方式一:iterator迭代器

    Collection接口继承了java.lang.iterator,该接口有一个iterator()方法。所以实现了Collection接口的集合类都有一个iterator

    ()方法,用以返回实现了iterator接口的对象,默认游标在第一个元素之前

    iterator仅用于遍历集合,iterator本身不提供承装对象的能力。如果需要创建iterator对象,则必须有一个被迭代的集合

    内部方法:

    • hashNext();判断是否有下一个元素
    • next()将指针下移,并返回当前元素
    • remove(),删除迭代器指定的元素

    方式二:增强for

    方式三:for(;;😉

  @Test
  public void test1(){     
	   Collection coll = new ArrayList();
        coll.add(123);
        coll.add(456);
        coll.add(new Person("Jerry",20));
        coll.add(new String("Tom"));
        coll.add(false);

      方式1:
        Iterator iterator = coll.iterator();        
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }
      方式2for(Object obj : coll){
            System.out.println(obj);
        }
    }

2.4 其他方法

boolean addAll(int index,Collection coll):从指定位置index开始将coll中元素添加进来
int indexOf(Object obj);返回obj在集合中首次出现的位置
int lastIndexOf(Object obj);返回obj在集合中最后一次出现的位置
List subList(int fromIndex,int toIndex);返回从fromIndex开始到toIndex的子集合
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值