版权声明:本文出自汪磊的博客,未经作者允许禁止转载。

本片我们分析基础数组的实现--ArrayList,不会分析整个集合的继承体系,这不是本系列文章重点

源码分析都是基于"安卓版"的源码,和java原生版核心思想都是差不多的。好了,废话依然少说,进入正文。

一、ArrayList中成员变量

源码:

     /**
* The minimum amount by which the capacity of an ArrayList will increase.
* This tuning parameter controls a time-space tradeoff. This value (12)
* gives empirically good results and is arguably consistent with the
* RI's specified default initial capacity of 10: instead of 10, we start
* with 0 (sans allocation) and jump to 12.
*/
private static final int MIN_CAPACITY_INCREMENT = 12; /**
* The number of elements in this list.
*/
int size; /**
* The elements in this list, followed by nulls.
*/
transient Object[] array;

第8行MIN_CAPACITY_INCREMENT的作用通过注释就大体可以知道了,主要就是用来控制内部维护的数组每次扩容时的大小,后面分析具体方法时会多次看到其身影。

第13行size就是内部维护的数组中元素的个数了,没什么多余解释的。

第18行array就是内部盛放数据的数组了,我们加入的数据就放到这里。

二、ArrayList构造方法与初始化

初始化方法有三个,源码如下:

 /**
* Constructs a new {@code ArrayList} instance with zero initial capacity.
*/
public ArrayList() {
array = EmptyArray.OBJECT;
} /**
* Constructs a new instance of {@code ArrayList} with the specified
* initial capacity.
*
* @param capacity
* the initial capacity of this {@code ArrayList}.
*/
public ArrayList(int capacity) {
if (capacity < 0) {
throw new IllegalArgumentException("capacity < 0: " + capacity);
}
array = (capacity == 0 ? EmptyArray.OBJECT : new Object[capacity]);
} /**
* Constructs a new instance of {@code ArrayList} containing the elements of
* the specified collection.
*
* @param collection
* the collection of elements to add.
*/
public ArrayList(Collection<? extends E> collection) {
if (collection == null) {
throw new NullPointerException("collection == null");
} Object[] a = collection.toArray();
if (a.getClass() != Object[].class) {
Object[] newArray = new Object[a.length];
System.arraycopy(a, 0, newArray, 0, a.length);
a = newArray;
}
array = a;
size = a.length;
}

4-6行,调用空参数的构造方法就是初始化一个空的array数组。

15-20行,初始化的时候我们可以指定一个大小的数值,如果小于0则会抛出异常,为0就和调用空参数构造方法一样了,大于0,则初始化一个对应大小的array数组。调用此方法我们可以初始化的时候就指定array数组的大小,如果我们事先知道放入元素个数,那么可以调用此方法初始化的时候就指定好内部array数组大小,省去后续不断放入元素扩容的操作。

29-42行,初始化的时候我们同样也可以传入一个集合:

30-32传入集合为null,则报空指针异常。

34行,集合collection转为Object数组。

35行,判断a的字节码类型是否为Object[].class类型,这里注意toArray()方法实际返回的不一定是Object[]类型数组,具体返回由具体的Collcetion子类自己去实现toArray()方法。这里只是用Object[]类型数组接收toArray()返回的数据。

如果返回的字节码类型不是Object[]则进入36-38行逻辑

36行,根据集合大小创建一个同样大小的Object[]类型newArray数组。

37行,就是数组的拷贝了,调用System.arraycopy方法将数组a从0位置开始,拷贝到newArray数组位置同样从0开始,并且拷贝数据长度为a.length。System.arraycopy方法一定要弄明白,后续主要用这个方法对数组进行操作。

经过37行,collection中数据就都拷贝到数组newArray中了。

38行,newArray赋值给数组a。

40-41行,a赋值给array变量,size变量记录数据大小。

以上就是三种创建ArrayList的方式,比较简单。继续往下看

三、ArrayList中add方法源码分析

添加操作主要有四个方法供我们使用,如下:

public boolean add(E object)//添加一个元素

public void add(int index, E object)//向指定位置添加一个元素

public boolean addAll(Collection<? extends E> collection)//添加一个集合

public boolean addAll(int index, Collection<? extends E> collection)//向指定位置添加一个集合

接下来,逐个分析

add(E object)源码:

     public boolean add(E object) {
Object[] a = array;
int s = size;
if (s == a.length) {
Object[] newArray = new Object[s +
(s < (MIN_CAPACITY_INCREMENT / 2) ?
MIN_CAPACITY_INCREMENT : s >> 1)];
System.arraycopy(a, 0, newArray, 0, s);
array = a = newArray;
}
a[s] = object;
size = s + 1;
modCount++;
return true;
}

这里最核心的就是有个扩容操作,加入元素的时候如果容器已满则进行5-7行扩容逻辑。

扩容方式为:若容量小于6则增加12,否则1.5倍扩容。

