周末回家写的ArrayList源码分析

Posted WwpwW

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了周末回家写的ArrayList源码分析相关的知识,希望对你有一定的参考价值。

一,ArrayList源码分析

再次看ArrayList的源码,一会儿就看完了,真快呀,这也就是自己所感慨的慢就是快吗?本次源码分析自己还是按照以往的风格来分析。

按照单点突破,逐个方法分析了,分析的思路还是和以往一样,不过这是我个人的风格。

如果你有自己的源码分析风格,可以按照自己的思路去做,这里如果可以帮助到你就再好不过了,下面自己就按照自己的思路和文章写法慢慢分析了。

二,方法分析

2.0,左右可滑动查看文字注释

2.1,构造函数

public ArrayList() {
//这里主要是构造了一个空的数组,这个常量就是{}
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

这里简单的说下,构造函数很重要,涉及的内容也很多,比如说,初始化一个对象要经历一个什么样的过程? 造轮子的时候,我们可以在构造函数里面做点什么呢? 都侧面说明了构造函数的特性,需要好好理解一下。

2.2,add()方法

public boolean add(E e) {
//进行扩容操作,看下第二步的内容
ensureCapacityInternal(size + 1);
//将元素e增加到数组的末尾位置
elementData[size++] = e;
//默认就是添加元素成功了
return true;
}
//第二步操作
private void ensureCapacityInternal(int minCapacity) {
//这一步的判断是有可能是第一次添加元素
//此时我们new ArrayList()操作后
//此时的成员变量elementData正好等于DEFAULTCAPACITY_EMPTY_ELEMENTDATA
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//由于默认初始容量是10,所以取两者之间的最大值,这里发现工具类已经体现出来了
//简化了代码的同时,增加了可读性
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
//看第三步操作
ensureExplicitCapacity(minCapacity);
}
//第三步操作
private void ensureExplicitCapacity(int minCapacity) {
modCount++;

//判断minCapacity是否大于初始的元素空间了
// 若大于,则需要扩容
if (minCapacity - elementData.length > 0)
//继续看如何扩容的操作吗?
grow(minCapacity);
}
//第四步操作
private void grow(int minCapacity) {
//获取原有的数组空间大小
int oldCapacity = elementData.length;
//容量在原有空间的基础上在增加1/2容量
//如oldCapacity=10,此时的newCapacity=10+5=15
int newCapacity = oldCapacity + (oldCapacity >> 1);
//拿增加后的容量和minCapacity进行对比,获取最大的容量
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//数组空间扩容
elementData = Arrays.copyOf(elementData, newCapacity);
}
//第五步操作,很简单,这里就不分析了
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0)
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}

经过上面的层层分析之后,我想你已经明白了,添加一个元素到集合里面,要做哪些操作了,由于数组的空间默认是有限的,即分配了就不可以改变了,所以需要在程序里面动态去判断,这里或许你已经明白为什么这么做了,那么我们继续往后分析咯。

2.3,size()方法

一般,我自己分析源码的时候,都是先从add()方法分析之后,就会一连串的分析一下size()方法和isEmpty()方法,因为这两个方法简单,且比较容易理解和记忆。

//此时的成员变量size就是表示集合元素的个数的,获取集合元素个数直接返回size即可
public int size() {
return size;
}

2.4,isEmpty()方法

//若集合里面没有元素,此时的成员变量size就等于0,默认size就是0
public boolean isEmpty() {
return size == 0;
}

2.5,contains()方法

