继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

ArrayList源码解析(基于Java8)

宝慕林4294392
关注TA
已关注
手记 295
粉丝 36
获赞 148

5bd6ce2e0001930007531061.jpg


5bd6ce2f00011c2310000555.jpg


5bd6ce300001135610000058.jpg


首先:执行List<Person> list1 = new ArrayList<>();
在堆内存开辟了一块空间,既然是new出来的,那我们直接从构造函数入手

5bd6ce3000016bf310000217.jpg


5bd6ce31000157d310000387.jpg


Object[]数组,也就是说该数组可以放任何对象(所有对象都继承自父类Object)
继续,执行list1.add(person1)看怎么处理add的

5bd6ce330001f35d10000808.jpg


先看ensureCapacityInternal方法,有个参数size,看一下这个size从哪来的

5bd6ce34000154bf10000189.jpg


size是int基本数据类型,成员变量初始化的为0


继续往下看


5bd6ce3400012d8510000189.jpg

ensureCapacityInternal是在add里面调用的

5bd6ce340001f0ac10001003.jpg


private void ensureCapacityInternal(int minCapacity) {        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {        //如果底层数组就是默认的缓存数组,取两个参数的大的一个值继续往下调用,很明显这个大值为10
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
 } private void ensureExplicitCapacity(int minCapacity) {
        modCount++;//记录修改次数

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

扩容

5bd6ce350001ae2310000528.jpg

    private void grow(int minCapacity) {        // 取到旧数组的长度
        int oldCapacity = elementData.length;        //计算新数组的长度
        //>>是移位运算符,相当于int newCapacity = oldCapacity + (oldCapacity/2),但性能会好一些
        int newCapacity = oldCapacity + (oldCapacity >> 1);        //保证长度在正常范围内
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);        // 用计算出来的数组长度,往下传继续处理
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

这就是数组的扩容,一般是oldCapacity + (oldCapacity >> 1),相当于扩容1.5倍

跟进到Arrays这个工具类,很简单


1000

再看copyOf()方法


1000


System.arraycopy()方法用了一个native来修饰

这是一个数组拷贝方法,大家还在写for循环拷贝数组吗?以后多用这个方法吧,简单又方便还能获得得更好的性能

1000


顺便看一看size()方法的源码


1000

很简单,就是返回size值,而不是底层数组的长度,就是为何String里叫length()而List里叫size()的原因

ArrayList还提供了其它构造方法,我们顺便来看一下

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

当我们在写代码过程中,如果我们大概知道元素的个数,比如一个班级大概有40-50人,我们优先考虑List<Person> list2 = new ArrayList<>(50)以指定个数的方式去构造,这样可以避免底层数组的多次拷贝,进而提高程序性能。

在长度为n数组中:

直接通过下标去访问元素,时间复杂度为O(1)
需要循环查找元素的时候,时间复杂度为O(n)

删除

删除指定位置的元素

public E remove(int index) {
        rangeCheck(index);        //维护的一个变量,记录修改次数
        modCount++;        //根据数组下标拿到底层数组里的元素
        E oldValue = elementData(index);        //计算数组需要拷贝的元素个数
        int numMoved = size - index - 1;        if (numMoved > 0)            //拷贝删除位置(index)+1后面的numMoved个元素并从删除位置(index)开始复制
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);        //相当于:size = size - 1; elementData[size] == null
        //size减1,数组最后一个位置赋值为null
        elementData[--size] = null; // clear to let GC do its work

        //返回事先拿到的删除元素
        return oldValue;
}

这段代码里还调了一个方法rangeCheck()方法,我们看一下

private void rangeCheck(int index) {        if (index >= size)            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

就是检查底层数组下标是否越界

再看另外一种删除方式

删除指定对象元素

public boolean remove(Object o) {        //如果要删除的元素为null
        if (o == null) {            for (int index = 0; index < size; index++)                if (elementData[index] == null) {                    //循环查找null,找到后执行fastRemove()方法
                    fastRemove(index);                    //删除成功,返回true
                    return true;
                }
        } else {            //要删除元素非空
            for (int index = 0; index < size; index++)                if (o.equals(elementData[index])) {                    //equals循环对比,对比成功则执行fastRemove()方法
                    fastRemove(index);                    //删除成功,返回true
                    return true;
                }
        }        //其他情况,返回 false
        return false;
}

再看一下fastRemove()方法

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; // clear to let GC do its work}

和上面用下标删除方式一致,就是少了某些代码,就不细说了

2 CopyOnWriteArrayList

写时复制的容器

当我们往一个容器添加元素的时候,不是直接往当前容器添加,而是

  • 先将当前容器进行copy一份,复制出一个新的容器

  • 然后对新容器里面操作元素,最后将原容器的引用指向新的容器

所以CopyOnWrite容器是一种读写分离的思想

2.1 应用场景

适合高并发的读操作(读多写少)。若写的操作非常多,会频繁复制容器,从而影响性能

CopyOnWriteArrayList 写时复制的集合,在执行写操作(如:addsetremove等)时,都会将原数组拷贝一份,然后在新数组上做修改操作。最后集合的引用指向新数组

CopyOnWriteArrayList 和Vector都是线程安全的,不同的是:前者使用ReentrantLock类,后者使用synchronized关键字。
ReentrantLock提供了更多的锁机制,在锁竞争的情况下能表现更佳的性能。就是它让JVM能更快的调度线程,才有更多的时间去执行线程。这就是为什么CopyOnWriteArrayList的性能在大并发量的情况下优于Vector的原因。

private E get(Object[] a, int index) {    return (E) a[index];
}public boolean add(E e) {    final ReentrantLock lock = this.lock;
    lock.lock();    try {
        Object[] elements = getArray();        int len = elements.length;
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        newElements[len] = e;
        setArray(newElements);        return true;
    } finally {
        lock.unlock();
    }
}private boolean remove(Object o, Object[] snapshot, int index) {    final ReentrantLock lock = this.lock;
    lock.lock();    try {
        Object[] current = getArray();        int len = current.length;
        ......
        Object[] newElements = new Object[len - 1];
        System.arraycopy(current, 0, newElements, 0, index);
        System.arraycopy(current, index + 1, newElements, index, len - index - 1);
        setArray(newElements);        return true;
    } finally {
        lock.unlock();
    }
}




作者:芥末无疆sss
链接:https://www.jianshu.com/p/36cd99e87347
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。


打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP