乐趣区

关于java:从面试角度分析ArrayList源码

注:本系列文章中用到的 jdk 版本均为java8

ArrayList类图如下:

ArrayList的底层是由数组实现的,数组的特点是 固定 大小,而 ArrayList 实现了 动静扩容

ArrayList局部变量如下,在上面的剖析中会用到这些变量。

/**
 * 默认容量
 */
private static final int DEFAULT_CAPACITY = 10;
/**
 * 空的对象数组
 */
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
 * 无参结构器创立的空数组
 */
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
 * 存放数据的数组的缓存变量
 */
transient Object[] elementData;
/**
 * 元素数量
 */
private int size;

一、初始化 ArrayList

初始化 ArrayList 个别会应用以下两个结构器

1.1 无参结构器

初始化 ArrayList 的时候如果不指定大小,则会创立一个空数组。

public ArrayList() {this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;}

1.2 指定数组大小的结构器

创立一个 预估 大小的数组,指定大小后只是指定了数组初始值的大小,不影响前面扩容,指定的益处就是能够节俭内存及工夫上的开销。

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

二、增加元素、动静扩容

ArrayList.add(E e)源码:

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

add()elementData[size++] = e 很好了解,就是将元素插入第 size 个地位,而后将 size++,咱们重点来看看ensureCapacityInternal(size + 1) 办法;

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

ensureCapacityInternal()办法中判断缓存变量 elementData 是否为空,也就是判断是否是第一次增加元素,如果是第一次增加元素,则设置初始化大小为默认容量 10,否则为传入的参数。这个办法的目标就是 获取初始化数组容量 。获取到初始化容量后调用ensureExplicitCapacity(minCapacity) 办法;

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

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

ensureExplicitCapacity(minCapacity)办法用来判断是否须要扩容,如果第一次增加元素,minCapacity10elementData 容量为 0,那么就须要去扩容。调用grow(minCapacity) 办法。

// 数组的最大容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    // 扩容大小为原来数组长度的 1.5 倍
    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);
}

grow(minCapacity)办法对数组进行扩容,扩容大小为原数组的 1.5 倍,如果计算出的扩容容量比须要的容量小,则扩容大小为须要的容量,如果扩容容量比数组最大容量大,则调用 hugeCapacity(minCapacity) 办法,将数组扩容为整数的最大长度,而后将 elemetData 数组指向新扩容的内存空间并将元素复制到新空间。

当须要的汇合容量特地大时,扩容 1.5 倍就会十分耗费空间,因而倡议初始化时预估一个容量大小。

三、删除元素

ArrayList提供两种删除元素的办法,能够通过 索引 元素 进行删除。两种删除大同小异,删除元素后,将前面的元素一次向前挪动。

ArrayList.remove(int index)源码:

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

删除元素时,首先会判断索引是否大于 ArrayList 的大小,如果索引范畴正确,则将索引地位的下一个元素赋值到索引地位,将 ArrayList 的大小 -1,最初返回移除的元素。操作图如下,如果我要移除索引为1 的元素:

四、总结

ArrayList底层是数组实现的,能够进行动静扩容,扩容大小为原来的 1.5 倍,尽管能够通过动静扩容,然而数组十分大时会特地节约空间,因而倡议初始化时预估数组大小。ArrayList容许插入反复值和空值。ArrayList实现了 RandomAccess 接口,反对疾速随机拜访,就是能够通过索引疾速查到某个元素,因而遍历时应用 for 循环的形式效率更高。ArrayList是线程不平安的,能够通过 Collections.synchronizedList 将其转变为线程平安的汇合,不过个别不会应用,VectorCopyOnWriteArrayList 是线程平安的,Vector性能个别,逐步被 CopyOnWriteArrayList 取代了。

点关注、不迷路

如果感觉文章不错,欢送 关注 点赞 珍藏,你们的反对是我创作的能源,感激大家。

如果文章写的有问题,请不要吝惜文笔,欢送留言指出,我会及时核查批改。

如果你还想看到更多别的货色,能够微信搜寻「Java 旅途」进行关注。「Java 旅途」目前曾经整顿各种中间件的应用教程及各类 Java 相干的面试题。

退出移动版