ArryList底层数据结构以及常用方法源码理解
1.先看一段测试代码
package com.heyuanhang.conllention_;
import java.util.ArrayList;
/**
* @Author 何远航
* @Date: 2021/4/17 12:57
* @Version 1.8
*/
public class ArrayListYuanMa {
public static void main(String[] args){
//无参构造器,定义一个ArrayList对象时,会初始化一个空数组
//当第一次加入数据元素时会申请一个10个长度的数组
ArrayList list=new ArrayList();
list.add("heyuanhang");
}
}
(1)查看ArrayList的无参构造
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
注意:上述的DEFAULTCAPACITY_EMPTY_ELEMENTDATA是ArryList的一个属性,很显然这个属性是一个空的Object类型的数组。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
看到这儿就已经知道了,原来当执行ArryList的无参构造时,是将ArryList底层维护的elementData数组初始化为{}即空的数组。
(2)好了,现在已经有一个空数组了,但是其长度却为,现在执行add方法开始添加元素.
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
这儿的ensureCapacityInternal(size + 1);方法是干嘛的?不妨进入看一下:
private void ensureCapacityInternal(int minCapacity) {
//这儿判断一下elementData是否是{}
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
由此就可以知道,原来这个方法是判断一下,elementData的长度是不是0,如果是0的话,就赋值10给minCapacity利用ensureExplicitCapacity(minCapacity)方法进行扩容。
(3)进入ensureExplicitCapacity(minCapacity)方法,源码如下:
private void ensureExplicitCapacity(int minCapacity) {
//记录一下这个数组被修改的次数
modCount++;
// overflow-conscious code
//看一下添加一个元素后的数组长度是否超过已有的数组元素
//有就开始扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
注意:上面的minCapacity这个形参最开始传入的值是,size+1即就是添加一个元素以后的值
(4)进入真正的扩容方法grow方法:
private void grow(int minCapacity) {
// overflow-conscious code
/*
获取已有的数组长度
*/
int oldCapacity = elementData.length;
//将已有长度的1.5倍赋值给newCapacity
int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果进行1.5倍扩容后长度任然小于minCapacity
//就将minCapacity赋值给newCapacity进行扩容
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);
}
由上述代码可以知道,原来ArryList的容量不够以后就进行1.5倍扩容。
(5)到此为止,ArryList就已经扩容好了,第一次添加元素时,扩容大小是10.
(6)最后一步就开始进行数据的存储。elementData[size++] = e;
由此,就会得出一个结论:
当使用ArryList无参构造时,第一次添加元素时会分配10个大小的空间,当容量不够以后再进行1.5倍的扩容。
这儿有必要看一下,ArryList的有参构造:
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);
}
}
(上述代码可知,当传过来的容量大于0时,直接分配指定大小的空间,如果等于0时,作用等同于无参构造,否则的话就抛出异常)
二、向指定位置添加元素add(int index,Object obj)
测试代码:
package com.heyuanhang.conllention_;
import java.util.ArrayList;
/**
* @Author 何远航
* @Date: 2021/4/17 12:57
* @Version 1.8
*/
public class ArrayListYuanMa {
public static void main(String[] args){
ArrayList list = new ArrayList(8);
list.add(0, 100);
System.out.println(list.toString());
}
}
采用了,有参构造,指定扩容的长度为8,同样当满8后,依旧会进行1.5倍扩容。
(这儿值得注意的是,指定位置的插入的位置index不是随便的,而是有一定规范的,不能认为只要在申请数组大小之内就行了,这样的认为是错误的,必须明确的是index的值必须小于已有元素所占有的长度,就像上面现在申请了8个大小的,但是还未添加数据时,里面的有效数据长度size的值就是0,因此当index大于或小于0时都会抛出异常)
(1)现在进入add的一个重载方法add(int index,Object obj)
public void add(int index, E element) {
//这个方法就是判断index即想插入的位置是否合理
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
(2)执行rangeCheckForAdd(index);
private void rangeCheckForAdd(int index) {
//如果超过有效长度
//或者小于0时
//都会抛出异常
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
(3)检测完index的合法性之后,就开始进入 ensureCapacityInternal(size + 1)方法进行判断elementData是否是空数组{},这个方法的作用上面,已经讲过,这儿就不多讲了。
(4)然后进入ensureExplicitCapacity(minCapacity);
方法确认一下是否需要进行扩容。
(5)最终进行数据的存储就行了。
三、删除指定位置的元素remove(int index
)
测试源码:
package com.heyuanhang.conllention_;
import java.util.ArrayList;
/**
* @Author 何远航
* @Date: 2021/4/17 12:57
* @Version 1.8
*/
public class ArrayListYuanMa {
public static void main(String[] args){
ArrayList list = new ArrayList(1);
list.add(100);
list.add(100);
list.add(100);
list.add(100);
list.remove(0);
System.out.println(list.toString());
}
}
(1)进入remove方法
public E remove(int index) {
//判断一下index是否合法
rangeCheck(index);
//记录一下数组被修改的次数
modCount++;
//将待删除位置的元素提取出来
E oldValue = elementData(index);
//这儿numMoved的值其实就是index位置之后的元素个数
//举个例子,如果elementData的有效长度是3
//当想删除第二个位置的元素即index=1时,此时位置1后还剩下3-1-1=1个元素
int numMoved = size - index - 1;
if (numMoved > 0)
//进行数组拷贝
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
针对上述的代码中的System.arraycopy(elementData, index+1, elementData, index, numMoved);方法参数的说明
elementData :源数组
index+1:元素组的起始位置
elementData:目标数组
index:目标数组的起始位置
numMoved:要赋值元素的长度
(2)好了,到这儿进入rangeCheck(index);
方法看一下:
private void rangeCheck(int index) {
//如果超过有效长度,抛出异常
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
(3)进入E elementData(int index)
方法
E elementData(int index) {
//返回指定位置的元素
return (E) elementData[index];
}
(4)好了,到此为止,删除方法已经完毕了。该方法返回的就是删除的元素值。
四、将一个集合添加到当前集合的方法boolean addAll(Collection<? extends E> c)
方法
测试代码:
将list1集合中的元素添加到list集合中
package com.heyuanhang.conllention_;
import java.util.ArrayList;
/**
* @Author 何远航
* @Date: 2021/4/17 12:57
* @Version 1.8
*/
public class ArrayListYuanMa {
public static void main(String[] args){
ArrayList list = new ArrayList();
ArrayList list1 = new ArrayList();
list.add(100);
list.add(100);
list.add(100);
list.add(100);
list1.add("何远航");
list1.add("heyuanhang");
list.addAll(list1);
System.out.println(list.toString());
}
}
(1)进入boolean addAll(Collection<? extends E> c)
方法:
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
对上述方法功能的简要概述:
1.先将传递过来的集合转换成一个Object类型的数组
2.然后获得该数组的长度
3.在利用ensureCapacityInternal
方法进行判断一下当前的elementData是否为{},然后调用void ensureExplicitCapacity(int minCapacity)
方法判断一下是否需要调用grow
进行扩容。
4.再进行数组的复制就OK了。
这就是对ArryList底层数据结构以及常用方法源码理解。