Java集合之ArrayList源码分析
1.简介
List在数据结构中表现为是线性表的方式,其元素以线性方式存储,集合中允许存放重复的对象,List接口主要的实现类有ArrayList和LinkedList。Java中分别提供了这两种结构的实现,这一篇文章是要熟悉下ArrayList的源码实现。ArrayList其实就是一组长度可变的数组,当实例化了一个ArrayList,该数据也被实例化了,当向集合中添加对象时,数组的大小也随着改变,这样它所带来的有优点是快速的随机访问,即使访问每个元素所带来的性能问题也是很小的,但缺点就是想其中添加或删除对象速度慢,当你创建的数组是不确定其容量,所以当我们改变这个数组时就必须在内存中做很多的处理,如你想要数组中任意两个元素中间添加对象,那么在内存中数组要移动所有后面的对象。使用示例如下:
package com.test.collections; import java.util.ArrayList; public class ArrayListTest { /**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
ArrayList<String> arrayList = new ArrayList<String>();
arrayList.add("A");
arrayList.add("B");
arrayList.add("C");
arrayList.add("D");
arrayList.add("E");
System.out.println(arrayList.add("F"));
System.out.println(arrayList.contains("A"));
System.out.println(arrayList.indexOf("E"));
System.out.println(arrayList.contains("A"));
System.out.println(arrayList.clone());
System.out.println(arrayList.lastIndexOf("D"));
System.out.println(arrayList.remove(1));
System.out.println(arrayList.remove("A"));
arrayList.trimToSize();
arrayList.add(1, "N");
arrayList.clear(); } }
2.继承结构
ArrayList继承了AbstractList<E>,实现了List<E>,RandomAccess, Cloneable, Serializable这几个接口,在ArrayList的内部还有私有化的内部类Itr继承实现了Iterator<E>接口,ListItr继承了ArrayList<E>.Itr 并且实现了 ListIterator<E>接口。可以使用迭代器的相关方法。
3.源码分析
a:相关属性
通过源码可以发现ArrayList实现类里面含有的属性有private static final int DEFAULT_CAPACITY = 10;private static final Object[] EMPTY_ELEMENTDATA = new Object[0];private transient Object[] elementData;private int size;private static final int MAX_ARRAY_SIZE = 2147483639;
DEFAULT_CAPACITY :缺省默认的初始化数组大小
EMPTY_ELEMENTDATA :创建一个空的初始化数组
elementData:瞬时数组记录存放数组列表的元素(transient 关键字标识该属性能和对象一起进行串行序列化)
size :标识记录List的大小
MAX_ARRAY_SIZE :数组允许的最大元素数量
b:构造方法
public ArrayList(int paramInt) {
if (paramInt < 0)
throw new IllegalArgumentException("Illegal Capacity: " + paramInt);
this.elementData = new Object[paramInt];
} public ArrayList() {
this.elementData = EMPTY_ELEMENTDATA;
} public ArrayList(Collection<? extends E> paramCollection)
{
this.elementData = paramCollection.toArray();
this.size = this.elementData.length;
if (this.elementData.getClass() == [Ljava.lang.Object.class)
return;
this.elementData = Arrays.copyOf(this.elementData, this.size, [Ljava.lang.Object.class);
}
通过源码我们可以发现AarrayList提供了三种类型的构造函数:无参的构造函数ArrayList()是创建了一个空的ArrayList;带有一个整形参数的构造函数ArrayList(int) 等于了初始化了一个固定容量大小的ArrayList;提供了含有稽核参数的构造函数ArrayList(Collection)是通过将将该集合中的数据通过复制到新的ArrayList中并且将它赋值给elementData;
c:trimeToSize()
public void trimToSize() {
this.modCount += 1;
if (this.size >= this.elementData.length)
return;
this.elementData = Arrays.copyOf(this.elementData, this.size);
}
通过源码可以发现整个方法的作用就是去除了空的未被利用的null空间。作用只是去掉预留元素位置而已,而之后的元素size是这个ArrayList存放的最小size。
d:inexOF(Object)和lastIndexOf(Object)
public int indexOf(Object paramObject) {
int i;
if (paramObject == null)
for (i = 0; i < this.size; ++i)
if (this.elementData[i] == null)
return i;
else
for (i = 0; i < this.size; ++i)
if (paramObject.equals(this.elementData[i]))
return i;
return -1;
} public int lastIndexOf(Object paramObject) {
int i;
if (paramObject == null)
for (i = this.size - 1; i >= 0; --i)
if (this.elementData[i] == null)
return i;
else
for (i = this.size - 1; i >= 0; --i)
if (paramObject.equals(this.elementData[i]))
return i;
return -1;
}
之所以把这两个方法放到一起分析是因为这两个方法在功能和实现上有很多类似的地方,indexOf()返回元素所在的下标位置,从第一个元素开始往后遍历,而lastIndexOf()方法则是相反的,则是最列表的最后一个元素开始遍历的 然后返回距离最后一个元素的位置。
以indexOf(Object)方法为例,通过源代码可以看到它的实现是这样的:如果传入的参数对象不为空的话,那么就开始从头到尾开始遍历这个List对象,如果所遍历到的元素对象不为空的话,那么就与传入的对象进行比较,如果比较的值相等那么就返回改比较的下标位置,如果没有找到这个元素就返回-1;
e:get(int)
public E get(int paramInt) {
rangeCheck(paramInt);
return elementData(paramInt);
} E elementData(int paramInt) {
return this.elementData[paramInt];
}
private void rangeCheck(int paramInt) {
if (paramInt < this.size)
return;
throw new IndexOutOfBoundsException(outOfBoundsMsg(paramInt));
}
这个方法是根据索引下标返回对应位置的List中的值;它首先通过rangCheck(int)这个方法判断传入的参数是否合法,然后通过elementData(int)方法返回它的值;但是我们可以发现它其实是根据数组下标直接拿到对应的值的。
f:clear()
public void clear() {
this.modCount += 1;
for (int i = 0; i < this.size; ++i)
this.elementData[i] = null;
this.size = 0;
}
clear()顾名思义是把列表清空,它的实现逻辑很简单就是通过遍历然后将每个位置上的值设为null,并且把列表的长度设置为0;
g:删除元素
public E remove(int paramInt) {
rangeCheck(paramInt);
this.modCount += 1;
Object localObject = elementData(paramInt);
int i = this.size - paramInt - 1;
if (i > 0)
System.arraycopy(this.elementData, paramInt + 1, this.elementData,
paramInt, i);
this.elementData[(--this.size)] = null;
return localObject;
} public boolean remove(Object paramObject) {
int i;
if (paramObject == null)
for (i = 0; i < this.size; ++i) {
if (this.elementData[i] != null)
continue;
fastRemove(i);
return true;
}
else
for (i = 0; i < this.size; ++i) {
if (!(paramObject.equals(this.elementData[i])))
continue;
fastRemove(i);
return true;
}
return false;
} private void fastRemove(int paramInt) {
this.modCount += 1;
int i = this.size - paramInt - 1;
if (i > 0)
System.arraycopy(this.elementData, paramInt + 1, this.elementData,
paramInt, i);
this.elementData[(--this.size)] = null;
}
第一个方法remove(int)是通过下标位置删除元素的,首先是判断参数是否合法,然后获取这个下标位置的元素值,通过复制将每个元素的位置后移一下,最后将该原列表的最后面元素置为null,返回被删除的对象;第二个方法是remove(Object)直接删除对象,从第一个元素开始遍历,如果遍历到了就删除返回true,如果元素不存在就返回false,第三个方法是将删除元素的方法独立了根据下标删除对象元素。
h:添加元素
public boolean add(E paramE) {
ensureCapacityInternal(this.size + 1);
this.elementData[(this.size++)] = paramE;
return true;
} public void add(int paramInt, E paramE) {
rangeCheckForAdd(paramInt);
ensureCapacityInternal(this.size + 1);
System.arraycopy(this.elementData, paramInt, this.elementData,
paramInt + 1, this.size - paramInt);
this.elementData[paramInt] = paramE;
this.size += 1;
}
public boolean addAll(Collection<? extends E> paramCollection) {
Object[] arrayOfObject = paramCollection.toArray();
int i = arrayOfObject.length;
ensureCapacityInternal(this.size + i);
System.arraycopy(arrayOfObject, 0, this.elementData, this.size, i);
this.size += i;
return (i != 0);
} public boolean addAll(int paramInt, Collection<? extends E> paramCollection) {
rangeCheckForAdd(paramInt);
Object[] arrayOfObject = paramCollection.toArray();
int i = arrayOfObject.length;
ensureCapacityInternal(this.size + i);
int j = this.size - paramInt;
if (j > 0)
System.arraycopy(this.elementData, paramInt, this.elementData,
paramInt + i, j);
System.arraycopy(arrayOfObject, 0, this.elementData, paramInt, i);
this.size += i;
return (i != 0);
}
第一个和第二个方式添加一个元素,第三个和第四个方法是添加一个集合到列表中去;第一个方法是首先检查列表的大小是否合乎要求,然后直接在列表的末尾添加上元素。第二个方式在制定的位置添加上元素对象,它除了检查下标位置还有是列表的容量增加之外,就是将每个元素的位置后移,然后把空出来的位置留给我们需要插入的对象。第三个方法和第四个方法实现和第一个第二个类型,只不过把一个元素变成了一个集合。
i:contains(Object)
public boolean contains(Object paramObject) {
return (indexOf(paramObject) >= 0);
}
该方法是看是否含有某一元素,首先获取这个元素在列表中的位置,如果位置存在就说么含有该元素。
4.其他(小结)
ArrayList是在数组的基础了增加了一些我们开发中经常用到了方法,可是有些方法的效率也不怎么高,我们可以在具体的开发过程中视情况而定是否需要重写。不过它提供了大量的方法基本上够我们使用了。只是通过源码的解析了解一些它的底层方法的实现。
Java集合之ArrayList源码分析的更多相关文章
- Java集合干货——ArrayList源码分析
ArrayList源码分析 前言 在之前的文章中我们提到过ArrayList,ArrayList可以说是每一个学java的人使用最多最熟练的集合了,但是知其然不知其所以然.关于ArrayList的具体 ...
- 【Java集合】ArrayList源码分析
ArrayList是日常开发中经常使用到的集合,其底层采用数组实现,因此元素按序存放.其优点是可以使用下标来访问元素,时间复杂度是O(1).其缺点是删除和增加操作需要使用System.arraycop ...
- 死磕 java集合之ArrayList源码分析
欢迎关注我的公众号"彤哥读源码",查看更多源码系列文章, 与彤哥一起畅游源码的海洋. 简介 ArrayList是一种以数组实现的List,与数组相比,它具有动态扩展的能力,因此也可 ...
- 死磕 java集合之CopyOnWriteArraySet源码分析——内含巧妙设计
问题 (1)CopyOnWriteArraySet是用Map实现的吗? (2)CopyOnWriteArraySet是有序的吗? (3)CopyOnWriteArraySet是并发安全的吗? (4)C ...
- 死磕 java集合之LinkedList源码分析
问题 (1)LinkedList只是一个List吗? (2)LinkedList还有其它什么特性吗? (3)LinkedList为啥经常拿出来跟ArrayList比较? (4)我为什么把LinkedL ...
- 死磕 java集合之DelayQueue源码分析
问题 (1)DelayQueue是阻塞队列吗? (2)DelayQueue的实现方式? (3)DelayQueue主要用于什么场景? 简介 DelayQueue是java并发包下的延时阻塞队列,常用于 ...
- 死磕 java集合之PriorityBlockingQueue源码分析
问题 (1)PriorityBlockingQueue的实现方式? (2)PriorityBlockingQueue是否需要扩容? (3)PriorityBlockingQueue是怎么控制并发安全的 ...
- 死磕 java集合之PriorityQueue源码分析
问题 (1)什么是优先级队列? (2)怎么实现一个优先级队列? (3)PriorityQueue是线程安全的吗? (4)PriorityQueue就有序的吗? 简介 优先级队列,是0个或多个元素的集合 ...
- 死磕 java集合之LinkedHashSet源码分析
问题 (1)LinkedHashSet的底层使用什么存储元素? (2)LinkedHashSet与HashSet有什么不同? (3)LinkedHashSet是有序的吗? (4)LinkedHashS ...
随机推荐
- 关于int全区变量读写的原子性
关于int全区变量读写的原子性 关于int变量的读写是否原子性网上有非常多讨论,貌似不同平台不同,这里自己做实如今arm9平台測试.这里要注意原子性并不是指一条汇编才原子,实际上即使一次赋值编 ...
- 【Linux命令】--(9)其他常用命令
其他常用命令+++++++++++++++++++++++++++++++lndiffdatecal grep wcpswatchatcrontab++++++++++++++++++++++++++ ...
- extjs_09_定义自己的页面组件
1.项目截图 watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvYWRhbV93enM=/font/5a6L5L2T/fontsize/400/fill/I0J ...
- Android Notification (转)
上回我们提到在4.0ICS之后,Google为Android平台的Notification这把“倚天剑”注入了更多新鲜的元素,使其更加实用美观.Notification的样式从此变得丰富起来,以适应于 ...
- msys2 安装注意事项
它一直在使用 msys.有一个最近发现 msys2.而且msys2 配套的编译器是MinGW-w64. 就试着用了用,感觉还不错,这里把安装过程记录一下. 简单的说,MSYS2 是MSYS的一个升级版 ...
- 在ASP.NET 5应用程序中的跨域请求功能详解
在ASP.NET 5应用程序中的跨域请求功能详解 浏览器安全阻止了一个网页中向另外一个域提交请求,这个限制叫做同域策咯(same-origin policy),这组织了一个恶意网站从另外一个网站读取敏 ...
- [2013山东ACM]省赛 The number of steps (可能DP,数学期望)
The number of steps nid=24#time" style="padding-bottom:0px; margin:0px; padding-left:0px; ...
- Node.js v0.10.31API手工-DNS
原版的API品种,这是从以前的翻译和翻译风格不同 Node.js v0.10.31API手冊-文件夹 DNS 使用 require('dns') 引入此模块. dns 模块中的全部方法都使用了 C-A ...
- strncpy和strcpy
实体化代码运行图: 实现代码: #include <stdio.h> #include <string.h> #include <math.h> #include ...
- 使用PHP顶替JS有趣DOM
較简单,我须要把一个导航页的数据整理好写入数据库.一个比較直观的方法是对html文件进行分析.通用的方法是用php的正則表達式来匹配.可是这样做开发和维护都非常困难,代码可读性非常差. 导航页的数据都 ...