集合之ArrayList(含JDK1.8源码分析)
一、ArrayList的数据结构
ArrayList底层的数据结构就是数组,数组元素类型为Object类型,即可以存放所有类型数据。我们对ArrayList类的实例的所有的操作(增删改查等),其底层都是基于数组的。
定义底层数据结构:Object[] elementData
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData; // non-private to simplify nested class access
===The capacity of the ArrayList is the length of this array buffer. ===
注释中这句话的意思是ArrayList的capacity即容量是数组elementData的长度。capacity = elementData.length,而不是arrayList.size()。
===Any empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA will be expanded to DEFAULT_CAPACITY when the first element is added.===
注释中这句话的意思是当通过如:List<String> dataList = new ArrayList<>();创建ArrayList的一个对象时,此时该dataList是没有被定义容量的,当add第一个元素的时候,此时才将dataList的容量设置为 DEFAULT_CAPACITY即10.
举例说明:
因为ArrayList中数组elementData的属性是default,所以我们通过反射来获取数组的长度即capacity。
public class Test {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
List<String> dataList = new ArrayList<>();
//初始化时获取ArrayList的capacity
int arrayListCapacity = getArrayListCapacity(dataList);
System.out.println("初始化时ArrayList的capacity:" + arrayListCapacity);
dataList.add("zhangsan");
//添加第一个元素初始化时获取ArrayList的capacity
int arrayListCapacity1 = getArrayListCapacity(dataList);
System.out.println("add第一个元素后ArrayList的capacity:" + arrayListCapacity1);
} /**
* 反射获取ArrayList的容量capacity
* @param arrayList
* @return
* @throws NoSuchFieldException
* @throws IllegalAccessException
*/
public static int getArrayListCapacity(List arrayList) throws NoSuchFieldException, IllegalAccessException {
Class<ArrayList> arrayListClass = ArrayList.class;
Field elementData = arrayListClass.getDeclaredField("elementData");
elementData.setAccessible(true);
Object[] objects = (Object[]) elementData.get(arrayList);
return objects.length;
}
}
结果:
初始化时ArrayList的capacity:0
add第一个元素后ArrayList的capacity:10
二、ArrayList的属性
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
// 版本号
private static final long serialVersionUID = 8683452581122892189L;
// 缺省容量
private static final int DEFAULT_CAPACITY = 10;
// 空对象数组
private static final Object[] EMPTY_ELEMENTDATA = {};
// 缺省空对象数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 元素数组
transient Object[] elementData;
// 实际元素大小,默认为0
private int size;
// 最大数组容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
}
ArrayList类的属性中核心的属性为elementData,类型为Object[],用于存放实际元素,并且被标记为transient,也就意味着在序列化的时候,此字段是不会被序列化的。
三、ArrayList的构造函数
public ArrayList()型构造函数
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
构造一个空的arrayList对象,初始容量为10。(该初始容量也是add第一个元素的时候设置的)。
public ArrayList(int initialCapacity)型构造函数
/**
* Constructs an empty list with the specified initial capacity.
*
* @param initialCapacity the initial capacity of the list
* @throws IllegalArgumentException if the specified initial capacity
* is negative
*/
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);
}
}
构造一个空的arrayList对象,并指定初始容量,不允许初始容量小于0,否则抛出异常。
public ArrayList(Collection<? extends E> c)型构造函数
/**
* Constructs a list containing the elements of the specified
* collection, in the order they are returned by the collection's
* iterator.
*
* @param c the collection whose elements are to be placed into this list
* @throws NullPointerException if the specified collection is null
*/
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;
}
}
举例说明:
public class Test {
private static int size;
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
List<String> dataList = new ArrayList<>();
dataList.add("zhangsan");
dataList.add("lisi");
dataList.add("wangwu");
System.out.println("传入的参数dataList的大小: " + dataList.size());
List<String> dataList2 = new ArrayList<>(dataList);
System.out.println("new ArrayList<>(dataList)的元素: " + dataList2);
int arrayListCapacity = getArrayListCapacity(dataList2);
System.out.println("new ArrayList<>(dataList)的capacity: " + arrayListCapacity); } /**
* 反射获取ArrayList的容量capacity
* @param arrayList
* @return
* @throws NoSuchFieldException
* @throws IllegalAccessException
*/
public static int getArrayListCapacity(List arrayList) throws NoSuchFieldException, IllegalAccessException {
Class<ArrayList> arrayListClass = ArrayList.class;
Field elementData = arrayListClass.getDeclaredField("elementData");
elementData.setAccessible(true);
Object[] objects = (Object[]) elementData.get(arrayList);
return objects.length;
}
}
结果:
传入的参数dataList的大小: 3
new ArrayList<>(dataList)的元素: [zhangsan, lisi, wangwu]
new ArrayList<>(dataList)的capacity: 3
此种构造方法得到的arrayList的capacity为传入collection的size。
四、ArrayList的增删改查
1、增:add
public class TestArrayList {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
List<String> dataList = new ArrayList<>();
int arrayListCapacity = getArrayListCapacity(dataList);
System.out.println("未添加元素在时capacity:" + arrayListCapacity);
dataList.add("zhangsan1");
int arrayListCapacity1 = getArrayListCapacity(dataList);
System.out.println("add第一个元素在时capacity:" + arrayListCapacity1);
dataList.add("zhangsan2");
dataList.add("zhangsan3");
dataList.add("zhangsan4");
dataList.add("zhangsan5");
dataList.add("zhangsan6");
dataList.add("zhangsan7");
dataList.add("zhangsan8");
dataList.add("zhangsan9");
dataList.add("zhangsan10");
int arrayListCapacity2 = getArrayListCapacity(dataList);
System.out.println("add第十个元素在时capacity:" + arrayListCapacity2);
dataList.add("zhangsan11");
int arrayListCapacity3 = getArrayListCapacity(dataList);
System.out.println("add第十一个元素在时capacity:" + arrayListCapacity3);
} /**
* 反射获取ArrayList的容量capacity
* @param arrayList
* @return
* @throws NoSuchFieldException
* @throws IllegalAccessException
*/
public static int getArrayListCapacity(List arrayList) throws NoSuchFieldException, IllegalAccessException {
Class<ArrayList> arrayListClass = ArrayList.class;
Field elementData = arrayListClass.getDeclaredField("elementData");
elementData.setAccessible(true);
Object[] objects = (Object[]) elementData.get(arrayList);
return objects.length;
}
}
结果:
未添加元素在时capacity:0
add第一个元素在时capacity:10
add第十个元素在时capacity:10
add第十一个元素在时capacity:15
结合以上,在添加第一个元素时进行了扩容,capacity为10。当添加第十一个元素时,此时容量为10,已不够用,又进行了扩容,capacity为15。
add源码如下:
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
ensureCapacityInternal函数确保elemenData数组有合适的大小。指定minCapacity大小
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
} ensureExplicitCapacity(minCapacity);
}
ensureExplicitCapacity函数判断是否需要扩容。当arrayList所需的最小容量minCapacity大于当前数组elementData的长度时,就需要扩容了。
private void ensureExplicitCapacity(int minCapacity) {
modCount++; // overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
真正的扩容方法grow。
拷贝扩容:Arrays.copyOf(elementData, newCapacity);根据newCapacity构建一个新的数组,并将原数组elementData的数组复制到新数组中。最后将新的数组赋值给原数组。
/**
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
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);
}
========int newCapacity = oldCapacity + (oldCapacity >> 1);=========
正常情况下会扩容1.5倍,特殊情况下(新扩展数组大小已经达到了最大值)则只取最大值。
增(插入):add(int index,E element)
举例:
public class Test {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("zhangsan1");
list.add("zhangsan2");
list.add("zhangsan3");
list.add("zhangsan4");
System.out.println("list before add===" + list);
list.add(1,"zhaoliu");
System.out.println("list after add====" + list);
}
}
结果:
list before add===[zhangsan1, zhangsan2, zhangsan3, zhangsan4]
list after add====[zhangsan1, zhaoliu, zhangsan2, zhangsan3, zhangsan4]
源码分析:类似于下面的remove,都涉及到元素的移动。
/**
* Inserts the specified element at the specified position in this
* list. Shifts the element currently at that position (if any) and
* any subsequent elements to the right (adds one to their indices).
*
* @param index index at which the specified element is to be inserted
* @param element element to be inserted
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
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++;
}
2、删:remove
举例:
public class TestArrayList {
public static void main(String[] args) {
List<String> dataList = new ArrayList<>();
dataList.add("zhangsan");
dataList.add("lisi");
dataList.add("wangwu");
dataList.add("zhaoliu");
System.out.println(dataList);
dataList.remove(1);
System.out.println(dataList);
}
}
结果:
[zhangsan, lisi, wangwu, zhaoliu]
[zhangsan, wangwu, zhaoliu]
remove源码:
/**
* Removes the element at the specified position in this list.
* Shifts any subsequent elements to the left (subtracts one from their
* indices).
*
* @param index the index of the element to be removed
* @return the element that was removed from the list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E remove(int index) {
rangeCheck(index); modCount++;
E oldValue = elementData(index); int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work return oldValue;
}
解释说明:
对索引进行校验后,判断删除后需要移动的个数,本例中删除索引1的元素,需要移动索引2,3即两个元素
移动元素,使用的是System.arraycopy(Object src, int srcPos,Object dest, int destPos,int length)方法。
原数组
[zhangsan, lisi, wangwu, zhaoliu]
变成
[zhangsan, wangwu, zhaoliu, zhaoliu]
最后 elementData[--size] = null 将最后一个元素置为null。此时的size为3,即将element[3]置为null,显示如下:
[zhangsan, wangwu, zhaoliu]
=========注意这句注释:clear to let GC do its work==========
说明:置空原尾部数据 不再强引用, 可以GC掉(引用为null)
3、改:set
举例:
public class TestArrayList {
public static void main(String[] args) {
List<String> dataList = new ArrayList<>();
dataList.add("zhangsan");
dataList.add("lisi");
dataList.add("wangwu");
dataList.add("zhaoliu");
System.out.println(dataList);
dataList.set(3,"zl");
System.out.println(dataList);
}
}
结果:
[zhangsan, lisi, wangwu, zhaoliu]
[zhangsan, lisi, wangwu, zl]
set源码:
/**
* Replaces the element at the specified position in this list with
* the specified element.
*
* @param index index of the element to replace
* @param element element to be stored at the specified position
* @return the element previously at the specified position
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E set(int index, E element) {
rangeCheck(index); E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
判读所给索引是否在arrayList的size之内,否则抛出 IndexOutOfBoundsException异常。
/**
* Checks if the given index is in range. If not, throws an appropriate
* runtime exception. This method does *not* check if the index is
* negative: It is always used immediately prior to an array access,
* which throws an ArrayIndexOutOfBoundsException if index is negative.
*/
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
4、查:get
举例说明:
public class TestArrayList {
public static void main(String[] args){
List<String> dataList = new ArrayList<>();
dataList.add("zhangsan");
dataList.add("lisi");
dataList.add("wangwu");
dataList.add("zhaoliu");
System.out.println(dataList.get(3));
}
}
结果:
zhaoliu
get源码:
/**
* Returns the element at the specified position in this list.
*
* @param index index of the element to return
* @return the element at the specified position in this list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index) {
rangeCheck(index); return elementData(index);
}
可以看到,ArrayList的增、删都涉及到元素的移动,当元素数量比较多的时候,效率相对就会降低。但是改、查不涉及到元素的移动,根据索引值直接定位,效率比较高。
五、总结
1、ArrayList的优缺点:
从上面的几个过程总结一下ArrayList的优缺点。ArrayList的优点如下:
1、ArrayList底层以数组实现,是一种随机访问模式,再加上它实现了RandomAccess接口,因此查找也就是get的时候非常快
2、ArrayList在顺序添加一个元素的时候非常方便,只是往数组里面添加了一个元素而已
不过ArrayList的缺点也十分明显:
1、删除元素的时候,涉及到一次元素复制,如果要复制的元素很多,那么就会比较耗费性能
2、插入元素的时候,涉及到一次元素复制,如果要复制的元素很多,那么就会比较耗费性能
因此,ArrayList比较适合顺序添加、随机访问的场景。
2、ArrayList和Vector的区别
Vector,它是ArrayList的线程安全版本,其实现90%和ArrayList都完全一样,区别在于:
1、Vector是线程安全的,ArrayList是线程非安全的
2、Vector可以指定增长因子,如果该增长因子指定了,那么扩容的时候会每次新的数组大小会在原数组的大小基础上加上增长因子;如果不指定增长因子,那么就给原数组大小*2,源码如下:
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
参考资料:
https://blog.csdn.net/hrbeuwhw/article/details/79435934 集合间关系图
https://www.cnblogs.com/leesf456/p/5308358.html
https://www.cnblogs.com/xrq730/p/4989451.html
集合之ArrayList(含JDK1.8源码分析)的更多相关文章
- 集合之HashSet(含JDK1.8源码分析)
一.前言 我们已经分析了List接口下的ArrayList和LinkedList,以及Map接口下的HashMap.LinkedHashMap.TreeMap,接下来看的是Set接口下HashSet和 ...
- 集合之TreeSet(含JDK1.8源码分析)
一.前言 前面分析了Set接口下的hashSet和linkedHashSet,下面接着来看treeSet,treeSet的底层实现是基于treeMap的. 四个关注点在treeSet上的答案 二.tr ...
- 集合之LinkedHashSet(含JDK1.8源码分析)
一.前言 上篇已经分析了Set接口下HashSet,我们发现其操作都是基于hashMap的,接下来看LinkedHashSet,其底层实现都是基于linkedHashMap的. 二.linkedHas ...
- 集合之HashMap(含JDK1.8源码分析)
一.前言 之前的List,讲了ArrayList.LinkedList,反映的是两种思想: (1)ArrayList以数组形式实现,顺序插入.查找快,插入.删除较慢 (2)LinkedList以链表形 ...
- 集合之LinkedList(含JDK1.8源码分析)
一.前言 LinkedList是基于链表实现的,所以先讲解一下什么是链表.链表原先是C/C++的概念,是一种线性的存储结构,意思是将要存储的数据存在一个存储单元里面,这个存储单元里面除了存放有待存储的 ...
- 集合之TreeMap(含JDK1.8源码分析)
一.前言 前面所说的hashMap和linkedHashMap都不具备统计的功能,或者说它们的统计性能的时间复杂度都不是很好,要想对两者进行统计,需要遍历所有的entry,时间复杂度比较高,此时,我们 ...
- 集合之LinkedHashMap(含JDK1.8源码分析)
一.前言 大多数的情况下,只要不涉及线程安全问题,map都可以使用hashMap,不过hashMap有一个问题,hashMap的迭代顺序不是hashMap的存储顺序,即hashMap中的元素是无序的. ...
- Java集合:ArrayList (JDK1.8 源码解读)
ArrayList ArrayList几乎是每个java开发者最常用也是最熟悉的集合,看到ArrayList这个名字就知道,它必然是以数组方式实现的集合 关注点 说一下ArrayList的几个特点,也 ...
- 【集合框架】JDK1.8源码分析之ArrayList详解(一)
[集合框架]JDK1.8源码分析之ArrayList详解(一) 一. 从ArrayList字表面推测 ArrayList类的命名是由Array和List单词组合而成,Array的中文意思是数组,Lis ...
随机推荐
- 【转】联普多WAN口路由器是否可以设置叠加带宽
TP-link联普是全球领先的通讯供应厂商之一,那么你是否知道联普多WAN口路由器可以设置叠加带宽吗?下面是学习啦小编整理的一些关于联普多WAN口路由器是否可以设置叠加带宽的相关资料,供你参考. 联普 ...
- day 03 基本数据类型的使用、运算符
一:基本数据类型的使用 1.为什么数据要区分类型 数据类型指的是变量值的类型,变量值是用来记录事物的状态的,而事物的状态具有不同的类型,不同类型的变量值表示不同类型的状态,所以数据要区分类型. 2.数 ...
- 获取数值型数组的最大值和最小值,使用遍历获取每一个值,然后记录最大值和最小值的方式。(数组遍历嵌套if判断语句)
package com.Summer_0420.cn; /** * @author Summer * .获取数值型数组的最大值.最小值 * 方法:遍历获取每一个值,记录最大值: * 方法:遍历获取每一 ...
- Intellij Idea 2017创建web项目及tomcat部署实战
相关软件:Intellij Idea2017.jdk16.tomcat7 Intellij Idea直接安装(可根据需要选择自己设置的安装目录),jdk使用1.6/1.7/1.8都可以,主要是配置好系 ...
- NuGet的本地服务器安装与Package的发布(呕吐)
主要的步骤是按照下面的例子来做的: NuGet学习笔记(1)——初识NuGet及快速安装使用 NuGet学习笔记(2)——使用图形化界面打包自己的类库 NuGet学习笔记(3)——搭建属于自己的NuG ...
- 分享vs低版本开发的项目到VS高版本时遇到的4个小问题解决之记录
分享vs低版本开发的项目到VS高版本时遇到的4个小问题解决之记录 原文首发: http://anforen.com/wp/2017/08/extensionattribute_compilerserv ...
- 平均精度均值(mAP)——目标检测模型性能统计量
在机器学习领域,对于大多数常见问题,通常会有多个模型可供选择.当然,每个模型会有自己的特性,并会受到不同因素的影响而表现不同. 每个模型的好坏是通过评价它在某个数据集上的性能来判断的,这个数据集通常被 ...
- linux screen 工具
一.背景 系统管理员经常需要SSH 或者telent 远程登录到Linux 服务器,经常运行一些需要很长时间才能完成的任务,比如系统备份.ftp 传输等等.通常情况下我们都是为每一个这样的任务开一个远 ...
- [转]WINDOWS服务器安全加固实战(WINDOWS SERVER 2008 R2和WINDOWS SERVER 2012)
主机安全 启用防火墙 阿里云windows Server 2008 R2默认居然没有启用防火墙.2012可能也是这样的,不过这个一定要检查! 补丁更新 启用windows更新服务,设置为自动更新状态, ...
- Django Rest framework基础使用之Request/Response
1.Request restframework提供了一个Request对象(rest_framework.request.Request) Request对象继承了Django默认的HttpReque ...