ArrayList底层扩容机制
先来看下没有指定参数的List集合:
// 使用无参构造创建List集合 List list = new ArrayList(); // 添加数据 for (int i = 1; i <= 10; i++) { list.add(i); } // 添加数据 for (int i = 11; i <= 15; i++) { list.add(i); }
话不多说直接上源码:
1、初始化集合
/** * 初始化一个容量为0的列表 */ public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
2、添加数据:涉及一个自动装箱
public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }
3、执行list.add方法:
1.这里进行判断容量是否够不够。
2.然后再进行赋值操作
/** * Appends the specified element to the end of this list. * * @param e element to be appended to this list * @return <tt>true</tt> (as specified by {@link Collection#add}) */ public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; }
3.执行ensureCapacityInternal方法
我们在for循环中添加的i就是minCapacity(1)跟DEFAULT_CAPACITY(常量为10),,进行比较,发现DEFAULT_CAPACITY大,就将这个10赋值给list集合无参构造器。也就是初始化容量为10
private static int calculateCapacity(Object[] elementData, int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); } return minCapacity; }
4、调用grow完成底层的扩容
modCount++:记录集合被修改的次数,防止有其他线程进来修改
minCapacity(10)-elementData.length(0) > 0; 实际上需要的最小容量 - Object数组初始化容量 > 0,elementData大小不够,才会调用底层的grow方法执行扩容。
private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); }
5、grow扩容方法
第一次进来:
elementData.length为0,oldCapacity + (oldCapacity >> 1)是等于0的; 那么第4行代码不会执行,也就是第一次不会执行扩容
第5行的if执行:
if (newCapacity - minCapacity < 0) 第一次进来这个if语句会被执行,0 - 10 是小于0的,那么就将minCapacity(10)赋值给newCapacity,即newCapacity为10.
第7行的if不会执行:
因为10-int类型最大值,不大于0
第10行的数组拷贝执行:
就是说将elementData(0),扩容为newCapacity(10)大小,没有数据,那么就是数组中有10个空数据
这里使用Arrays.copyOf()就是为了保留原有数组的数据的情况下,然后增加新的数组(null)
现在elementData为10个大小了,第二次及其以后都是按照1.5(oldCapacity + (oldCapacity >> 1)进行扩容了。)
1 private void grow(int minCapacity) { 2 // overflow-conscious code 3 int oldCapacity = elementData.length; 4 int newCapacity = oldCapacity + (oldCapacity >> 1); 5 if (newCapacity - minCapacity < 0) 6 newCapacity = minCapacity; 7 if (newCapacity - MAX_ARRAY_SIZE > 0) 8 newCapacity = hugeCapacity(minCapacity); 9 // minCapacity is usually close to size, so this is a win: 10 elementData = Arrays.copyOf(elementData, newCapacity); 11 }
6、放入第一个数据
回到第二步的:elementData[size++] = e;
elementData数组为10,这时候size默认是为0的,就将数据放到数组的第一个位置上面:elementData[0] = 1;
这里注意的是,第一次扩容完毕数组大小为10,只有这10个数组容量没有了才会继续执行扩容,就说明在第一次扩容后可以添加for循环中1-10这十个数据~
7、第二次扩容:数组容量不够存放1-10个数据,执行扩容
执行到11-15数据的添加位置上:继续回到第五步的grow继续执行扩容
扩容后的elementData数组:
end:第三次扩容不显示库容后的容量问题
很多小伙伴在debug的时候,发现1-10正常显示,11-15正常显示(上图),但是执行到第三次扩容的时候(10+15/2 = 22)时候并没有看到null。
这是因为idea的原因,只需要在设置中调试一下:
对比一下没设置跟设置后的: