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 ...
随机推荐
- cocos2dx --- button点击放大中心
自定义简单button,直接附着到代码: MenuItem* MenuItemNode::create( const char* normal,Ref* target,SEL_MenuHandler ...
- ContentMode 几个属性
版权声明:本文博客原创文章,博客,未经同意,不得转载.
- java类和对象之间的差
java类和对象之间的差别是一个普遍的问题,刚开始学习java当它来到与类和对象接触.今天就来总结一下它们之间的差异. 先说说class和object差异.事实上,词:object是全部class的父 ...
- [SignalR]在非Hub继承类中使用脚本方法
原文:[SignalR]在非Hub继承类中使用脚本方法 新建一个普通类OutHub,里面包含一个脚本方法OutHubTest. 因为大家知道,若能让脚本调用到的话,必须继承Hub,那怎么实现了?通过G ...
- Eclipse Maven构建Spring MVC项目
工作中项目开发使用Maven管理项目的构建.打包.编译,框架採用的是Spring MVC框架,而且实现了多模块.多项目的管理.自己也简单的參与了架构的设计.对于刚開始学习的人来说,使用Maven构建项 ...
- ASP.NET Identity 身份验证和基于角色的授权
ASP.NET Identity 身份验证和基于角色的授权 阅读目录 探索身份验证与授权 使用ASP.NET Identity 身份验证 使用角色进行授权 初始化数据,Seeding 数据库 小结 在 ...
- 在InstallShield中发布单一的Setup.exe文件
原文:在InstallShield中发布单一的Setup.exe文件 InstallShield默认的Release模式下,生成了一个setup.exe文件,一个msi文件,以及一些其它的杂项文件. ...
- Java并发编程:线程池的使用(转)
Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了, ...
- 减小Cookie体积
HTTP coockie能够用于权限验证和个性化身份等多种用途.coockie内的有关信息是通过HTTP文件头来在webserver和浏览器之间进行交流的.因此保持coockie尽可能的小以降低用户的 ...
- UOJ #5. 【NOI2014】动物园 扩大KMP
第一次NOI称号. ... 扩展假设知道KMP如果. .. . 就是水题了. ... #5. [NOI2014]动物园 统计提交情况 描写叙述 提交 近日.园长发现动物园中好吃懒做的动物越来越多了.比 ...