java中集合是重要的部分之一,在实际工作过程也被高频率使用,这里大概记录一下1.8中的ArrayList,主要分析一下几个常用的方法,扩容机制,以及多线程下引起的线程安全问题,纯属个人拙见,不足之处还请评论支出,共同学习进步!
1.对于集合整体架构如下图所示:
通过源码我们可以看到ArrayLis的底层数据结构是数组,这也赋予了它检索效率高的优势,但是由于数组的局限性,达到一定容量时需要扩容,为了减少扩容带来性能降低,我们在使用ArrayList初始化时可以根据使用场景预估其长度而定一个初始容量值,这样可以减少频繁扩容而带来不必要的开销。
2.常用方法
构造方法:
通过注释我们可以看出来三个构造方法适用于不同的场景,
/**
* Constructs an empty list with the specified initial capacity.
* 初始容量值的构造方法
*/
public ArrayList(int initialCapacity);
/**
* Constructs an empty list with an initial capacity of ten.
* 无参构造方法,初始容量为10
*/
public ArrayList();
/**
* Constructs a list containing the elements of the specified
* collection, in the order they are returned by the collection's
* iterator.
* 构建一个包含具体集合元素的构造方法,它们的顺序依次通过集合的迭代器返回
*/
public ArrayList(Collection c);
添加元素方法:
/**
* Appends the specified element to the end of this list.
* 在list的末尾增加元素e
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
我们先来说说ArrayList的两个重要参数,如注释
/** * Shared empty array instance used for empty instances. * 用于空实例的空数组实例 */ private static final Object[] EMPTY_ELEMENTDATA = {}; /** * Shared empty array instance used for default sized empty instances. We * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when * first element is added. * 用于默认大小空实例的空数组实例,我们将此与EMPTY_ELEMENTDATA区别开来,以了解添加第一个元素时需要扩容多少。 */ private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
我们继续跟进add方法
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
// 操作数记录变量,Java快速报错机制后续分析
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
calculateCapacity这个方法就利用了如上的两个参数,如果是使用默认无参构造方法也就是DEFAULTCAPACITY_EMPTY_ELEMENTDATA数组对象时,增加第一个元素E e返回的是默认的容量长度DEFAULT_CAPACITY,在进入ensureExplicitCapacity方法后,经过if判断后进行扩容,执行grow方法,我们继续看grow方法
/**
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
* 增加容量以确保它至少可以容纳最小容量参数指定的元素数量。简而言之就是扩容
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
// 右移1位操作,等效于oldCapacity/2,但是位移是底层操作更高效
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;
}
对于扩容有几种情况:
至此ArrayList扩容机制分析完毕,扩容完成后,添加新元素E e。
在ensureExplicitCapacity方法中有个一个增量modCount,这里是利用了java的快速报错机制,也算是一种保护机制,能够防止多个进程同时修改同一个容器的内容。如果迭代遍历比如使用迭代器Iterator(ListIterator)或者forEach,会将modCount变量值传给exceptedModCount,并且会检查modCount与exceptedModCount是否相等,如果此时对同一个容器执行新增删除或者修改操作,modCount会改变,二者不相等则会抛出ConcurrentModificationException异常。但是如果使用Iterator的remove方法则不会发生此异常。举例如下:
剩下的三个添加元素的方法在此不再赘述。
此篇主要内容到此结束,有不足之处还请大家指正,谢谢!