【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集合中的排序算法拿出来讲解,是因为在阿里巴巴内推面试的时候面试官问过 ...
随机推荐
- bzoj4152[AMPPZ2014]The Captain 最短路
4152: [AMPPZ2014]The Captain Time Limit: 20 Sec Memory Limit: 256 MBSubmit: 1517 Solved: 603[Submi ...
- UVA 1146 Now or later
The Terminal Radar Approach CONtrol (TRACON) controls aircraft approaching and departing when they a ...
- [Project] Simulate HTTP Post Request to obtain data from Web Page by using Python Scrapy Framework
1. Background Though it's always difficult to give child a perfect name, parent never give up trying ...
- .net4.0设计模式(一)使用Lazy的单例模式
延迟加载,亦称延迟实例化,延迟初始化等, 主要表达的思想是,把对象的创建将会延迟到使用时创建,而不是在对象实例化时创建对象,即用时才加载.这种方式有助于提高于应用程序的性能,避免浪费计算,节省内存的使 ...
- candy(动态规划)
题目描述 There are N children standing in a line. Each child is assigned a rating value. You are giving ...
- Python中模块之shutil及zipfile&tarfile的功能介绍
shutil的功能介绍及其他打包.压缩模块 1. shutil模块的方法 chown 更改指定路径的属组 2. copy 拷贝文件和权限 方法:shutil.copy(src,dst,*,follow ...
- 如何避免 async/await 地狱
简评:async/await 写着很爽,不过要注意这些问题. async/await 让我们摆脱了回调地狱,但是这又引入了 async/await 地狱的问题. 什么是 async/await 地狱 ...
- asp.net用户控件引用
<%@ Register Src="~/_module/IndexChannelHead.ascx" TagName="tn" TagPrefix=&qu ...
- easyui datagrid 横向滚动条
要出现横向滚动条则不能有fitColumns:true/false
- Oracle中case用法总结
--case语句的种类: .简单case语句 语法: case exp when comexp then returnvalue ... when comexp then returnvalue el ...