ArrayList:源码

构造方法

本文基于jdk1.8

三个构造方法

public ArrayList();//无参
public ArrayList(int initialCapacity);//指定初始化容量
public ArrayList(Collection<? extends E> c);//构造包含指定集合的元素的列表

无参构造源码

	//默认一个空容量的数组,长度为零
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; 
    //真正存储数据的容器
    transient Object[] elementData;
	public ArrayList() {
        //赋值
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

有参构造源码(1)

如果指定参数大于0,List容器大小为指定该参数

如果指定参数为0,则创建空容量数组。

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

有参构造源码(2)

    public ArrayList(Collection<? extends E> c) {
        //将c转为数组
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            //这里为确保是Object[].class,进一步处理
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

添加方法

add(E e)

    public boolean add(E e) {
        //保证容器容量足够
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //添加指定元素到数组中
        elementData[size++] = e;
        return true;
    }

ensureCapacityInternal代码

image-20210117182426233

grow(int minCapacity) 源码:


    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        //右移几位就相当于除以2的几次幂
        //左移几位就相当于乘以2的几次幂
        //这里右移一位,并加上原来大小,算出来大小差不多相当于乘以1.5   
        //newCapacity = 1.5*oldCapacity
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //如果算出来的newCapacity比当前所需容量要小,那么直接让newCapacity = minCapacity
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        //如果算出来的newCapacity比数组的最大容量大,那么让newCapacity重新计算出一个大容量的数
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        //将原来的数组copy到新的大小的数组中去,新数组大小为计算出的newCapacity
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

    //这里的方法,可能有人看不太懂,需要结合MAX_ARRAY_SIZE的注释,
   //数组的理论长度是Integer.MAX_VALUE
    //但是部分虚拟机在数组分配长度大于Integer.MAX_VALUE-8时就会发生OOM异常
    //所以java中先限制MAX_ARRAY_SIZE = Integer.MAX_VALUE-8,这样会避免部分虚拟机报错
    //但是如果minCapacity大于Integer.MAX_VALUE-8,会直接扩容到Integer.MAX_VALUE
    //如果Integer.MAX_VALUE还放不下,那兄弟你耗子尾汁!
    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

add(int index, E element)

注意:只有容量不够的时候,才会扩容,并不是所有时候都会扩容

image-20210117190648907

其中:System.arraycopy这一行含义是 将elementData数组从索引index开始往后size-index个元素,复制到elementData,所有元素从index+1依次排开

image-20210117190513021

addAll(Collection<? extends E> c)

image-20210117191317897

addAll(int index, Collection<? extends E> c)

image-20210117192216392

上面四个add方法还是比较容易理解的,需要注意的点还是ArrayList的扩容情况。

并发修改异常(迭代器)

next方法

        ArrayList<String> list = new ArrayList<>();
        list.add("20");
        list.add("30");
        list.add("40");
        for (String o : list) {
            if(o.equals("20")){
                list.remove(o);
            }
        }

image-20210117200219054

增强for循环在java底层用的是迭代器遍历实现的。

而使用迭代器遍历会调用iterator的next方法:

        public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }
		//检查修改
        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }

而上述代码使用的并不是迭代器的remove方法(这个方法会维护迭代器中expectedModCount这个值),使用的是ArrayList的remove方法,而这个方法不会维护expectedModCount和modCount的一致性,所以在checkForComodification方法中抛出了异常。

如果想要避免抛出异常可以改成

        Iterator<String> it = list.iterator();
        while (it.hasNext()){
            String str = it.next();
            if(str.equals("20")){
                it.remove();
            }
        }

或者使用jdk8的新增api:removeIf方法

remove方法

再看一下迭代器中remove方法:

image-20210117202757163

特殊情况

一种特殊情况:

        ArrayList<String> list = new ArrayList<>();
        list.add("30");
        list.add("20");
        list.add("40");
        for (String o : list) {
            System.out.println(o);
            if(o.equals("20")){
                list.remove(o);
            }
        }

上面这种情况,要remove的元素在list的倒数第二个位置

我们看hasNext的源码:

		int cursor; 
		public boolean hasNext() {
            return cursor != size;
        }

当要删除元素在list的倒数第二个位置时,这时候删除后,size=size-1,而cursor这时候正好等于size-1,返回了false,就不会调用执行while中内容,也就不会执行next方法。(此时for循环执行了size-1次就结束了)

面试题

频繁扩容

频繁扩容导致性能急剧下降,怎么处理?

例如向ArrayList中添加一百万条数据,如果直接new一个空的ArrayList,会频繁扩容,那么为何不 直接new ArrayList<>(1000000)呢?

线程安全

线程不安全。以下代码直接抛出ConcurrentModificationException异常

    public static void main(String[] args)  {
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);
        Thread thread1 = new Thread(){
            public void run() {
                for (Integer integer : list) {
                    System.out.println(integer);
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
        };
        Thread thread2 = new Thread(){
            public void run() {
                list.removeIf(integer -> integer == 2);
            };
        };
        thread1.start();
        thread2.start();
    }

上述代码换成Vector也是直接报错的。虽然Vector的方法采用了synchronized进行了同步,但是实际上通过Iterator访问的情况下,每个线程里面返回的是不同的iterator,也即是说expectedModCount是每个线程私有。
假若此时有2个线程,线程1在进行遍历,线程2在进行修改,那么很有可能导致线程2修改后导致Vector中的modCount自增了,线程2的expectedModCount也自增了,但是线程1的expectedModCount没有自增,此时线程1遍历时就会出现expectedModCount不等于modCount的情况了。

上述代码换成CopyOnWriteArrayList就不会报错了。

原文地址:https://www.cnblogs.com/wwjj4811/p/14290165.html