Android版数据结构与算法(二):基于数组的实现ArrayList源码彻底分析
版权声明:本文出自汪磊的博客,未经作者允许禁止转载。
本片我们分析基础数组的实现--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源码彻底分析的更多相关文章
- Android版数据结构与算法(三):基于链表的实现LinkedList源码彻底分析
版权声明:本文出自汪磊的博客,未经作者允许禁止转载. LinkedList 是一个双向链表.它可以被当作堆栈.队列或双端队列进行操作.LinkedList相对于ArrayList来说,添加,删除元素效 ...
- Android版数据结构与算法(四):基于哈希表实现HashMap核心源码彻底分析
版权声明:本文出自汪磊的博客,未经作者允许禁止转载. 存储键值对我们首先想到HashMap,它的底层基于哈希表,采用数组存储数据,使用链表来解决哈希碰撞,它是线程不安全的,并且存储的key只能有一个为 ...
- Java -- 基于JDK1.8的ArrayList源码分析
1,前言 很久没有写博客了,很想念大家,18年都快过完了,才开始写第一篇,争取后面每周写点,权当是记录,因为最近在看JDK的Collection,而且ArrayList源码这一块也经常被面试官问道,所 ...
- Android版数据结构与算法(五):LinkedHashMap核心源码彻底分析
版权声明:本文出自汪磊的博客,未经作者允许禁止转载. 上一篇基于哈希表实现HashMap核心源码彻底分析 分析了HashMap的源码,主要分析了扩容机制,如果感兴趣的可以去看看,扩容机制那几行最难懂的 ...
- Android版数据结构与算法(一):基础简介
版权声明:本文出自汪磊的博客,未经作者允许禁止转载. 一.前言 项目进入收尾阶段,忙忙碌碌将近一个多月吧,还好,不算太难,就是麻烦点. 数据结构与算法这个系列早就想写了,一是梳理总结,顺便逼迫自己把一 ...
- java版数据结构与算法第二章数组
数组由一组具有相同类型的数据元素组成,并存储在一组连续存储单元中.一维数组是常量. 二维数组:若一维数组中的数据元素又是一堆数据结构,我们称之为二维数组.二维数组可以看成是n个列向量组成的线性表. 数 ...
- Android版数据结构与算法(七):赫夫曼树
版权声明:本文出自汪磊的博客,未经作者允许禁止转载. 近期忙着新版本的开发,此外正在回顾C语言,大部分时间没放在数据结构与算法的整理上,所以更新有点慢了,不过既然写了就肯定尽力将这部分完全整理好分享出 ...
- Android版数据结构与算法(八):二叉排序树
本文目录 前两篇文章我们学习了一些树的基本概念以及常用操作,本篇我们了解一下二叉树的一种特殊形式:二叉排序树(Binary Sort Tree),又称二叉查找树(Binary Search Tree) ...
- Java集合基于JDK1.8的ArrayList源码分析
本篇分析ArrayList的源码,在分析之前先跟大家谈一谈数组.数组可能是我们最早接触到的数据结构之一,它是在内存中划分出一块连续的地址空间用来进行元素的存储,由于它直接操作内存,所以数组的性能要比集 ...
随机推荐
- DevOps之一 Gitlab的安装与配置
gitlab的安装 参考治疗:https://www.gitlab.com.cn/installation/#centos-7 http://www.21yunwei.com/archives/435 ...
- HTML学习笔记:2.基础语法
HTML基本结构 HTML标签 HTML元素 HTML属性 注释 ①基本结构 <html> html:指明是个html文件 <head> <title>标题< ...
- 【转】IE浏览器快捷键大全
一般快捷键F11打开/关闭全屏模式 TAB循环的选择地址栏,刷新键和当前标签页 CTRL+F在当前标签页查询字或短语 CTRL+N为当前标签页打开一个新窗口 CTRL+P打印当前标签页 CTRL+A选 ...
- Spring Data JPA 初体验
一,JPA相关的概念 JPA概述 全称是:JavaPersistence API.是SUN公司推出的一套基于ORM的规范. Hibernate框架中提供了JPA的实现. JPA通过JDK 5.0注解或 ...
- mysql SQL Layer各个模块介绍
https://blog.csdn.net/cymm_liu/article/details/45821929
- springboot之启动原理解析
前言 SpringBoot为我们做的自动配置,确实方便快捷,但是对于新手来说,如果不大懂SpringBoot内部启动原理,以后难免会吃亏.所以这次博主就跟你们一起一步步揭开SpringBoot的神秘面 ...
- 《Spring Cloud与Docker微服务架构实战》配套代码
不才写了本使用Spring Cloud玩转微服务架构的书,书名是<Spring Cloud与Docker微服务架构实战> - 周立,已于2017-01-12交稿.不少朋友想先看看源码,现将 ...
- c# xml操作(二)
c# xml操作(二) 此博文包含图片 (-- ::)转载▼ 标签: 杂谈 分类: c# 上次,我们介绍了增加和删除xml的一些操作,这次我们将介绍如何更改和读取xml特定节点.我们依然以上次的xml ...
- monkey操作
1.monkey命令格式 直接adb shell进入后输入monkey [options] <eventcount> 或者每次输入adb shell monkey [options] &l ...
- 怎样在Ubuntu中设置环境变量
首先启动终端. 单击屏幕左上角的Ubuntu图标,在弹出的窗口中点击搜索栏,输入"terminal", 稍等片刻,终端就会赫然在目!二话不说,直接点击! 然后打开环境设置文 ...