ArrayList 源码详解

首先,我们看一下相关的变量:

private static final int DEFAULT_CAPACITY = 10;//当不设置长度时默认长度为10

//   ArrayList(int initialCapacity) 当initialCapacity = 0时使用
private static final Object[] EMPTY_ELEMENTDATA = {};//


//    用于空参构造
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};


transient Object[] elementData; // non-private to simplify nested class access



private int size;

 

    /**
     * 在add元素的时候并不是加一个扩容一个长度,而是会预先扩容一定的倍数(oldCapacity + 
     *(oldCapacity >> 1) = 1.5倍)。比如加入了100个元素
     *但是现在的长度是150,这个方法的目的就是删除剩下的50个位置
    */
    public void trimToSize() {
        modCount++;
        if (size < elementData.length) {
            elementData = (size == 0)
              ? EMPTY_ELEMENTDATA
              : Arrays.copyOf(elementData, size);
        }
    }


/**
*当我们向list中add元素的时候,如果增加的元素很多,事先调用ensureCapacity预先设置list的大小,可以
*提高效率。minExpand的意思是如果现在list中已经存在元素,那么
*
*/
   public void ensureCapacity(int minCapacity) {
        int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
            // any size if not default element table
            ? 0
            // larger than default for default empty table. It's already
            // supposed to be at default size.
            : DEFAULT_CAPACITY;

        if (minCapacity > minExpand) {
            ensureExplicitCapacity(minCapacity);
        }
    }

/*
*如果需要的最小长度大于现在list中元素的长度,就进行扩容
*
*/
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

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

/*
*首先将list扩容到原来的1.5倍,如果还是小于需要的,那么直接让newCapacity = minCapacity;如果需要的
*长度大于了数组的最大长度(Integer.MAX_VALUE - 8),那么就执行hugeCapacity,那么hugeCapacity的
*作用是什么呢?就是让她等于Integer.MAX_VALUE相当于给了一个底线吧;如果在
*范围内,直接将list扩容成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);
    }

    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }


/*
*list中遍历存照是否o存在,主要注意一下当o==null的情况
*/
public int indexOf(Object o) {
        if (o == null) {
            for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = 0; i < size; i++)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }



/*
*这里的clone属于浅拷贝。
*/
public Object clone() {
        try {
            ArrayList<?> v = (ArrayList<?>) super.clone();
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError(e);
        }
    }

/*
*这里提供了两个将list转成数组的toArray方法。具体区别可以看一下这篇文章:
*https://www.cnblogs.com/goloving/p/7693388.html
*/

public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }

public <T> T[] toArray(T[] a) {
        if (a.length < size)
            // Make a new array of a's runtime type, but my contents:
            return (T[]) Arrays.copyOf(elementData, size, a.getClass());
        System.arraycopy(elementData, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
    }

/**
*向list增加一个元素时,先调用ensureCapacityInternal,
*1:如果elementData长度为0,则扩容至Math.max(DEFAULT_CAPACITY, size+1);
*2:如果elementData长度不为0,但是小于(size+1),则扩容到(size+1)
*3:如果elementData长度不为0,并且大于等于(size+1)时说明不用扩容,可以装下
*这里说的扩容到(size+1)之类的都是说调用grow()的传入参数,实际上应该是(size+1)*1.5等不同的方
*案具体看上文中对grow方法的解析    
*/

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

    
/*
*和上面的方法类似都会调用ensureCapacityInternal,不同指出在于会调用System.arraycopy将index之后的*元素全部朝后挪,并且将新的元素插入到index位置
*/
public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }



/*
*batchRemove方法有两个参数,第一个表示一个集合,第二个表示表示第一个集合中的元素是要删除还是保留。
*有点拗口:c.contains(elementData[r])这句中,遍历list中的所有元素,
*1: complement=true,从list中删除所有非list与C交集的元素。
*2: complement=false,从list中删除list与C的所有交集。
*/
public boolean removeAll(Collection<?> c) {
        Objects.requireNonNull(c);
        return batchRemove(c, false);
    }

private boolean batchRemove(Collection<?> c, boolean complement) {
        final Object[] elementData = this.elementData;
        int r = 0, w = 0;
        boolean modified = false;
        try {
            for (; r < size; r++)
                if (c.contains(elementData[r]) == complement)
                    elementData[w++] = elementData[r];
        } finally {
            // Preserve behavioral compatibility with AbstractCollection,
            // even if c.contains() throws.
//这里主要是担心contains抛出异常。还需要注意的是,这里的contains底层是使用equals来实现的,所以需要
//重写list中类的equals()和hashcode()方法
            if (r != size) {
                System.arraycopy(elementData, r,
                                 elementData, w,
                                 size - r);
                w += size - r;
            }
            if (w != size) {
                // clear to let GC do its work
                for (int i = w; i < size; i++)
                    elementData[i] = null;
                modCount += size - w;
                size = w;
                modified = true;
            }
        }
        return modified;
    }

 

这里简单说明一下DEFAULTCAPACITY_EMPTY_ELEMENTDATA和EMPTY_ELEMENTDATA的区别。按理说两者都代表了一个空的数组,那么为什么还需要两个表示呢?首先我们先关注一下ArrayLIst的构造函数:

