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的源码,在分析之前先跟大家谈一谈数组.数组可能是我们最早接触到的数据结构之一,它是在内存中划分出一块连续的地址空间用来进行元素的存储,由于它直接操作内存,所以数组的性能要比集 ...
随机推荐
- CSS学习笔记3:选择器及优先级
CSS选择器的类型: 标签选择器 类选择器 ID选择器 全局选择器 群组选择器 后代选择器 1.标签选择器: 以HTML的标签作为选择器,凡是选择了一个标签,那么所有这个标签的内容都是用了 ...
- Java中的String类型
1.基本类型和引用类型 在C语言里面,是有指针这么一个变量类型的,指针变量保存的就是所要指向内容的地址.在Java里面,没有了指针的这么个说法,而是换了一个词:引用类型变量. 先说Java里面的基本类 ...
- DX11 Without DirectX SDK--06 DirectXMath数学库
回到 DirectX11--使用Windows SDK来进行开发 xnamath.h原本是位于DirectX SDK的一个数学库,但是现在Windows SDK包含的数学库已经抛弃掉原来的xnamat ...
- Mysql主从方案的实现
Mysql主从方案介绍 mysql主从方案主要作用: 读写分离,使数据库能支撑更大的并发.在报表中尤其重要.由于部分报表sql语句非常的慢,导致锁表,影响前台服务.如果前台使用master,报表使用s ...
- capwap学习笔记——初识capwap(一)(转)
初识CAPWAP 2.1 CAPWAP简介 CAPWAP——Control And Provisioning of Wireless Access Points Protocol Specificat ...
- SSM-MyBatis-17:Mybatis中一级缓存(主要是一级缓存存在性的证明,增删改对一级缓存会造成什么影响)
------------吾亦无他,唯手熟尔,谦卑若愚,好学若饥------------- 缓存------------------------------------------> 很熟悉的一个 ...
- no system images installed for this target这个问题如何解决?
今天想查看个项目的布局,结果发现这个Hierarchy这个工具没有提供对实体机的支持,所以就想加一个模拟机,结果还曝出了这么么一个错误,导致不能设置模拟机: 在网上的查找下,发现原来是缺失了 圈中的是 ...
- 安装mysql5.5.28的步骤 2017.6.27
http://blog.sina.com.cn/s/blog_7cd69a6501014x7h.html
- 玩转spring mvc(六)---自定义异常跳转页面
本文主要是关于如何在出现异常 如404时,跳转到自定义的异常页面,当然这不是spring的知识,但可以整合进去. 在web.xml中新增如下代码,里边的路径可以根据实际情况进行修改 <!-- 7 ...
- python读文件的三个方法read()、readline()、readlines()详解
文件 runoob.txt 的内容如下: 1:www.runoob.com2:www.runoob.com3:www.runoob.com4:www.runoob.com5:www.runoob.co ...