ArrayList集合底层原理
1.利用空参构造创建的集合,在底层会创建一个默认长度为0的数组(elementData)
2.添加第一个元素时,底层会创建一个新的长度为10的数组
3.存满时,数组容量会扩容1.5倍
4.若依次添加多个元素,1.5倍还放不下,则新创建的数组的长度会以实际为准
说明:
第一种情况:
添加第一个元素
ArrayList<String> list=new ArrayList<> ();
当我们使用无参构造创建一个对象,JVM会在底层创建一个默认长度为0的数组,名字叫elementData,还有一个成员属性size。
size有两层含义: 1.集合中元素的个数 2.元素下次存入的位置。
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
private int size; //size成员变量
transient Object[] elementData; // elementData是一个数组,
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//将空数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA 赋值给 elementData
public boolean add(E e) {
modCount++;
add(e, elementData, size);
return true;
}
当我们使用: list.add("aaa"); 添加第一个元素,
e="aaa",调用 add(e, elementData, size) (其中第一个参数:要添加的元素,第二个参数:底层该数组的名称,第三个参数:集合的长度/元素下次应存入的位置).
执行以下代码:其中s=0, elementData.length=0,执行 elementData = grow(); 语句,表示数组扩容,调用grow()方法
private void add(E e, Object[] elementData, int s) {
if (s == elementData.length)
elementData = grow();
elementData[s] = e;
size = s + 1;
}
执行下列代码:
private Object[] grow() {
return grow(size + 1);
}
size + 1 = 0 +1 = 1,调用下面grow(1)
private Object[] grow(int minCapacity) {
int oldCapacity = elementData.length; //记录原来的老数组
if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
int newCapacity = ArraysSupport.newLength(oldCapacity,
minCapacity - oldCapacity, /* minimum growth */
oldCapacity >> 1 /* preferred growth */);
return elementData = Arrays.copyOf(elementData, newCapacity);
} else {
return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
} //DEFAULT_CAPACITY为 10
}
//private static final int DEFAULT_CAPACITY = 10;
minCapacity=1,oldCapacity = 0,第一次添加数据的时候,会执行这里的else语句,其中DEFAULT_CAPACITY默认容量为10,调用Math.max()方法,然后new,会创建一个长度为10的数组,然后 return 返回到 elementData = grow()调用处,继续执行 elementData[s] = e; size = s + 1; 语句添加元素,size加一。
第二种情况:
若我们已经向数组中添加了10个元素,“aaa”为第11个
ArrayList<String> list=new ArrayList<> ();
当我们使用无参构造创建一个对象,JVM会在底层创建一个默认长度为0的数组,名字叫elementData,还有一个成员属性size。
size有两层含义: 1.集合中元素的个数 2.元素下次存入的位置。
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
private int size; //size成员变量
transient Object[] elementData; // elementData是一个数组,
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//将空数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA 赋值给 elementData
public boolean add(E e) {
modCount++;
add(e, elementData, size);
return true;
}
当我们使用: list.add("aaa"); 添加第一个元素,
e="aaa",调用 add(e, elementData, size)(其中第一个参数:要添加的元素,第二个参数:底层该数组的名称,第三个参数:集合的长度/元素下次应存入的位置),
执行以下代码:其中s=11, elementData.length=10,执行 elementData = grow(); 语句,表示数组扩容,调用grow()方法
private void add(E e, Object[] elementData, int s) {
if (s == elementData.length)
elementData = grow();
elementData[s] = e;
size = s + 1;
}
执行下列代码:
private Object[] grow() {
return grow(size + 1);
}
size + 1 = 10 +1 = 11,调用下面grow(11)
private Object[] grow(int minCapacity) {
int oldCapacity = elementData.length; //记录原来的老数组
if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
int newCapacity = ArraysSupport.newLength(oldCapacity, //老容器
minCapacity - oldCapacity, //理论上我们至少要新增的容量
oldCapacity >> 1 ); //默认新增的容量
return elementData = Arrays.copyOf(elementData, newCapacity);
} else {
return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
} //DEFAULT_CAPACITY为 10
}
//private static final int DEFAULT_CAPACITY = 10;
minCapacity=11, oldCapacity=10, 执行if语句,调用方法 ArraysSupport. newLength (oldCapacity, minCapacity - oldCapacity, oldCapacity >> 1 ); 其中oldCapacity代表老容器的容量,(minCapacity - oldCapacity)表示理论上我们至少要新增的容量,oldCapacity >> 1(表示老容器容量除以2,扩容1.5倍中 .5 的来源)表示默认新增的容量大小,
执行以下代码
public static int newLength(int oldLength, int minGrowth, int prefGrowth) {
int prefLength = oldLength + Math.max(minGrowth, prefGrowth); // might overflow
if (0 < prefLength && prefLength <= SOFT_MAX_ARRAY_LENGTH) {
return prefLength;
} else {
// put code cold in a separate method
return hugeLength(oldLength, minGrowth);
}
}
其中Math.max(minGrowth, prefGrowth)把我们至少要新增的容量和默认新增的容量大小进行比较
为啥呢:
因为在集合中,我们不仅仅一次添加一个元素,还可以一次添加多个元素
若我们调用addAll()方法,一次添加100个元素,那么minGrowth=100,这时候就以我们实际的元素个数来进行扩容
int prefLength = oldLength + Math.max(minGrowth, prefGrowth);
//表示新数组真正的长度
-
第一种情况:如果一次添加一个元素,那么第二个参数一定是1,表示此时数组只要扩容1个单位大小就可以了
-
第二种情况:如果一次添加多个元素,假设为100个,那么第二个元素为100,表示此时数组需要扩容100个单位大小才可以
将新数组真正的长度prefLength 交给newCapacity,然后调用 Arrays.copyOf(elementData, newCapacity); 方法进行数组拷贝,该方法:
1.会根据第二个参数创建新的数组
2.把第一个参数中的所有数据,全部拷贝到新数组当中
然后 return 返回到 elementData = grow()调用处,继续执行 elementData[s] = e; size = s + 1; 语句添加元素,size加一。