JDK1.8源码学习-ArrayList
JDK1.8源码学习-ArrayList
目录
一、ArrayList简介
为了弥补普通数组无法自动扩容的不足,Java提供了集合类,其中ArrayList对数组进行了封装,使其可以自动的扩容或缩小长度,相当于动态数组。
ArrayList封装了一个动态的可以重新分配的Object[]数组,其中每一个类的对象都有一个capacity属性,表示了它们所封装的Object[]数组的长度,当向ArrayList中添加元素的时候,该属性会自动的添加。如果想要添加大量元素的时候,可以使用ensureCapacity方法一次性增加capacity,减少数组的重新分配次数,提高效率。
二、ArrayList工作原理和数据结构
ArrayList 是基于数组来实现的
对于数组,这里就不进行过多叙述了。 想要说明的是ArrayList集合中底层数组元素的类型为Objec类型,即可以存放所有类型的数据。我们对ArrayList类的实例的操作,也都是基于数组的。
三、ArrayList源码分析
3.1、继承关系分析
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
ArrayList继承自AbstractList,实现了List、RandomAccess、Cloneable、Serializable接口,其中List中定义了一些基本的增删查改的功能,实现RandomAccess接口则具有随机读写的功能,实现了Cloneable接口,可以被克隆,实现了Serializable可以使ArrayList实现序列化。
3.2、成员变量分析
//定义的序列化id,主要是为了标识不同版本的兼容性
private static final long serialVersionUID = 8683452581122892189L;
//默认的数组存储容量为 10
private static final int DEFAULT_CAPACITY = 10;
//当指定数组的容量为0的时候使用这个变量赋值,空对象数组
private static final Object[] EMPTY_ELEMENTDATA = {};
//默认的实例化的时候使用此变量赋值,空对象数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//真正存放数据的对象数组,并不被序列化 (前面加有transient关键字)
transient Object[] elementData;
//数组中的真实元素个数它小于或等于elementData.length
private int size;
//数组中最大存放元素的个数
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
3.3、构造函数分析
3.3.1无参构造函数
ArrayList无参构造函数,默认初始化容量是10。
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; //默认值:10
}
在这里注释上说明构造一个容量为10的空的list,但是并没有看到数组的容量变为10,而是一个空的数组,实际上当有元素被加入(add方法)的时候,才会被初始化为10的数组。
3.3.2传入初始化容量构造函数
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);
}
}
创建一个initialCapacity大小的数组。
3.3.3传入初始化元素的构造函数
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
//判断引用的数组类型,并将引用转换成Object数组引用
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
//替换为空数组
this.elementData = EMPTY_ELEMENTDATA;
}
}
传入一个集合类(Collection的子类即可),转为list。
总结:可以看出ArrayList的内部存储结构就是一个Object类型的数组,因此它可以放任意类型的元素。在构造ArrayList的时候,如果传入初始大小,那么它将新建一个指定容量的Object数组,如果不设置初始大小,那么它将不会分配内存空间而是使用对象数组,在实际要放入元素的时候再进行内存分配。
3.4、add()方法分析
ArrayList中提供了四种方式的添加:1.直接添加;2.指定位置添加;3.添加全部;4.在指定位置添加全部。
1.直接添加
public boolean add(E e) {
//添加前先检查是否需要拓展数组, 此时数组长度最小为size+1
ensureCapacityInternal(size + 1);
//将元素添加到数组末尾
elementData[size++] = e;
return true;
}
add(E e)调用涉及的方法:
1.private void ensureCapacityInternal(int minCapacity) {
//判断数组是不是一个长度为0的空数组
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//如果是的话则给容量赋值为默认的容量大小10
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
2. private void ensureExplicitCapacity(int minCapacity) {
modCount++;
//判断当前容量是否大于数组当前的长度
if (minCapacity - elementData.length > 0)
//如果数组容量过小则进行扩容
grow(minCapacity);
}
3.private void grow(int minCapacity) {
int oldCapacity = elementData.length;
//扩容的长度是增加了原来数组的一半大小
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//判断是否达到了数组扩容的上限
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//把旧数组里面的数据拷贝到新数组中
elementData = Arrays.copyOf(elementData, newCapacity);
}
4.private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0)
throw new OutOfMemoryError();
//如果minCapacity还大于MAX_ARRAY_SIZE,那么就将Integer.MAX_VALUE返回,反之将MAX_ARRAY_SIZE返回。
//因为maxCapacity是三倍的minCapacity,可能扩充的太大了,就用minCapacity来判断了。
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
2.指定位置添加
public void add(int index, E element) {
//插入位置范围检查
rangeCheckForAdd(index);
//检查是否需要扩容
ensureCapacityInternal(size + 1);
//挪动插入位置后面的元素(将elementData中的元素从index开始,拷贝到index+1的位置,拷贝size-index个元素)
System.arraycopy(elementData, index, elementData, index + 1, size - index);
//在要插入的位置赋上新值
elementData[index] = element;
size++;
}
在指定位置进行添加,必然会影响到该位置的元素以及后续元素,因为是数组结构,所以只能进行元素的后移了,同时要首先检查入参的元素的位置(index)是否在范围内。
其中System.arraycopy(Object src,int srcPos,Object dest,int destPos,int length)方法中的参数的含义:src(原数组)、srcPos(从原数组的起始位置开始)、dest(目标数组)、destPos(目标数组的起始位置)、length(要copy的数组的长度)
3.添加一个集合中的全部元素
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
//检查是否需要扩容
ensureCapacityInternal(size + numNew);
//进行元素拷贝
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
4.在指定位置上添加一个集合中的全部元素
public boolean addAll(int index, Collection<? extends E> c) {
//插入位置范围检查
rangeCheckForAdd(index);
Object[] a = c.toArray();
int numNew = a.length;
//检查是否需要扩容
ensureCapacityInternal(size + numNew);
//复制元素的个数
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;
}
3.5、set()方法分析
修改的方法比较简单,就是给数组的下标重新赋值。
public E set(int index, E element) {
//index不能大于size
rangeCheck(index);
E oldValue = elementData(index);
//替换成新元素
elementData[index] = element;
return oldValue;
}
确保set的位置(index)小于当前数组的长度(size)并且大于0,获取指定位置(index)元素,然后放到oldValue中存放,将需要设置的元素放到指定位置(index)上。
3.6、remove()方法分析
1. E remove(int index):根据下标进行删除
public E remove(int index) {
// 检查下标是否越界
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
//最后 -1 是为了数组下标不越界
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; //置空引用,让GC进行回收
return oldValue;
}
2.boolean remove(Object o):根据传入对象进行删除
// 删除成功返回true, 失败false
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
} // 快速删除指定下标的元素, 无越界检查
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; //置空引用,让GC进行回收
}
3. boolean removeAll(Collection<?> c):根据传入集合进行删除
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, false);
}
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
int r = 0, w = 0;
boolean modified = false;
try {
for (; r < size; r++)
// 如果complement为false, 则c集合包含本集合中的元素, 则不进行操作, 这就是保留不属于c集合中的所有元素.
if (c.contains(elementData[r]) == complement)
elementData[w++] = elementData[r];
} finally {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
if (r != size) {
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
if (w != size) {
// clear to let GC do its work
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;
modified = true;
}
}
return modified;
}
如果传入的集合c中包含本集合中的元素,则对元素进行处理,把匹配的元素(c.contains(elementData[r])==complement)进行保留,不需要保留的下标置为null,其中利用complement属性,来决定含有的元素是删除还是保留。
3.7、get()方法分析
public E get(int index) {
//检查是否越界
rangeCheck(index);
//返回指定位置上的元素
return elementData(index);
}
四、ArrayList总结
1、ArrayList底层实现是基于数组的,因此对指定下标的查找和修改比较快,但是删除和插入操作比较慢。
2、构造ArrayList的时候尽量指定容量,减少扩容所带来的数组复制操作,如果不知道大小可以采用默认值10。
3、每次添加元素之前会检查是否需要扩容,每次扩容都是增加原有容量的一半。
4、ArrayList中的所有的方法都没有进行同步,因此它不是线程安全的。
5、在查找给定元素索引值等方法中,源码都将该元素的值分为null和不为null两种情况进行处理,ArrayList中允许元素为null。
JDK1.8源码学习-ArrayList的更多相关文章
- JDK1.8源码学习-LinkedList
JDK1.8源码学习-LinkedList 目录 一.LinkedList简介 LinkedList是一个继承于AbstractSequentialList的双向链表,是可以在任意位置进行插入和移除操 ...
- JDK1.8源码学习-String
JDK1.8源码学习-String 目录 一.String简介 String类是Java中最常用的类之一,所有字符串的字面量都是String类的实例,字符串是常量,在定义之后不能被改变. 二.定义 p ...
- JDK1.8源码学习-Object
JDK1.8源码学习-Object 目录 一.方法简介 1.一个本地方法,主要作用是将本地方法注册到虚拟机中. private static native void registerNatives() ...
- JDK1.8源码学习-HashMap
JDK1.8源码学习-HashMap 目录 一.HashMap简介 HashMap 主要用来存放键值对,它是基于哈希表的Map接口实现的,是常用的Java集合之一. 我们都知道在JDK1.8 之前 的 ...
- 由JDK源码学习ArrayList
ArrayList是实现了List接口的动态数组.与java中的数组相比,它的容量能动态增长.ArrayList的三大特点: ① 底层采用数组结构 ② 有序 ③ 非同步 下面我们从ArrayList的 ...
- jdk1.8源码学习笔记
前言: 前一段时间开始学习了一些基本的数据结构和算法,算是弥补了这方面的知识短板,但是仅仅是对一些算法的了解,目前工作当中也并没有应用到这些,因此希望通过结合实际例子来学习,巩固之前学到的内容,思前想 ...
- 从JDK源码学习Arraylist
从今天开始从源码去学习一些Java的常用数据结构,打好基础:) Arraylist源码阅读: jdk版本:1.8.0 首先看其构造方法: 构造方法一: 第一种支持初始化容量大小,其中声明一个对象数组, ...
- Java JDK1.8源码学习之路 1 Object
写在最前 对于一个合格的后端程序员来说,现行的流行框架早已经能胜任基本的企业开发,Springboot 任何的框架都把重复的工作更佳简单/优化的解决掉,但是完全陷入在这样的温水里面, 好比温水煮青蛙, ...
- JDK1.8源码学习-String-hashCode方法为什么选择数字31作为乘子
1. 背景 某天,我在写代码的时候,无意中点开了 String hashCode 方法.然后大致看了一下 hashCode 的实现,发现并不是很复杂.但是我从源码中发现了一个奇怪的数字,也就是本文的主 ...
随机推荐
- NIO入门之缓冲区Buffer
缓存区 Buffer 是数据容器 ByteBuffer 可以存储除了 boolean 以外的其他 7 种Java基本数据类型,如 getInt.putInt Buffer 是抽象类,它有除了 Bool ...
- java实现单链表的增删改以及排序
使用java代码模拟单链表的增删改以及排序功能 代码如下: package com.seizedays.linked_list; public class SingleLinkedListDemo { ...
- C++头文件居然可以这么打!!!! 长见识了!!!
返回主页 longdie 这人,生于天,立于地,为的就是顶天立地. 未来的答案早已被宇宙计算好了,人类自出现,答案就在那里,人类灭亡了,答案也在那里,,但是人活着,不就是为了看看未来发生了什么吗?如果 ...
- three.js 欧拉角和四元数
这篇郭先生就来说说欧拉角和四元数,欧拉角和四元数的优缺点是老生常谈的话题了,使用条件我就不多说了,我只说一下使用方法. 1. 欧拉角(Euler) 欧拉角描述一个旋转变换,通过指定轴顺序和其各个轴向上 ...
- 一周一个中间件-ES搜索引擎
---toc: truetitle: 一周一个中间件-ES搜索引擎date: 2019-09-19 18:43:36tags: - 中间件 - 搜索引擎--- ## 前言 > 在众多搜索引擎中, ...
- Python访问、修改、删除字典中的值
Python访问字典中的值: # 使用字典 ['键'] 获取字典中的元素 dic = {'a':123,'b':456,'c':789} print(dic['a']) # print(dic['c' ...
- Python os.write() 方法
write()方法语法格式如下:高佣联盟 www.cgewang.com os.write(fd, str) 参数 fd -- 文件描述符. str -- 写入的字符串. 返回值 该方法返回写入的实际 ...
- 5.22 noip模拟赛
本来我是不想写的,无奈不会写.蒟蒻 考场就是想不出来 今天得到了100分额外水过了100分我是真的失败.还有一个根本不会check 感觉自己非常之菜. 这道题是这样的 还行吧比较有意思 首先确立一个真 ...
- 面试题:JVM 堆内存溢出后,其他线程是否可继续工作?
来源:http://sina.lt/gqaM 最近网上出现一个美团面试题:“一个线程OOM后,其他线程还能运行吗?”.我看网上出现了很多不靠谱的答案.这道题其实很有难度,涉及的知识点有jvm内存分配. ...
- resultMap的用法以及关联结果集映射
resultType resultType可以把查询结果封装到pojo类型中,但必须pojo类的属性名和查询到的数据库表的字段名一致. 如果sql查询到的字段与pojo的属性名不一致,则需要使用res ...