构造方法
本文基于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代码
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)
注意:只有容量不够的时候,才会扩容,并不是所有时候都会扩容
其中:System.arraycopy这一行含义是 将elementData数组从索引index开始往后size-index个元素,复制到elementData,所有元素从index+1依次排开
addAll(Collection<? extends E> c)
addAll(int index, Collection<? extends E> c)
上面四个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);
}
}
增强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方法:
特殊情况
一种特殊情况:
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就不会报错了。