1:首先是无参构造函数:this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;

2:传入特定长度的构造函数:

if (initialCapacity > 0) {
    this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
    this.elementData = EMPTY_ELEMENTDATA;
} 

3:传入一个集合的构造函数:

public ArrayList(Collection<? extends E> 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;
    }
}

从代码中可以看出,如果传入的长度大于0, 那么就构造一个对应长度的数组。如果等于0,那么就让她等于 EMPTY_ELEMENTDATA。至此两个变量都用上了。总结一下:无参构造使用DEFAULTCAPACITY_EMPTY_ELEMENTDATA,有参构造使用EMPTY_ELEMENTDATA。  

为什么会这样呢?我们来看一下add方法:

不管是public boolean add(E e)还是public void add(int index, E element),都会调用ensureCapacityInternal方法,而这个方法:

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

    ensureExplicitCapacity(minCapacity);
}

会判断是不是等于DEFAULTCAPACITY_EMPTY_ELEMENTDATA,如果是的,那么就调用ensureExplicitCapacity进行扩容;如果不是,就直接扩容成minCapacity大小。这里注意一下,这代表这两个add元素时的策略:

1:如果使用的是无参构造,且是第一次添加一个元素时,就默认扩容到10。当然如果调用的是addAll()方法时,可能第一次添加的就不止10个,就会扩容到minCapacity;

2:如果使用的是有参构造,添加第一个元素时,就不会使用默认值10进行扩容,而是需要多少个就给多少个。

怎么解释这个原理呢?如果选用无参构造,那就说明我不确定需要多少个,为了尽量少的进行复制扩容,那么我就先给你十个;但是如果选用的;如果有参构造,就说明我知道我需要多少个,所以无需自动帮我扩容到默认的10个。

还有一个问题就是ArrayList中有一个变量modCount,这个变量记录了list被改变的次数:比如调用remove()方法会modCount++;

那么记录修改次数有什么用呢?仔细想一下,如果我们获取到Arraylist的迭代器itrator进行遍历的中途,这个list发生了改变,那么就会出现线程安全问题。例如下面的代码:

ArrayList<Integer> list = new ArrayList<>();
        for (int i = 0; i < 20; i++) {
            list.add(i);
        }
        Iterator it = list.iterator();
        while(it.hasNext()){
            int i = (int) it.next();
            if (i == 10){
                list.remove(i);
            }
            System.out.println(i);
        }


结果:0
1
2
3
4
5
6
7
8
9
10
Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
	at java.util.ArrayList$Itr.next(ArrayList.java:851)
	at ExceptionTest.main(ExceptionTest.java:35)

运行会出现java.util.ConcurrentModificationException问题。为什么会出现这个问题呢?我们看调用list.iterator()返回的是new Itr();而Itr类中有一个变量expectedModCount,这个值等于创建Itr时的modCount。在每次进行遍历的时候都会比较这两个值的大小,如果两个不相等,说明list已经被修改,那么就已经出现了数据不一致的问题。

fast-fail 机制

大概意思是:在系统设计中,快速失效系统一种可以立即报告任何可能表明故障的情况的系统。快速失效系统通常设计用于停止正常操作,而不是试图继续可能存在缺陷的过程。这种设计通常会在操作中的多个点检查系统的状态,因此可以及早检测到任何故障。快速失败模块的职责是检测错误,然后让系统的下一个最高级别处理错误。

​ 其实就是在做系统设计的时候先考虑异常情况,一旦发生异常,直接停止并上报,比如下面的这个简单的例子

//这里的代码是一个对两个整数做除法的方法,在fast_fail_method方法中,我们对被除数做了个简单的检查,如果其值为0,那么就直接抛出一个异常,并明确提示异常原因。这其实就是fail-fast理念的实际应用。
public int fast_fail_method(int arg1,int arg2){
    if(arg2 == 0){
        throw new RuntimeException("can't be zero");
    }
    return arg1/arg2;
}

​ 在Java集合类中很多地方都用到了该机制进行设计,一旦使用不当,触发fail-fast机制设计的代码,就会发生非预期情况。我们通常说的Java中的fail-fast机制,默认指的是Java集合的一种错误检测机制。当多个线程对部分集合进行结构上的改变的操作时,有可能会触发该机制时,之后就会抛出并发修改异常**ConcurrentModificationException**.当然如果不在多线程环境下,如果在foreach遍历的时候使用add/remove方法,也可能会抛出该异常。

​ 之所以会抛出ConcurrentModificationException异常,是因为我们的代码中使用了增强for循环,而在增强for循环中,集合遍历是通过iterator进行的,但是元素的add/remove却是直接使用的集合类自己的方法。这就导致iterator在遍历的时候,会发现有一个元素在自己不知不觉的情况下就被删除/添加了,就会抛出一个异常,用来提示可能发生了并发修改!所以,在使用Java的集合类的时候,如果发生ConcurrentModificationException,优先考虑fail-fast有关的情况,实际上这可能并没有真的发生并发,只是Iterator使用了fail-fast的保护机制,只要他发现有某一次修改是未经过自己进行的,那么就会抛出异常。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值