ArrayList
本系列的Java源码版本是官网最新版java 11
ArrayList
有如下几个特性:
- 动态数组
- 顺序访问
- size、isEmpty、get、set、iterator、listIterator方法时间复杂度是 O ( 1 ) O(1) O(1) ,也就是常量时间
- 其他方法时间复杂度是 O ( n ) O(n) O(n)
- 可以存放
null
- 非线程安全
- iterator的fail-fast机制
让我们从源码的角度来分析下是如何动态扩容的。
private Object[] grow() {
return grow(size + 1);
}
private Object[] grow(int minCapacity) {
return elementData = Arrays.copyOf(elementData,
newCapacity(minCapacity));
}
private int newCapacity(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity <= 0) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
return Math.max(DEFAULT_CAPACITY, minCapacity);
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return minCapacity;
}
return (newCapacity - MAX_ARRAY_SIZE <= 0)
? newCapacity
: hugeCapacity(minCapacity);
}
扩容算法是old Capacity右移1位也就是原来得容量除2,比如原来的容量是10,那么扩容后的容量是15。扩容是需要进行数组的内存分配和数据拷贝上的性能损耗的,所以建议如果知道容量大小前提下给list设置初始化容量防止系统自动扩容。
/**
* This helper method split out from add(E) to keep method
* bytecode size under 35 (the -XX:MaxInlineSize default value),
* which helps when add(E) is called in a C1-compiled loop.
*/
private void add(E e, Object[] elementData, int s) {
if (s == elementData.length)
elementData = grow();
elementData[s] = e;
size = s + 1;
}
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#add})
*/
public boolean add(E e) {
modCount++;
add(e, elementData, size);
return true;
}
add方法是容器的最核心的方法之一,调用频率非常高。该方法的性能直接影响整个程序的性能,java在对其作了一个性能优化 方法内联
java执行方法流程如下:
- 在栈上分配空间存储方法参数、局部变量和返回地址等等
- 跳到目标方法执行
- 存储返回值,销毁方法和变量的空间
- 返回到上次执行的地址
总之,java在执行方法调用是需要一定的时间和空间的消耗的,故java将add方法体变得特别小有助于JIT编译器能进行方法内联从而减少因方法调用造成得损耗
This helper method split out from add(E) to keep method bytecode size under 35 (the -XX:MaxInlineSize default value),which helps when add(E) is called in a C1-compiled loop
中文意思:在C1编译器中默认最大方法内联字节数为35 byte,为了保持add方法体在35字节数以下,故将多余的逻辑进行抽离出来
fast-fail机制
protected transient int modCount = 0;
ArrayList
通过modCount来实现fast-fail机制,当多线程同时操作list并且至少一个线程对list做了结构化操作时,如果出现modCount不一致会抛出一个 ConcurrentModificationExceptions
异常。
这个机制是通过 transient
关键字实现的,该关键字会将最新的值立刻刷新到所有线程的本地缓存中从而使得所有线程读取得值都是当前最新得值
其他方法
add
public void add(int index, E element) {
rangeCheckForAdd(index);
modCount++;
final int s;
Object[] elementData;
if ((s = size) == (elementData = this.elementData).length)
elementData = grow();
System.arraycopy(elementData, index,
elementData, index + 1,
s - index);
elementData[index] = element;
size = s + 1;
}
如图示,按指定下标添加元素除了动态扩容开销外还需要对数据腾挪上的时间和空间的开销,建议使用 LinkedList
替代
clear
public void clear() {
modCount++;
final Object[] es = elementData;
for (int to = size, i = size = 0; i < to; i++)
es[i] = null;
}
该方法实现了2个功能,首先将size清零,然后把数组的所有元素置为null。我们在日常开发中可能会疏忽第二步,如果一个数组不再使用应该将数组的所有元素都置为null,目的就是防止内存泄露
总结
本次主要介绍了ArrayList的动态扩容机制、fast-fail机制以及java方法内联优化。java方法内联优化是日常开发中常见的性能优化方式之一,实现原理上也比较简单,可以通过vm参数PrintInlining校验效果。
适用在while和for这样的循环体内的方法,主要是频繁调用相关方法
- 尽量保持内联的方法体足够小
- 尽量使用private、final、static进行修饰