ArrayList源码解读

本文深入解析ArrayList的内部实现,包括其特点、数组扩容机制、继承实现关系及构造方法。详细阐述了add、get、remove、set等核心方法的源码细节,揭示基于数组的List在随机访问与遍历方面的高效性。

一、前言

   一直都只是会用ArrayList,对于ArrayList的理解都比较简单。正好借此机会也把自己观看源码的一些理解写出来。方便自己以后回顾。使用的版本是JDK1.8。

二、ArrayList特点

  随机访问速度快,插入和移除性能较差(数组的特点),支持null元素,有顺序,元素可以重复,线程不安全。

三、数组扩容所使用到的API

  1、直接将数组容量扩容至一个固定值。生成一个新的数组

      Object[] objs = {1, 2};
      System.out.println(objs.length);
      Object[] objects = Arrays.copyOf(objs, 10);
     System.out.println(objects.length);

  2、将原数组从起始位置开始到复制的长度,替换目标数组从目标起始位置到替换的长度。

//原始数组
int [] arr={1,2,3,4,5,6,7,8};
for (int i : arr) {
    System.out.print(i+",");
}
System.out.println();
//目标数组
int[] arr1=new int[11];
arr1[0]=1;
arr1[1]=2;
arr1[2]=3;
//参数  原数组 ,起始位置 ,目标数组 ,目标的起始位置 , 复制的长度   这是一个本地方法
System.arraycopy(arr,0,arr1,3,8);
//从起始位置复制,替换目标起始位置,需要复制的长度
for (int i : arr1) {
    System.out.print(i+",");
}

  

结论:从arr数组0索引开始,copy到arr1数组从索引为3的开始。长度为8。

 四、ArrayList的继承实现关系

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

  1、ArrayList实现了List接口是一个数组队列拥有了List基本的增删改查功能

  2、ArrayList实现了RandomAccess接口拥有随机读写的功能

  3、ArrayList实现了Cloneable接口可以被克隆

  4、ArrayList实现了Serializable接口并重写了序列化和反序列化方法,使得ArrayList可以拥有更好的序列化的性能

 

 五、ArrayList类属性

//序列版本号
private static final long serialVersionUID = 8683452581122892189L;
//默认数组容量
private static final int DEFAULT_CAPACITY = 10;
//当指定数组的容量为0时使用这个常量赋值
private static final Object[] EMPTY_ELEMENTDATA = {};
//默认空参构造函数时使用这个常量赋值
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//真正存放数据的对象数组,transient标识不被序列化
transient Object[] elementData;
//数组中的真实元素个数,该值小于或等于elementData.length
private int size;
// 最大数组容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//记录修改次数    继承于AbstractList
protected transient int modCount = 0;

六、ArrayList构造方法

//初始化设置数组集合长度   
 public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];//初始长度大于0,就直接初始化数组容量
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;//默认复制一个空数组
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);//报错
        }
    }

 

    public ArrayList() {
//默认空数组 JDK1.7之后初始化的时候不指定大小,那么集合初始大小为0 this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }

  

    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();//转化为数组
        if ((size = elementData.length) != 0) {//非空集合
            if (elementData.getClass() != Object[].class)//是否转化为Object类型数组
                elementData = Arrays.copyOf(elementData, size, Object[].class);//不为Object类型数组就进行复制
        } else {
            this.elementData = EMPTY_ELEMENTDATA;//空集合,初始为空数组
        }
    }

六、add方法

    public boolean add(E e) {
        ensureCapacityInternal(size + 1); //真实数组元素加1  调用ensureCapacityInternal方法
        elementData[size++] = e;   //进行处理之后,将值复制到索引为size的位置上     
        return true;
    }

    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

  //判断 private static int calculateCapacity(Object[] elementData, int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { //空数组,返回两个值最大的一个 return Math.max(DEFAULT_CAPACITY, minCapacity); } //不是空数组直接返回minCapacity return minCapacity; } private void ensureExplicitCapacity(int minCapacity) { modCount++; //修改统计数+1 // 判断数组真实元素个数加1后的长度与当前数组长度大小关系,如果小于0,返回,如果大于0就调用grow()方法 if (minCapacity - elementData.length > 0) grow(minCapacity); } private void grow(int minCapacity) { //原始数组的长度 int oldCapacity = elementData.length; // 新增数组容量大小 (oldCapacity >> 1)=oldCapacity/2 int newCapacity = oldCapacity + (oldCapacity >> 1); //这个地方有一个面试题,如果初始数组的长度1,增加的元素超过1个,那么上面计算的新数组大小也是为1的。这个判断就是如果新数组小于minCapacity,最少保证newCapacity=minCapacity if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) //最多不能超过最大容量 newCapacity = hugeCapacity(minCapacity); //复制数组从新赋值 elementData = Arrays.copyOf(elementData, newCapacity); }

  

    public void add(int index, E element) {
        rangeCheckForAdd(index);//索引校验
      //这里和上面add()处理的差不多   扩容数组
        ensureCapacityInternal(size + 1);  
      //这个地方和上面最开始说的那种copy例子差不多,从elementData数组的index索引位置开始,copy到elementData数组索引位置为index+1。长度为size-index
       //说白了就是将存在index索引位置的值一直到size-index中间所有的值。替换index-1索引位置一直到size-index的值 System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element;//根据索引复制 size++; } private void rangeCheckForAdd(int index) { //超过数组实际长度报错 if (index > size || index < 0) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); }

结论:从源码上看,两个方法大致相同,最主要的差别是System.arraycopy(elementData, index, elementData, index + 1, size - index),从index位置开始以及之后的数据,整体拷贝到index+1开始的位置,然后再把新加入的数据放在index这个位置,而之前的数据不需要移动。(这些动作比较消耗性能)

七、get方法

    public E get(int index) {
        rangeCheck(index);//校验索引是否越界
//根据索引取值
        return elementData(index);
    }

八、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);
        //因为数组已经替换了,把size-1的索引位置的元素赋值为null,方便GC回收
        elementData[--size] = null;

        return oldValue;
    }    

 

public boolean remove(Object o) {
        if (o == null) {
      // 为null的时候遍历删除第一个位null的值
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
        //不为null的时候通过equals判断删除
            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;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null; 
}

  

结论:删除其实还是使用的是java.lang.System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length),也是比较消耗性能的

 九、set方法

    public E set(int index, E element) {
        rangeCheck(index);

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

结论:修改没什么好说的,索引校验,然后获取之前索引的值,替换、返回。

 

 

总结:基于数组实现的List在随机访问和遍历的效率比较高,但是往指定位置加入元素或者删除指定位置的元素效率比较低

   后续如果观看了其他ArrayList的方法,也会继续补充的。

 

转载于:https://www.cnblogs.com/yangk1996/p/10703659.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值