【java集合框架源码剖析系列】java源码剖析之ArrayList
注:博主java集合框架源码剖析系列的源码全部基于JDK1.8.0版本。
本博客将从源码角度带领大家学习关于ArrayList的知识。
一ArrayList类的定义:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
从上述代码可以看到:ArrayList继承自AbstractList同时实现了List,RandomAccess,Cloneable与Serializable接口
二ArrayList类一些重要属性:
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData;
private int size;
从这里可以看到ArrayList全部操作是基于Object[]数据结构的,即ArrayList底层是基于数组实现的,因此具备较好的随机访问的能力
三ArrayList内部实现原理:我们先来看一下ArrayList的构造器
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
可以看到当用一个集合作为参数去初始化一个ArrayList的时候,其内部处理过程与LinkedList非常类似,即先将集合转化为数组,然后将元素复制到自己内部定义的数组elementData数组中。返回的若不是Object[]将调用Arrays.copyOf方法将其转为Object[]。Arrays.copyOf代码如下:
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
四ArrayList一些重要的函数:
1add(E e)与add(int index,E element)
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
其中add(E e)表示在ArrayList的末尾添加一个元素,而add(int index,E element)表示在index处添加一个元素,其中为了防止当数组中的容量不够的情况,会调用ensureCapacityInternal(size + 1); 函数用来确保向数组中添加元素的时候数组大小始终足够。代码如下;
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
而在该函数中调用了ensureExplicitCapacity(int minCapacity)函数,我们来看一下其源码:
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)// 超出了数组可容纳的长度,则进行动态扩展,即调用grow函数
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);//将oldCapacity除以2在加上自己,即新的容量为原来的1.5倍
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
综上,当调用add()添加元素时,add方法先调用ensureCapacityInternal方法增加自身容量(注意此时仅仅是minCapicity的值加1,而数组内存还未扩容),本质上是通过grow()函数来完成的,在调用grow()函数时,先求出原数组的长度记为oldCapacity,然后将该值更新为oldCapacity+oldCapacity>>1即新的容量为原来容量的1.5倍.,将其记为newCapacity,如果扩容后的容量超出了数组可容纳的最大长度MAX_ARRAY_SIZE,则调用hugeCapacity()将其返回值 Integer.MAX_VALUE作为新的容量,然后调用系统的arraycopy函数将原数组的内容复制到新数组中返回。
即add()方法中存在扩容机制,当通过add()方法添加一个元素时,会先调用ensureCapacityInternal方法来确保数组内存始终足够,本质上是通过grow函数来完成的,在grow()函数中会将原数组的长度更改为其原来的1.5倍,如果更改后的数组容量比传入的minCapacity小,则将更改后的容量更新为minCapacity(该值是期望的数组的最小容量,如当已存在1个元素然后添加一个元素则minCapacity的值为2即能容纳添加进去的元素的最小容量,扩容后的值newCapacity在绝大多数情况下比它大,但可能比它小,因为上述扩容机制采用的是扩大为1.5倍,而采用的是整除,如1+1/2=1,而minCapacity=size+1=2,此时newCapacity<minCapacity)
2addAll(Collection<? extends E> c) 与addAll(int index, Collection<? extends E> c)
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
int numMoved = size - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
可以看到,addAll()与add()方法几乎完全相同,只不过存在一个将集合转化为数组的过程,这一点与LinkedList非常类似,然后接下来的操作与add操作完全相同,即
调用ensureCapacityInternal扩容,如果插入位置不是Arraylist的末尾,则调用系统的arraycopy函数将索引处及其后的元素后移一位在index处插入该元素。处理完毕后将临时数组a中的元素arraycopy到elementData中。
3Object[] toArray()
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}
public <T> T[] toArray(T[] a) {
if (a.length < size)
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
该函数的作用就是返回一个包含list中所有元素的数组,具体实现过程如下:
如果传入数组的长度length小于size,返回一个新的数组,大小为size,类型与传入数组相同;
否则将elementData的元素全部复制到传入的数组a中,并返回a;
若length大于size,除了复制elementData外,还将把返回数组的第size个元素置为空。
4void sort(Comparator<? super E> c)
public void sort(Comparator<? super E> c) {
final int expectedModCount = modCount;
Arrays.sort((E[]) elementData, 0, size, c);
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
modCount++;
}
可以看到该sort函数是基于Comparator<? super E> c接口的,即待排序的元素必须实现Comparator接口,其实不仅仅是ArrayList,java集合框架中的全部集合的排序都是基于
Comparator接口的。
五总结:
1ArrayList的内部是基于Object[]类型的数组实现的,支持下标访问,因此具备较高的随机访问速度。
2当用一个集合作为参数来构造一个ArrayList时,其内部是是先将该集合转化为数组,这一点与LinkedList非常类似,与HashMap一样,ArrayList也存在扩容机制,即调用ensureCapacityInternal扩容
3可以看到ArrayList中的方法都未使用synchronized关键字修饰,即ArrayList是非同步的。
4ArrayList允许重复元素存在,因为在add元素的过程中不存在HashMap中put时判重替换的过程,只是进行简单的插入操作。
【java集合框架源码剖析系列】java源码剖析之ArrayList的更多相关文章
- [转载] Java集合框架之小结
转载自http://jiangzhengjun.iteye.com/blog/553191 1.Java容器类库的简化图,下面是集合类库更加完备的图.包括抽象类和遗留构件(不包括Queue的实现): ...
- Java集合框架体系JCF
Java 集合框架体系作为Java 中十分重要的一环, 在我们的日常开发中扮演者十分重要的角色, 那么什么是Java集合框架体系呢? 在Java语言中,Java语言的设计者对常用的数据结构和算法做了一 ...
- Java集合框架使用总结
Java集合框架使用总结 前言:本文是对Java集合框架做了一个概括性的解说,目的是对Java集合框架体系有个总体认识,如果你想学习具体的接口和类的使用方法,请参看JavaAPI文档. 一.概述数据结 ...
- 【java集合框架源码剖析系列】java源码剖析之TreeSet
本博客将从源码的角度带领大家学习TreeSet相关的知识. 一TreeSet类的定义: public class TreeSet<E> extends AbstractSet<E&g ...
- 【java集合框架源码剖析系列】java源码剖析之HashSet
注:博主java集合框架源码剖析系列的源码全部基于JDK1.8.0版本.本博客将从源码角度带领大家学习关于HashSet的知识. 一HashSet的定义: public class HashSet&l ...
- 【java集合框架源码剖析系列】java源码剖析之TreeMap
注:博主java集合框架源码剖析系列的源码全部基于JDK1.8.0版本.本博客将从源码角度带领大家学习关于TreeMap的知识. 一TreeMap的定义: public class TreeMap&l ...
- 【java集合框架源码剖析系列】java源码剖析之LinkedList
注:博主java集合框架源码剖析系列的源码全部基于JDK1.8.0版本. 在实际项目中LinkedList也是使用频率非常高的一种集合,本博客将从源码角度带领大家学习关于LinkedList的知识. ...
- 【java集合框架源码剖析系列】java源码剖析之HashMap
前言:之所以打算写java集合框架源码剖析系列博客是因为自己反思了一下阿里内推一面的失败(估计没过,因为写此博客已距阿里巴巴一面一个星期),当时面试完之后感觉自己回答的挺好的,而且据面试官最后说的这几 ...
- 【java集合框架源码剖析系列】java源码剖析之java集合中的折半插入排序算法
注:关于排序算法,博主写过[数据结构排序算法系列]数据结构八大排序算法,基本上把所有的排序算法都详细的讲解过,而之所以单独将java集合中的排序算法拿出来讲解,是因为在阿里巴巴内推面试的时候面试官问过 ...
随机推荐
- 【poj 1087 a plug for UNIX】
在大米饼的帮助下,终于找到了大米饼程序中如同大米饼一般的错误! 考点在问题转化,然后就跑一个你喜欢的最大流算法(二分图可以啵?) 再来一个例子吧: [纯手绘大米饼图片] 其中有的边权是1,否则就是in ...
- bzoj3944Sum
3944: Sum Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 5149 Solved: 1385[Submit][Status][Discuss ...
- 设置思科设备console密码、enable密码、vty登录密码
思科设备各级密码:1) console密码 SW2(config)#line console 0SW2(config-line)#password ciscoSW2(config-line)#log ...
- vue开发中遇到的问题集锦(2)
1,在搭建了一个vue的脚手架之后,写了第一个组件,路由也已经配置完毕,且页面的路由显示是:http://localhost:8080/#/userLogin,userLogin里面有内容,但是页面显 ...
- JAVA 第二天 关键字
一.标识符及字符集 Java语言规定标识符是以字母.下划线"_"或美元符号"$"开始,随后可跟数字.字母.下划线或美元符号的字符序列.Java标识符大小写敏感, ...
- 聊聊LightProbe原理实现以及对LightProbe数据的修改
0x00 前言 最近工作比较忙,所以文章已经很久没有更新了.这篇小文的主题也是在出差的高铁上想到,因为最近和一些朋友聊天,发现他们中很多人的项目中都使用了多个实时光源.细问之下主要是某些物体,例如角色 ...
- MLDS笔记:浅层结构 vs 深层结构
深度学习出现之前,机器学习方面的开发者通常需要仔细地设计特征.设计算法,且他们在理论上常能够得知这样设计的实际表现如何: 深度学习出现后,开发者常先尝试实验,有时候实验结果常与直觉相矛盾,实验后再找出 ...
- MySQL LIKE 子句
MySQL LIKE 子句 我们知道在MySQL中使用 SQL SELECT 命令来读取数据, 同时我们可以在 SELECT 语句中使用 WHERE 子句来获取指定的记录. WHERE 子句中可以使用 ...
- Docker的Fig 项目
在你的应用里面添加一个 fig.yml 文件,并指定一些简单的内容,执行 fig up 它就能帮你快速建立起一个容器 快速搭建基于 Docker 的隔离开发环境 使用 Dockerfile 文件指定你 ...
- 深入理解DirectByteBuffer
介绍 最近在工作中使用到了DirectBuffer来进行临时数据的存放,由于使用的是堆外内存,省去了数据到内核的拷贝,因此效率比用ByteBuffer要高不少.之前看过许多介绍DirectBuffer ...