ArrayList的扩容机制

前置知识

ArrayList的底层实现是一个Object[],而LinkedList的底层实现是一个链表
ArrayList与LinkedList相对比:

  • ArrayList在随机访问时可以做到O(1),但是LinkedList的随机访问就是遍历链表,所以时间复杂度是O(N)
  • ArrayList在插入/删除元素时,需要移动额外的很多元素,但是LinkedList在插入/删除时无需移动其他元素,效率更高
  • 如果数据需要频繁的插入/删除,那么建议使用LinkedList
  • 如果数据较为稳定,希望高效的访问元素,建议使用ArrayList
利用反射获取ArrayList容量

由于ArrayList中并没有提供获取底层数组的方法,如何利用反射来获取ArrayList的真实容量

public static int getCapatity(ArrayList list) {  
    int len = 0;  
    try {  
        // 获取Class对象  
        Class<?> clazz = Class.forName("java.util.ArrayList");  
        // 获取类属性  
        Field field = clazz.getDeclaredField("elementData");  
        // 暴力打开权限  
        field.setAccessible(true);  
        Object[] objList = (Object[]) field.get(list);  
        // 返回数组长度  
        len = objList.length;  
    } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException e) {  
        e.printStackTrace();  
    }  
    // 返回数组长度  
    return len;  
}

ArrayList的扩容机制

首先,创建ArrayList的方式有哪些
ArrayList提供了三个构造方法来创建ArrayList

// 1. 无参构造
public ArrayList() 

// 2. 指定容量大小
public ArrayList(int initialCapacity) 

// 3. 指定元素来创建  
public ArrayList(Collection<? extends E> c) 
无参构造

当调用无参构造来创建ArrayList时,看此无参构造方法的源码

public ArrayList() {  
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;  
}

其中elementData就是ArrayList实现的Object[ ],在无参构造中,直接为数据赋了一个默认的空元素值,也就是此时的容量为0
当ArrayList的容量不足时,扩容的机制是:
当目前的容量是0时,第一次扩容后的容量为10,以后每次扩容后的容量都是原始容量的1.5倍!!!
但是此处的1.5倍并不是在算术意义上的size*1.5,因为计算机并不能真正处理浮点数
此处是1.5倍是位运算的右移1位,能够避免浮点数的问题
即:

扩容后的容量 = 原始容量 + 原始容量 >> 1

当容量不足时,会创建一个新的指定大小的新数组,将原始数组中的内容拷贝到新的数组中(旧的数组不被再引用,就会被垃圾回收掉)
分析以下代码来试试:

// 1. 通过无参构造创建,此时容量为0
ArrayList list = new ArrayList();
// 2. 向容器中添加元素
list.add(5);
// 此时容量不足,触发第一次扩容,此时容量为10

// 3. 继续添加10个元素,添加后共有11个元素
for(int i = 1; i <= 10 ;i ++){
	list.add(i);
}
// 当要插入第11个元素时,检查发现此时容量不足,触发第二次扩容
// 扩容后的容量为 = 10 + 10 >> 1 = 15;

// 4. 此时有11个元素,容量为15
// 继续添加
for(int i = 1;i <= 10 ; i ++){
	list.add(i);
}
// 当添加第16个元素时,发现容量又不足了,所以此时触发第三次扩容
// 新的容量 = 15 + 15 >> 1 = 22
有参构造指定容量大小

来看利用ArrayList的指定容量大小的源码:如果指定的容量大于0,就会创建指定容量大小的数组。如果指定的容量小于0,抛出异常。

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);  
    }  
}

当指定了ArrayList的初始容量后,以后每次的扩容就是 扩容后的容量 = 原始容量 + 原始容量 >> 1,如果初始容量为0,还是会第一次扩容为10

指定元素创建

直接传入一个集合,来创建包含指定元素的ArrayList

ArrayList list = new ArrayList(Arrays.asList(1,2,3));

此时会使用指定的元素大小作为起始容量,以后的扩容规则还是同上。

addAll()

add()方法是一次添加一个元素,当使用addAll()方法可以一次添加多个元素。
以上的扩容机制都是在add()方法触发的情况下,新的容量 = 原始容量 + 原始容量 >> 1
当addAll()触发扩容时,扩容机制是不一样的。
会在 默认下次扩容后的容量大小当前所有的元素数量 之间选择一个较大值

ArrayList list = new ArrayList();
// 此时容量为0
list.add(Arrays.asList(1,2,3));
// 此时容量不足,触发扩容,
// 此时的元素数量是3,默认下次扩容后的容量是10
// 选择10 作为扩容后的容量

再来看一个例子

Array list = new ArrayList();
// 刚创建,此时容量为0
// 继续添加,容量不足,触发扩容
list.addAll(Arrays.asList(1,2,3,4,5,6,7,8,9,10,11));
// 默认下次扩容后的容量是10, 但是此时的元素数量是11,
// 所以扩容后的容量为11

再来看一个例子

ArrayList list = new ArrayList();
for(int i = 1;i <= 10 ;i ++){
	list.add(i);
}
// 此时容量为10,容量不足
list.addAll(Arrays.asList(1,2,3))
// 下次扩容后的容量为15,元素数量是13,
//  所以扩容后的容量是15

总结

三种创建ArrayList的方式:

  • ArrayList() 会创建一个起始容量为0的大小
  • ArrayList(int capacity)以指定的大小作为起始容量
  • ArrayList(Collection c)以指定的元素的数量作为起始容量

两种扩容机制:

  • add()如果原始容量是0,则扩容后的容量为10,以后每次扩容都是之前的1.5倍
  • addAll()会在下次扩容的大小和当前元素数量之间选择一个较大值
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值