写在前面
最近因为拥抱变换,所以开始无奈的面试之路。因为在集合的源码分析上,出了些问题,所以这段时间,好好重新理一理常用的集合源码。(版本基于JDK1.7)
感兴趣的看官,可以看看我的其他文章:
1、常用集合的源码分析:HashMap
2、Java反射实践:从反射中理解class
3、从公司项目配置看Gradle
ArrayList
毫无疑问,提到常用集合。ArrayList势必是第一个被搬出来的,因此我们就先拿它开刀了。
add(E e)
1、初始化
ArrayList的初始化,只有在第一次add的时候进行new数据,数组默认容量是10。
private static final int DEFAULT_CAPACITY = 10;
2、扩容
每次add,第一步先初始化,初始化过后,开始判断是否需要扩容。
扩容的判断,需要借助内部的一个size变量,这个变量用于统计当前数组的真实容量。也就是说我们的每一次add,size都会++。因此在我们进行扩容判断的时候,就是通过这个size和数组的当前最大容量进行比较。
如果if (minCapacity - elementData.length > 0)
其中minCapacity==size+1。满足这个条件说明需要扩容。
扩容的过程比较直接,直接上代码:
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
//扩容1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//越界判断
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//拷贝数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
3、赋值
赋值的过程,就没什么好说的了:
elementData[size++] = e;
OK,add方法的过程就先看到此。非常简单的过程。1、第一次add,初始化一个容量10的数组。2、每次add()需要判断是否需要扩容。3、扩容过程完毕,把数据查到数组中。
add(int index, E element)
接下来,让我们看一个往明确的下标中add的方法。
1、第一步
刚开始的过程没什么好说的,就是比add(E e)的过程,多一步先判断index是否越界;然后依旧是初始化,和扩容的过程。
//判断是否越界
rangeCheckForAdd(index);
//初始化以及扩容
ensureCapacityInternal(size + 1);
2、移动数组
既然我们是往某个下标下插值,那么插入之前,我们就要给要插入的index空出位置来,也就是数组整体移动位置。也就是下边这行代码。
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
通过这个操作我们能看到一个问题,那就是如果我们连续进行相同的add(int index,E e)操作,那么很明显不会出现覆盖。所以,如果我们想要有覆盖的操作,需要调用set系列方法(后续会对此进行分析)。
这里必须得提一点,这里的操作因为需要移动数组,因此对于ArrayList来说,这是一个效率比较低的操作。
也印证了我们老生常谈的话题:数组适合随机读取;链表适合增加与删除。
3、赋值
没啥好说的,同上:
elementData[size++] = e;
get(int index)
关于get操作,其实没啥好特别留意的,很普通的操作。因为直接返回对应数组的index下标即可呐。
public E get(int index) {
//判断是否越界
rangeCheck(index);
return elementData(index);
}
set(int index, E element)
其实覆盖操作,也没什么难,就是单纯把index那个数据覆盖了,仅此而已。
public E set(int index, E element) {
//判断越界问题
rangeCheck(index);
//覆盖数据
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
remove(int index)
remove的操作也没有特别需要注意的地方。基本上贴上代码,就能很清楚的明白:
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null;
return oldValue;
}
基本上到这一步关于ArrayList的正常使用的分析就告一段落。当然,我们提到ArrayList,难免会扯到Vector身上。虽然它的身份略显尴尬,充其量就是一个跑龙套的角色。
而且它内部的保证线程安全的操作,仅仅是使用了synchronized关键字而已。因为就不增加篇幅去梳理它了。
尾声
结束ArrayList的分析,我们可以发现,ArrayList的实现,相对比较的简单。需要留意的点比较的少,大概就剩下了如果此次add,超出了现有数组容量,进行1.5倍扩容。
本菜开源的一个自己写的Demo,这个项目拆解并组合了很多业务。目的在于遇到类似业务,可以快速的ctrl+c/v。希望能给Androider们有所帮助,水平有限,见谅见谅…
https://github.com/zhiaixinyang/PersonalCollect