文章目录
- 一、前言
- 二、ArrayList扩容机制
- 1、适用于什么场景?
- 2、ArrayList特点
- 3、ArrayList扩容机制
- 3.1、内存分配的效率:
- 3.2、数据迁移的代价
- 3.3、性能和空间的平衡
- 三、总结
一、前言
对于ArrayList集合可能大家并不陌生,但ArrayList集合的扩容机制大家是否了解呢?我们今天着重来看看
二、ArrayList扩容机制
1、适用于什么场景?
检索比较多的场景
2、ArrayList特点
1、ArrayList集合底层采用了数据这种数据结构,是Object类型
2、ArrayList的默认初始容量为10,扩容因子为1.5
3、建议给定一个预估计的初始化容量,减少数组扩容的次数,这是ArrayList集合比较重要的优化策略.因为在在扩容的同时需要将原来数组中的数据复制到新数组里,但如果要插入大量数据时,赋值数组的形式效率很低,所以大多数情况下会使用带参构造函数,传入一个预估计容量,提前定义好容量。
4、ArrayList是非线程安全的
import java.util.ArrayList;
import java.util.List;
public class ListTest {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("b");//第一个,索引下标0
list.add("d");
list.add("c");
list.add("a");
list.add("d"); //允许使用重复元素
System.out.println(list); //输出结果:[b, d, c, a, d]
System.out.println(list.get(2)); //输出指定下标的元素,输出结果:c
list.add(1,"f");//在指定索引下标位置添加元素
System.out.println(list); //输出结果:[b, f, d, c, a, d],原来下标为1和1之后的下标索引位置的元素自动向后移动
List<String> a = new ArrayList<String>();
a.add("123");
a.add("456");
list.addAll(2,a); //在指定索引下标的位置插入集合
System.out.println(list);//输出结果:[b, f, 123, 456, d, c, a, d]
//获取指定元素在集合中第一次出现的索引下标
System.out.println(list.indexOf("d")); //输出结果:4
//获取指定元素在集合中最后一次出现的索引下标
System.out.println(list.lastIndexOf("d"));//输出结果:7
list.remove(2); //根据指定的索引下标移除元素
System.out.println(list); //输出结果:[b, f, 456, d, c, a, d]
list.set(1,"ff"); //根据指定的索引下标修改元素
System.out.println(list); //输出结果:[b, ff, 456, d, c, a, d]
//根据索引下标的起始位置截取一段元素形成一个新的集合,截取的时候,包含开始的索引不包含结束时的索引
List<String> sublist= list.subList(2,4);
System.out.println(sublist);//输出结果:[456, d]
System.out.println(list.size());//输出结果7
}
}
import java.util.LinkedList;
import java.util.List;
public class ListTest {
public static void main(String[] args){
List l1 = new LinkedList();
for(int i = 0;i<=5;i++){
l1.add("a"+i);
}
System.out.print(l1);
l1.add(3,"a100");
System.out.println(l1);
l1.set(6,"a200");
System.out.println(l1);
System.out.print((String)l1.get(2)+" ");
System.out.println(l1.indexOf("a3"));
l1.remove(1);
System.out.println(l1);
}
}
输出结果:
[a0,a1,a2,a3,a4,a5]
[a0,a1,a2,a100,a3,a4,a5]
[a0,a1,a2,a100,a3,a4,a200]
a2 4
[a0,a2,a100,a3,a4,a200]
3、ArrayList扩容机制
ArrayList的使用前不需要像数组一样提前定义大小空间,容量是随着使用时自动增长的,那为什么在使用ArrayList的add方法添加元素的时候底层还需要判断集合的容量是否能够放下要添加的元素呢?又没有定义固定大小直接放进去不就好了吗?
add方法添加分为三步:
①、判断集合容量是否满足添加的元素
②、添加元素
③、集合长度+1
解答:
用户不需要提前定义大小,那是因为底层默认已经定义好了大小。其实是有一个边界值的,并不是无限增长的。使用时增加,是因为底层有扩展因子(扩容因子是1.5),当数量达到数组的百分之多少的时候就会扩容。ArrayList默认的初始大小是10
问题:大家可以思考思考为什么ArrayList底层扩容因子是1.5?为什么不是1.3、2.4……?
ArrayList的底层扩容因子是1.5,而不是其他数字,是为了在平衡内存使用和性能之间找到一个合适的折中方案。下面是一些原因:
3.1、内存分配的效率:
扩容因子的选择会影响内存分配的效率。如果扩容因子过小,每次扩容都只增加少量的容量,这会导致频繁的内存分配操作,增加了时间和空间的开销。而如果扩容因子过大,每次扩容都会增加大量的容量,这可能会导致浪费过多的内存。1.5是一个相对较小的扩容因子,可以在一定程度上平衡内存使用和性能。
3.2、数据迁移的代价
当ArrayList需要扩容时,需要将原有数据迁移到新的更大的数组中。扩容因子的选择会影响数据迁移的频率和代价。较小的扩容因子会导致更频繁的数据迁移,而较大的扩容因子会减少数据迁移的次数。1.5作为一个相对较小的扩容因子,可以在一定程度上减少数据迁移的代价。
3.3、性能和空间的平衡
ArrayList旨在提供高效的随机访问和动态增长的能力。选择1.5作为扩容因子可以在一定程度上平衡性能和空间的需求。较小的扩容因子可以减少内存的浪费,而较大的扩容因子可以减少内存分配的频率。
综上所述,ArrayList的特点如下
- 初始容量:当你创建一个ArrayList对象时,它会分配一个初始容量(默认为10,可以通过构造函数设置初始容量)。这个初始容量表示ArrayList内部的数组可以容纳的元素数量。
- 添加元素:当你向ArrayList中添加元素时,它首先检查是否需要扩容。这是通过比较ArrayList内部的元素数量与当前容量之间的关系来完成的。
- 扩容条件:通常,ArrayList会在元素数量达到当前容量时触发扩容操作。当元素数量等于或超过当前容量时,ArrayList就会启动扩容机制
- 扩容策略:ArrayList会创建一个新的更大的数组,通常情况下,新数组的容量会是当前容量的1.5倍(这个倍数可以通过ensureCapacity方法或构造函数的参数进行调整)。然后,ArrayList会将所有现有元素复制到新数组中。
- 复制元素:将元素复制到新数组中可以使用System.arraycopy方法或类似的机制。这个操作的时间复杂度是O(n),其中n是元素的数量。
- 更新容量:一旦所有元素都复制到新数组中,ArrayList会将内部的数组引用指向新数组,并且更新容量为新数组的容量。
- 添加新元素:最后,ArrayList会将新元素添加到新数组中,现在新数组的容量足够容纳所有元素。
- 扩容成本:由于扩容操作需要复制元素到新数组,因此它会引入一些性能开销。为了减少扩容的频率,通常可以在创建ArrayList时就指定一个足够大的初始容量,以避免多次扩容。
三、总结
ArrayList的扩容机制是自动管理的,它会在需要时动态地扩展内部数组的容量,以适应不断增加的元素。这个机制确保了ArrayList在添加元素时能够保持高效性能,但也需要考虑到扩容操作可能引入的性能开销。因此,在处理大量数据时,可以通过手动设置足够大的初始容量来减少扩容的次数,从而提高性能。