题容器源码
温馨提醒:idea的进入源码只要点击鼠标滑轮,返回上级只要输入Ctrl+alt+←
ArrayList:
- 底层数据结构:数组,没什么好说的,后续比如链表或者红黑树,我会细讲。
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;//(值为空{},空数组)
}
Add方法:(面试可以秀一下grow过程)
- 第一次新增元素,minCapacity=size+1=0+1=1(size初始为0)
public boolean add(E e) {
ensureCapacityInternal(size + 1); // size初始为0
elementData[size++] = e;
return true;
}
- 点击ensureCapacityInternal再往底层
- (elementData、DEFAULTCAPACITY_EMPTY_ELEMENTDATA是一个对象数组,初始为空)
- DEFAULT_CAPACITY初始为10,可以说是初始容量,(但不是在这里决定的
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//如果这是第二个元素放入,则不会进入判断而是直接返回minCapacity大小的容量
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); //取出最大值,此时minCapacity=10
}
ensureExplicitCapacity(minCapacity);
}
- 在进入ensureExplicitCapacity方法里查看
- modCount初始为0,并且被transient修饰,保证不参与序列化和反序列化。是一个计数器,记录ArrayList进行add和remove的次数。
private void ensureExplicitCapacity(int minCapacity) {
modCount++; //记录add和remove操作次数
// overflow-conscious code
if (minCapacity - elementData.length > 0) //elementData初始为0代表原有数组,minCapacity初始为10
grow(minCapacity);
}
- ArrayList扩容
- oldCapacity代表原来数组长度:旧的数组长度
- newCapacity代表新生成的数组长度:旧的数组长度+旧的数组长度右移一位
- MAX_ARRAY_SIZE:Integer的最大长度-8(减去8是因为jvm运行需要一些头字,大小为8)
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length; //拿到原来数组
int newCapacity = oldCapacity + (oldCapacity >> 1); //>>代表右移,右移移位相当于除2,所以整体长度为1.5倍原数组
if (newCapacity - minCapacity < 0) //如果新的数组长度小于minCapacity长度(初始为size+1,为1,后面变为10)
newCapacity = minCapacity; //扩容长度变为初始容量10
if (newCapacity - MAX_ARRAY_SIZE > 0) //怕超过数字的最大容量-8,为什么减去8,因为jvm会保存一些头字,这些为8
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
- 进入hugeCapacity源码继续查看
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow //为什么超过了Integer最大长度-8还会小于0,因为溢出了二进制最大值
throw new OutOfMemoryError(); //OOM异常
return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE :MAX_ARRAY_SIZE;
//如果没有溢出,则妥协 返回Integer最大长度给她
}
- 最后使用copyOf,将旧数组的值赋值给新数组
elementData = Arrays.copyOf(elementData, newCapacity);
- 最后再返回add方法里,将索引挪到下一位,然后把元素添加进去
elementData[size++] = e;
return true;
Get方法:
- get方法进入
public E get(int index) {
rangeCheck(index); //进入判断索引有没有超过数组最大长度
return elementData(index);
}
- 进入rangeCheck方法
private void rangeCheck(int index) {
if (index >= size) //索引不能超过数组最大值,否则异常
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
- 进入elementData方法
- 这里面的E就是T,泛型
E elementData(int index) {
return (E) elementData[index]; //从elementData数组中取值返回
}
Remove方法:(面试可以秀一下arraycopy过程)
- remove方法是通过元素移动来实现的,先提取出要移动的元素,然后进行移动,最后将其值赋为空,jvm会帮我们自动处理数组内值为null的元素。
public E remove(int index) { //默认数组为{a1,a2,a3,a4,a5}
rangeCheck(index);
modCount++; //计数器,记录add和remove了几次操作
E oldValue = elementData(index); //从数组中取出索引对应元素
int numMoved = size - index - 1; //记录要移动的元素个数,例如你删除的元素索引值为“2”,则得到2
if (numMoved > 0) //当要移动的值大于0,因为有可能删除的是最后一个元素,就没有移动的元素
System.arraycopy(elementData, index+1, elementData, index,numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
- rangeCheck:检查索引是否超过数组范围
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
- System.arraycopy:是一个本地方法,下面方法运行过程
- 默认数组为{a1,a2,a3,a4,a5},删除了a3,此时numMoved=2,要移动2个元素。
- 第一个传入数组是原数组,从index+1(a4)开始复制numMoved位(2位)带第二个elementData的第三个位置中(原本a3位置)
- 最终得到的数组是{a1,a2,a4,a5,a5}
public static native void arraycopy(Object src, int srcPos,Object dest, int destPos,int length);
//下面是从remove里抠下来的方便解释
//
System.arraycopy(elementData, index+1, elementData, index,numMoved);
- 最终将最后一个元素赋值为null,让JVM的GC帮我们处理空值,结果为{a1,a2,a4,a5}
elementData[--size] = null; // clear to let GC do its work
return oldValue;