ArrayList源码解读

一、前言

   一直都只是会用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
  • ArrayList实现了List接口是一个数组队列拥有了List基本的增删改查功能
  • ArrayList实现了RandomAccess接口拥有随机读写的功能
  • ArrayList实现了Cloneable接口可以被克隆
  • 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在随机访问和遍历的效率比较高,但是往指定位置加入元素或者删除指定位置的元素效率比较低。jdk1.7之前默认初始长度为10,jdk1.8之后初始长度为0,第一次add的时候如果没有指定长度默认为10。如果数组的实际容量 + 1 大于 数组的存储容量的时候,就开始扩容,每次扩1.5 倍。

posted @ 2020-04-07 14:02  无话可说丶  阅读(179)  评论(0编辑  收藏  举报