public boolean contains(Object o) {
//判断集合里面是否含有元素o,看第二步操作
return indexOf(o) >= 0;
}
//第二步操作
public int indexOf(Object o) {
if (o == null) {
//循环遍历集合的元素,一一比对是否存在集合的元素是否等于元素0,
//若相等则返回对应元素的下标
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}

由于集合里面是既可以装非null元素,也可以装填null元素的,所以这里要区分查询,时间复杂度为O(n),这里是重点,需要重点理解一下,回过头来,看看是不是和你开发的应用编写逻辑是否很像是吧。

2.6,indexOf()方法

public int indexOf(Object o) {        if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}

这个方法,上面已经分析过了,这里就不分析了,避免重复性文字说明,这也是代码复用的好处,显而易见的,这个方法是为了获取元素的索引下标的。

2.7,lastIndexOf()方法

public int lastIndexOf(Object o) {
if (o == null) {
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
for (int i = size-1; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}

这个方法就是从后向前查找元素出现在集合的下标位置,找到就立刻返回了,此时的时间复杂度为O(n),n就是问题大小的规模

2.8,get()方法

public E get(int index) {
//预检查机制
rangeCheck(index);
return elementData(index);
}
//第二步操作
private void rangeCheck(int index) {
//index不可能大于等于size大小的,因为索引下标是从0开始的
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
//构建一个异常提示信息
private String outOfBoundsMsg(int index) {
return "Index: "+index+", Size: "+size;
}

这个方法就是为了获取指定索引下标的元素的,首先要先检查索引位置是否合法,不符合时,直接抛出索引非法异常的问题,由于数组可以基于索引下标快速找到元素,获取元素的时间复杂度为o(1)了

2.9,remove()方法

public E remove(int index) {
//先检查索引位置是否合法
rangeCheck(index);

modCount++;
//获取待删除的索引位置的元素
E oldValue = elementData(index);
//获取需要移动元素的个数
int numMoved = size - index - 1;
//数组元素的移动操作,这里涉及到c语言的底层实现,因为调用了native关键字修饰的方法
//所以在代码层面就不过多分析,就是数组元素的移动操作
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//这个注释已经很好的说明了,为啥要将删除后的元素置为null,
//不是很了解的可以看下java虚拟机相关的内容哈
//在之前的源码分析过程中,已经多次提到了将元素置为null的原因了
//这里自己就不再解释了
//--size 集合的元素个数减一
elementData[--size] = null; // clear to let GC do its work

return oldValue;
}

2.10,clear()方法

public void clear() {
modCount++;
//循环将集合的元素置为null,等待gc机制在某个时刻触发,将不可达对象进行回收处理
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;

size = 0;
}

三,源码分析总结

3.1,总结一下

关于ArrayList集合源码的常用方法,自己到这里就分析完了,或许你有发现我基本上都是分析大部分的方法,对于那些很少用到的方法,自己不会过多分析,知道有这么个方法就可以,比如说取交集的retainAll(),循环遍历元素的foreach()方法,我觉得还是以实用为主,不太可能每个方法都要去分析的。

此时这里给个个人的小建议吧,不会或者不了解java8操作的可以去了解下,因为太常用了,我这里也不是说会有"教会徒弟,饿死师傅"的心态,我一直觉得,互联网下,不会像传统手艺人那样存在这样的一种现象,如果别人不告诉你,你也可以通过自己的搜索,慢慢学到一点东西的,不过是时间问题而已,所以你应该感谢那些无私分享自己心得,避免你多走弯路的前辈们,因为他们,所以你少了很多困难,这也是互联网所倡导的开源的好处,所以,我现在分享自己的一点内容,不是说我多好,而是我曾经也受惠了哪些帮助过的人,这里觉得自己也想帮助自己的同时,帮助到别人是在好不过的一件事情了,所以这里多说了一点,喜欢我的内容,不妨转发分享一下吧。


以上是关于周末回家写的ArrayList源码分析的主要内容,如果未能解决你的问题,请参考以下文章

ArrayList源码分析

关于ArrayList的一些源码分析

ArrayList源码分析超详细

JAVA——底层源码阅读——集合ArrayList的实现底层源码分析

ArrayList源码和多线程安全问题分析

Android 插件化VirtualApp 源码分析 ( 目前的 API 现状 | 安装应用源码分析 | 安装按钮执行的操作 | 返回到 HomeActivity 执行的操作 )(代码片段