关于扩容这里有两种策略,时间换空间和空间换时间,比如我可以一次扩容很大,这样不断加入元素的时候就省去每次扩容所花费的时间了,同样,也可以每次就扩容那么多,放入一个元素我就扩容1,放入10个元素我就扩容10,这样节省了空间但是每次都要扩容从而牺牲了时间。而谷歌工程师在这里考虑了时间空间的平衡,既不扩容很大也照顾扩容次数,个人感觉这里没有一个完美解决方案,个设计者个人喜好也有一定关系。

8行,又是数组的拷贝了,原来数组元素全部拷贝到新数组newArray。

11-12行,元素放入数组以及元素个数加一,没什么要解释的。

13行,记录操作次数。

add(int index, E object)源码:

     public void add(int index, E object) {
Object[] a = array;
int s = size;
if (index > s || index < 0) {
throwIndexOutOfBoundsException(index, s);
} if (s < a.length) {
System.arraycopy(a, index, a, index + 1, s - index);
} else {
// assert s == a.length;
Object[] newArray = new Object[newCapacity(s)];
System.arraycopy(a, 0, newArray, 0, index);
System.arraycopy(a, index, newArray, index + 1, s - index);
array = a = newArray;
}
a[index] = object;
size = s + 1;
modCount++;
}

8行,首先判断数组中元素个数是否小于数组长度,其实就是判断数组是否已经填满,没填满,则代表可以直接放入数据,不容扩容。

9行,同样是数组的拷贝,这里拷贝需要自己小小的计算一下了,其实就是将index开始的元素拷贝到index+1开始的位置,把index位置空出来。

如果已经填满数据,则就需要扩容操作了。

12行,创建一个新数组,数组大小为newCapacity(s),自己看一下就可以了,和上面扩容逻辑一样。

13-14行同样是数组的拷贝,将原数组数据拷贝到新数组,同样新数组的index位置会空出来。

17行在index位置上加入新数据。

其余就没什么好解释的了,如果以上两个方法完全明白操作套路,那么其余两个add方法就很容易理解了,这里就不一一分析了。

通过上面分析可以看出添加元素到指定位置需要将其后所有元素都要向后移动一位,所以ArrayList向指定位置添加元素还是比较消耗性能的。

四、ArrayList中remove方法源码分析

删除操作主要有以下两个方法:

remove(int index) //根据索引删除

remove(Object object)//根据元素删除

remove(int index)源码:

     public E remove(int index) {
Object[] a = array;
int s = size;
if (index >= s) {
throwIndexOutOfBoundsException(index, s);
}
E result = (E) a[index];
System.arraycopy(a, index + 1, a, index, --s - index);
a[s] = null; // Prevent memory leak
size = s;
modCount++;
return result;
}

最核心就是8-9行代码了

8行,同样也是数组的拷贝操作,从将要删除元素索引index开始将之后元素全部向前移动一位,也就是将index位置元素覆盖掉了。

9行,防止内存泄露,将最后一个元素置null。

其余就没什么好解释得了,关键是第8行一定要理解。

remove(Object object)与remove(int index)主要区别就是有个判断object是否为null操作,其余就基本都差不多了,就不再分析了。

同样,根据上面分析我们可以看出对于删除某一个元素其后所有元素都要向前移动一位,也是比较消耗性能

五、ArrayList中contains方法源码分析

接下来我们看下ArrayList是怎么判断是否包含某一元素的。

contains(Object object)源码如下:

     public boolean contains(Object object) {
Object[] a = array;
int s = size;
if (object != null) {
for (int i = 0; i < s; i++) {
if (object.equals(a[i])) {
return true;
}
}
} else {
for (int i = 0; i < s; i++) {
if (a[i] == null) {
return true;
}
}
}
return false;
}

比较简单,无论object是否为null,都需要遍历整个数组比较,所以效率是比较低的。

六、总结

以上就是ArrayList主要方法的分析,整体下来感觉和数组差不多,其优势就是数据的随机访问,对于指定位置插入数据以及删除数据性能都比较"费劲",在初始化完成后如果我们想插入大量数据可以调用ensureCapacity(int minimumCapacity)来提前对ArrayList进行扩容,而不是在不断插入数据的时候自身不断扩容,如果是数据频繁的增加删除LinkedList则是最佳选择。

好了,本片到此结束,希望对你有用。

声明:文章将会陆续搬迁到个人公众号,以后文章也会第一时间发布到个人公众号,及时获取文章内容请关注公众号

