目录
2.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;
/**
* 带初始容量的构造函数,
*/
public ArrayList(int initialCapacity) {
//初始化大小不为0,例:ArrayList(10)---initialCapacity=10
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
//初始大小为0
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
/**
* 默认构造函数,使用初始容量构造一个空的列表
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* 构造包含指定的Collection元素的列表,这些元素利用该集合的迭代器按顺序返回
* 如果指定的集合为null,则会抛出NullPointerException
*/
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;
}
}
以无参构造器方法创建的ArrayList时,实际上上初始化赋值的是一个空数组;
二、ArrayList中的添加方法add()
add()方法:
/**
* 将制定元素添加到此列表的尾部
*/
public boolean add(E e) {
//添加元素之前,先调用ensureCapacityInternal方法
ensureCapacityInternal(size + 1); // Increments modCount!!
//这里看到ArrayList添加元素的实质相当于为数组赋值
elementData[size++] = e;
return true;
}
ensureCapacityInternal():
//add方法首先调用了ensureCapacityInternal(size+1)
private void ensureCapacityInternal(int minCapacity) {
//得到最小的扩容容量
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
当要add第一个元素的时候,minCapacity=1,在Math.max()方法比较后,minCapacity=10;
ensureExplicitCapacity():
//如果调用ensureCapacityInternal一定会执行ensureExplicitCapacity这个方法
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
//判断是否需要扩容
// overflow-conscious code
if (minCapacity - elementData.length > 0)
//调用grow方法进行扩容
grow(minCapacity);
}
(1)当add第一个元素到ArrayList,elementData.length为0(是一个空list),因为执行了ensureCapacityInternal()方法,所以minCapacity =10。此时,minCapacity - elementData.length>0成立,所以进入grow(minCapacity)方法;
(2)当add第二个元素到ArrayList,minCapacity=2,此时elementData.length(容量)在添加完第一个元素后扩容成10了。此时,minCapacity - elementData.length > 0不成立,所以不会进入grow(minCapacity)方法。
(3)添加第3、4…第10个元素时,依然不会进入grow(minCapacity)方法,数组数量都为10;直到添加第11个元素,minCapacity(为11)比elementData.length(为10)要大,进入grow方法进行扩容。
grow方法
/**
* 要分配的最大数组大小
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/**
* ArrayList扩容的核心方法
*/
private void grow(int minCapacity) {
//旧的容量大小,newCapacity为新容量
int oldCapacity = elementData.length;
//将旧的容量右移一位,相当于oldCapacity/2
//位运算的速度远远快于整除运算,整句运算式的结果就是将新的容量更新为旧的容量的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//然后检查新的容量是否大于最小需要的容量,若还是小于最小需要容量,那么就把最小需要的容量当做数组的新容量
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//如果新容量大于MAX_ARRAY_SIZE ,执行hugeCapacity()方法来比较minCapacity和MAX_ARRAY_SIZE
//如果minCapacity大于最大容量,则新容量则为Integer.MAX_VALUE,否则,新容量大小为MAX_ARRAY_SIZE即为Integer.MAX_VALUE - 8
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);
}
- 当add第一个元素的时候,oldCapacity=0,经比较后第一个if成立,newCapacity = minCapacity(为10)。但是第二个if不成立,不会调用hugeCapacity()方法。数组容量为10,add方法返回true,size+1。
- 当add第11个元素会调用grow方法,newCapacity =15,比minCapacity =11要大,第一个if不成立。新容量没有大于数组最大size,不会调用hugeCapacity方法。数组容量扩容15,add方法返回true,size=11。
Arrays.copyOf() :
//以正确的顺序返回一个包含列表中所有元素的数组(从第一个到最后一个元素);返回的数组的运行时类型是指定数组的运行时类型。
public Object[] toArray() {
//elementData:要复制的数组;size:要复制的长度
return Arrays.copyOf(elementData, size);
}
三、常见面试题
1.那我们本身就有数组了,为什么要用ArrayList呢?
原生的数组会有一个特点:你在使用的时候必须要为它创建大小,而ArrayList不用。
2.ArrayList是怎么实现的吧,为什么ArrayList就不用创建大小呢?
其实是这样的,我看过源码。当我们new ArrayList()
的时候,默认会有一个空的Object
数组,大小为0。当我们第一次add
添加数据的时候,会给这个数组初始化一个大小,这个大小默认值为10;
数组的大小是固定的,而ArrayList的大小是可变的;因为ArrayList是实现了动态扩容的,假设我们给定数组的大小是10,要往这个数组里边填充元素,我们只能添加10个元素。而ArrayList不一样,ArrayList我们在使用的时候可以往里边添加20个,30个,甚至更多的元素。
3.ArrayList怎样实现动态扩容的?
使用ArrayList在每一次add
的时候,它都会先去计算这个数组够不够空间,如果空间是够的,那直接追加上去就好了。如果不够,那就得扩容。在源码里边,有个grow
方法,每一次扩原来的1.5
倍。比如说,初始化的值是10嘛。现在我第11个元素要进来了,发现这个数组的空间不够了,所以会扩到15;空间扩完容之后,会调用arraycopy
来对数组进行拷贝。
4.线程安全的List还有什么?
在java.util.concurrent
包下还有一个类,叫做CopyOnWriteArrayList。CopyOnWriteArrayList是一个线程安全的List,底层是通过复制数组的方式来实现的;
在add()
方法其实他会加lock
锁,然后会复制出一个新的数组,往新的数组里边add
真正的元素,最后把array的指向改变为新的数组;CopyOnWriteArrayList是很耗费内存的,每次set()/add()
都会复制一个数组出来,另外就是CopyOnWriteArrayList只能保证数据的最终一致性,不能保证数据的实时一致性。假设两个线程,线程A去读取CopyOnWriteArrayList的数据,还没读完,现在线程B把这个List给清空了,线程A此时还是可以把剩余的数据给读出来