ArrayList的创建与扩容机制详解
ArrayList的创建与扩容
一.创建ArrayList
1.元素的存储
粗浅讲一下 ArrayList 底部还是用数组存储具体元素的
还使用了一个计数器 size存储当前数组存放最后一个元素的下标加1 也就是当前元素的个数;
2.无参的构造方式:
//无参构造
ArrayList<String> arrayList=new ArrayList();
System.out.println(arrayList.size()); //输出arraylist里面的内容长度 为0
System.out.println(getArrayListLength(arrayList));//输出arrayList的当前容量 为0
//下面这个静态方法 getArrayListLength(arrayList) 会贴上去 大家可以复制一下自己去测试下看看
默认容量:0
过程是:将ArrayList里面用来存储内容的Object 数组elementData 赋值一个静态常量
该静态常量也是Object []即Object类型的数组 只不过内容为空容量为空
前面两个静态常量大家先记住,等后面分析源码的时候会用到;
3.传入指定容量参数的构造方式
ArrayList<String> arrayList1=new ArrayList(8);
System.out.println(arrayList1.size()); //输出arraylist里面的内容长度 为0
System.out.println(getArrayListLength(arrayList1));//输出arrayList的当前容量 为8
arrayList1.addAll(Arrays.asList("你好1","你好2","你好3","你好4","你好5","你好6","你好7","你好8"));
//Arrays.asList 是调用一个Arrays里的静态方法快速创建了一个List类型的容器(外部声明为List 其实底部是继承自AbstracArraylist抽象类的
//Arrays里面的一个内部类) 作用是接受一个字符串的动态参数创建出一个List对象 然后作为参数让arrayList1添加进去
4.传入集合参数的构造方式
ArrayList<String> arrayList2=new ArrayList<String>(arrayList1);
System.out.println(arrayList2.size()); //输出arraylist里面的内容长度 为0
System.out.println(getArrayListLength(arrayList2));//输出arrayList的当前容量 为8
输出:
二.如何查看一个ArrayList的容量与内容长度?
查看内容长度:
调用arrayList.size()
方法:
该方法会返回ArrayList里的size属性
System.out.println(arrayList.size());
查看当前容量:
大家可以复制粘贴一下一下代码 自己也可以去测试和使用
public static int getArrayListLength(ArrayList list) throws Exception{
//获取Class对象
Class c = Class.forName("java.util.ArrayList");
//映射Class对象c所表示类(即Arraylist)的属性
Field field = c.getDeclaredField("elementData"); //这里要import java.lang.reflect.Field;
//设置访问状态表示为true
field.setAccessible(true);
//返回指定对象上此 Field 表示的字段的值
Object[] object = (Object[])field.get(list);
return object.length;
}
该方法是利用反射机制输出当前ArrayList的容量 会输出当前底层Object数组的大小 也就是容量 具体实现可以不必在意,只要注意怎么使用
1.注意导入包:
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
2.注意在main方法体外部加上抛出异常的声明
public static void main(String[] args) throws Exception {
}
3.之后就可以直接用方法名调用了
System.out.println(getArrayListLength(arrayList1));
三.创建后的扩容
1.无参构造生成对象容量的首次扩充
使用add()添加一个字符串之后容量直接变成了10 这是为什么?去看看源码:
接着再往里看底部:
我们这时候需要的最小容量是0+1 也就是1,而如果你是使用无参构造,他会返回一个 你需要的最小容量1 与 一个常量值DEFAULT_CAPACITY(值为10)的最大值.
所以此时容量newCapacity 为10
回到上一层
grow(minCapacity);
是具体实现扩容的底层代码
Arrays.copyOf(elementData, newCapacity)
该方法再底部新建了一个容量为newCapacity的新数组 并讲elementData数组里面的所有元素复制了进去 ,最后再将创建的新数组引用值赋到当前ArrayList的Object [] elementData下,这样就完成了一次扩容;
这是中间代码块的作用与含义:(看不懂可以先忽略)
回头再看一下结果:
所以我们得出的结论是:
无参构造的情况下,在使用add添加一个元素的时候,首次扩充就会扩充到容量为10的程度;(下一次扩充 就是要add第11个元素的时候)
2.给定指定容量构造方式生成对象的首次扩充
ArrayList<String> arrayList1=new ArrayList(0);//如果指定了容量8的话 首次添加不会扩充
//其中有两个步骤体现不出来 这里我传入参数 设置其为0
//注意 我设置其为0,和无参构造是不同的
//不同点就体现在其首次扩充的时候
System.out.println(arrayList1.size());
System.out.println(getArrayListLength(arrayList1));
arrayList1.add("你好");
System.out.println("添加一个");
System.out.println(arrayList1.size()); //1
System.out.println(getArrayListLength(arrayList1)); //1 注意1: 这就是区别 如果是无参构造 第一次扩充就会扩充10个;
arrayList1.add("你好2");
System.out.println("添加第二个");
System.out.println(arrayList1.size()); //2
System.out.println(getArrayListLength(arrayList1)); //2
arrayList1.add("你好3");
System.out.println("添加第三个");
System.out.println(arrayList1.size()); //3
System.out.println(getArrayListLength(arrayList1)); //3
arrayList1.add("你好4");
System.out.println("添加第四个");
System.out.println(arrayList1.size()); //4
System.out.println(getArrayListLength(arrayList1)); //4
arrayList1.add("你好5");
System.out.println("添加第5个");
System.out.println(arrayList1.size()); //5
System.out.println(getArrayListLength(arrayList1)); //6 注意2: 前面发生的扩充都是 添加一个扩充一个
输出结果:
底层原因:
需要多少容量:当前容量 + 传进来几个元素(现在都使用add 一次只加1个)
第一次添加 需要0+1 minCapacity,而当前容量为0
0*1.5=0;0<1 进入if代码块里面 最终容量为1
第二次添加 需要1+1 minCapacity,而当前容量为1
1+1/2(int取整)=1;1<2 进入if代码块里面 最终容量为2
第三次添加 需要2+1 minCapacity,而当前容量为2
2+2/2(int取整)=3;3不小于3 不进入if代码块里面直接return 最终容量为3
第四次添加 需要3+1 minCapacity,而当前容量为3
3+3/2(int取整)=4;4不小于4 不进入if代码块里面直接return 最终容量为4
第五次添加 需要4+1 minCapacity,而当前容量为4
4+4/2(int取整)=6;6不小于5 不进入if代码块里面直接return 最终容量为6
这就是这几次运算的过程:
什么时候按1.5倍扩充来 什么时候按自己最小容量需求来
:
3.使用addALL一次添加多个元素时的首次扩充情况
4.后续多次扩容情况
1. 扩充1.5倍满足了需求
就按照1.5倍来
2. 扩充1.5倍没有满足需求
就按照具体需要多少扩充多少