ArrayList 源码解析

一、ArrayList的概述
ArrayList 是我们开发中常用的一种数据结构,它的底层是基于数组实现的,是一个动态数组,容量你可以动态 增加,ArrayList实现Serializable 接口,他能支持序列化传输;实现了RandomAccess接口,支持快速随机访问,也就是通过下标可以实现快速访问;实现了Cloneable 接口,意味着可以被克隆。

二、ArrayList 的源码解析
首先看下 构造函数

	/**
     * Constructs an empty list with an initial capacity  of ten.
     */
    public ArrayList() {
        this.elementData =  DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    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);
        }
    }

    public ArrayList(Collection c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return  Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData,  size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

①、第一个无参构造器 是将一个空数组赋值给 elementData;
②、而第二个有参数构造器是先判断initialCapacity与0之间的关系,如果大于0,则创建一个大小为initialCapacity的对象数组赋给elementData;如果等于0,则将EMPTY_ELEMENTDATA赋给 elementData, 如果小于0;则抛出异常;
③、第三个构造器将参数集合转换成数组判断集合是否为空,为空将 EMPTY_ELEMENTDATA 赋值给elementData,否则将转换后的数组拷贝到elementData中,由此可知elementData就是ArrayList底层维护的数据

通过上面发现一个神奇的问题:在无参数构造函数中将 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 这个数组赋值给elementData但是在有参构造函数中initialCapacity大小为0时 将 EMPTY_ELEMENTDATA 赋值给elementData, 带着疑问继续看源码发现:

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

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

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(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);
    }

DEFAULTCAPACITY_EMPTY_ELEMENTDATA 是在calculateCapacity 这个方法里面使用的,这个方法是判断默认容量大小的。
如果是无参的构造,他的默认容量会被设置为10;
而如果是有参的话就会取minCapacity的值,这也体现出jdk源码设计的微妙,如果我们在有参构造中将elementData赋值为DEFAULTCAPACITY_EMPTY_ELEMENTDATA 那么就会导致小于10的容量变成10,假如我们只存储3个元素,那么无形中就有7个空间被白白的浪费掉了。

接着往下走我们会发现,添加的时候会先去判断容量是否够用,如果够用就不在去扩容,如果不够用,就进行扩容,扩大到上次的1.5 倍,因为数组的大小一旦声明是无法修改的,源码中是使用了copyOf 函数, copyOf 底层的原理是在内存中重新开辟一块空间将已有的数据拷贝过去,这样就在不影响原有数据的基础上进行了扩容,可以编写一个demo验证一下:

public static void main(String[] args) {
          
//        List list = new ArrayList<>();
//        System.out.println(list);
          
          Object[] object = {1,2,3};
          System.out.println(object);
          object = Arrays.copyOf(object, 10);
          System.out.println(object);
     }

运行后发现使用 copyof 函数后并没有指向当前数组,而是指向了其他,这也就证明了他不会在当前数组进行扩容,而是在其他的地方重新创建了一块空间。
假设数据量很大的时候,会导致效率大大的降低,所以以后在使用ArrayList 的时候即使不知道具体大小,也可以指定一个大概的容量,这也就可以避免重复的扩容,降低效率.

通过刚才的分析,还发现了一个问题是add 方法居然没有加锁,这样如果在多线程的情况下,ArrayList 的add方法是线程不安全的,容易产生数组越界问题.
1.当线程A调用ensureCapacityInternal 方法时 这时size=9 判断容量后发现不用扩容
2.当线程B 进入时 他得到size 也是9,而elementData的大小是10,判断容量后发现不用扩容
3.当线程A去执行elementData[size++] = e 操作时,size++, size 的大小变成了10,
4.当线程B再去执行elementData[size++] = e 操作时,就会产生数组越界的问题,
解决方法:为了解决线程安全问题可以使用 Collections.synchronizedList(),但是会降低效率 如:
List list = Collections.synchroizedList(new ArrayList<>());

删除 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;
    }

删除的方法比较简单,每次进行数组的复制,然后将旧的 elementData=null 让垃圾回收机制去回收。
Arrylist 的 get ,set,clear 也没有其他复杂的操作, 都是一些对数组的简单操作。

ArrayList的优点
1.ArrayList底层以数组实现,是一种随机访问模式,再加上它实现了RandomAccess接口,因此查找也就是get的时候非常快。
2.可以做到自动扩容,无需开发者关心数组大小

ArrayList的缺点
1.因为每次插入和删除都需要对数组进行拷贝操作,所以插入和删除的效率并不高
2.是线程不安全

你可能感兴趣的:(Java)