Android版数据结构与算法(二):基于数组的实现ArrayList源码彻底分析的更多相关文章

  1. Android版数据结构与算法(三):基于链表的实现LinkedList源码彻底分析

    版权声明:本文出自汪磊的博客,未经作者允许禁止转载. LinkedList 是一个双向链表.它可以被当作堆栈.队列或双端队列进行操作.LinkedList相对于ArrayList来说,添加,删除元素效 ...

  2. Android版数据结构与算法(四):基于哈希表实现HashMap核心源码彻底分析

    版权声明:本文出自汪磊的博客,未经作者允许禁止转载. 存储键值对我们首先想到HashMap,它的底层基于哈希表,采用数组存储数据,使用链表来解决哈希碰撞,它是线程不安全的,并且存储的key只能有一个为 ...

  3. Java -- 基于JDK1.8的ArrayList源码分析

    1,前言 很久没有写博客了,很想念大家,18年都快过完了,才开始写第一篇,争取后面每周写点,权当是记录,因为最近在看JDK的Collection,而且ArrayList源码这一块也经常被面试官问道,所 ...

  4. Android版数据结构与算法(五):LinkedHashMap核心源码彻底分析

    版权声明:本文出自汪磊的博客,未经作者允许禁止转载. 上一篇基于哈希表实现HashMap核心源码彻底分析 分析了HashMap的源码,主要分析了扩容机制,如果感兴趣的可以去看看,扩容机制那几行最难懂的 ...

  5. Android版数据结构与算法(一):基础简介

    版权声明:本文出自汪磊的博客,未经作者允许禁止转载. 一.前言 项目进入收尾阶段,忙忙碌碌将近一个多月吧,还好,不算太难,就是麻烦点. 数据结构与算法这个系列早就想写了,一是梳理总结,顺便逼迫自己把一 ...

  6. java版数据结构与算法第二章数组

    数组由一组具有相同类型的数据元素组成,并存储在一组连续存储单元中.一维数组是常量. 二维数组:若一维数组中的数据元素又是一堆数据结构,我们称之为二维数组.二维数组可以看成是n个列向量组成的线性表. 数 ...

  7. Android版数据结构与算法(七):赫夫曼树

    版权声明:本文出自汪磊的博客,未经作者允许禁止转载. 近期忙着新版本的开发,此外正在回顾C语言,大部分时间没放在数据结构与算法的整理上,所以更新有点慢了,不过既然写了就肯定尽力将这部分完全整理好分享出 ...

  8. Android版数据结构与算法(八):二叉排序树

    本文目录 前两篇文章我们学习了一些树的基本概念以及常用操作,本篇我们了解一下二叉树的一种特殊形式:二叉排序树(Binary Sort Tree),又称二叉查找树(Binary Search Tree) ...

  9. Java集合基于JDK1.8的ArrayList源码分析

    本篇分析ArrayList的源码,在分析之前先跟大家谈一谈数组.数组可能是我们最早接触到的数据结构之一,它是在内存中划分出一块连续的地址空间用来进行元素的存储,由于它直接操作内存,所以数组的性能要比集 ...

随机推荐

  1. Django REST framework+Vue 打造生鲜超市(七)

    目录 生鲜超市(一)    生鲜超市(二)    生鲜超市(三) 生鲜超市(四)    生鲜超市(五)    生鲜超市(六) 生鲜超市(七)    生鲜超市(八)    生鲜超市(九) 生鲜超市(十) ...

  2. Java面试官最常问的volatile关键字

    在Java相关的职位面试中,很多Java面试官都喜欢考察应聘者对Java并发的了解程度,以volatile关键字为切入点,往往会问到底,Java内存模型(JMM)和Java并发编程的一些特点都会被牵扯 ...

  3. Mego(05) - Mego Tools使用教程

    前言 使用过EntityFramework6的朋友应该都知道EF中的PowerTools这个工具可以帮助初学者或者开发人员快速构建一个EF的数据上下文,并且可以很直观的看到实体之间的关系.不过目前升级 ...

  4. laravel5.5 延时队列的使用

    队列这个知识相对比较冷门,因为平时的CURD基本用不到这个知识,今天用到了,所以就写个博客记录一下吧. 首先你得清楚要用什么驱动,除了database队列驱动(选择database驱动要php art ...

  5. Vue 实现网易云音乐 WebApp

  6. js基础进阶--编码实用技巧(二)

    我的个人博客:http://www.xiaolongwu.cn 接上篇文章 js编码的实用技巧(一) 5.合理利用||运算符 使用||可以作为参数之外的默认值,当第一个参数返回值为false时,那么第 ...

  7. Python高级教程

    关键字is 和 == 的区别 a = 'hello world' b = 'hello world' a == b #返回True a is b #返回False 注意:is 判断是否是一个ID, = ...

  8. 我热爱Coding,但厌恶这个IT互联网

    在我十岁的时候,我觉得这个世界上没有什么东西能比我老爸的那台破电脑更好玩的了.后来,我成为一名Web开发爱好者.大学毕业之后,我在一个软件公司工作,那个时候赚得并不多.再后来,我成为一名正式的Web开 ...

  9. SQL*Loader FAQ

    SQL*Loader FAQ: Contents [hide]  1 What is SQL*Loader and what is it used for? 2 How does one use th ...

  10. composer安装以及更新问题,配置中国镜像源。

    配置国内镜像源 中国镜像源 https://pkg.phpcomposer.com/ composer 中文官网地址 http://www.phpcomposer.com/ 下载 Composer 安 ...