JDK1.8-java.util.ArrayList类源码阅读
java.util包下的内容是用得非常多的,而且也是面试必问的,我们先从用得非常多的ArrayList说起。
1、定义
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
ArrayList是一个用数组实现的集合,元素有序且可重复。实现了RandomAccess接口,支持随机访问,实现了Cloneable,Serializable接口,表明可以拷贝和序列化。实现了List接口,表明支持List里定义的方法
至于RandomAccess接口很多同学可能不太了解,下面引用JDK API文档的描述:
Marker interface used by List implementations to indicate that they support fast (generally constant time) random access. The primary purpose of this interface is to allow generic algorithms to alter their behavior to provide good performance when applied to either random or sequential access lists.
大意就是这是一个标记接口,实现了RandomAccess接口就表明支持随机访问。此接口的主要目的是允许通用算法更改其行为,以便在应用于随机访问列表或顺序访问列表时提供良好的性能。
后一句话意思是我们通过判断是否实现了该接口来选择不同的遍历方式,因为实现了RandomAccess接口的集合,普通for循环比迭代器要快,这点官方文档也有说明:
for (int i=0, n=list.size(); i < n; i++)
list.get(i);
runs faster than this loop:
for (Iterator i=list.iterator(); i.hasNext(); )
i.next();
比如在Collections类里copy方法就实际运用了:
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
int srcSize = src.size();
if (srcSize > dest.size())
throw new IndexOutOfBoundsException("Source does not fit in dest");
if (srcSize < COPY_THRESHOLD ||
// 这里判断了是否实现了RandomAccess,从而选择了不同的遍历方式
(src instanceof RandomAccess && dest instanceof RandomAccess)) {
for (int i=0; i<srcSize; i++)
dest.set(i, src.get(i));
} else {
ListIterator<? super T> di=dest.listIterator();
ListIterator<? extends T> si=src.listIterator();
for (int i=0; i<srcSize; i++) {
di.next();
di.set(si.next());
}
}
}
2、字段
// 默认的初始容量
private static final int DEFAULT_CAPACITY = 10;
// 空的数组
private static final Object[] EMPTY_ELEMENTDATA = {};
// 这也是一个空的数组,和EMPTY_ELEMENTDATA的区别下面会讲到
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 这是ArrayList里真正存元素的数组,它的长度就是ArrayList的容量。为什么用transient修饰?下面会讲
transient Object[] elementData;
// ArrayList里元素的数量
private int size;
// 要分配的最大数组大小,一些虚拟机会在数组中保留一些header words。尝试分配更大的数组可能会导致OutOfMemoryError,所以这里-8
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
// 序列化的UID
private static final long serialVersionUID = 8683452581122892189L;
2.1、EMPTY_ELEMENTDATA和DEFAULTCAPACITY_EMPTY_ELEMENTDATA的区别
当使用有参构造函数并且初始容量为0时,将EMPTY_ELEMENTDATA赋值给elementData。当使用无参构造函数时,将DEFAULTCAPACITY_EMPTY_ELEMENTDATA赋值给elementData。
他们在第一次添加元素时扩容逻辑不同,EMPTY_ELEMENTDATA添加第一个元素后容量为1。DEFAULTCAPACITY_EMPTY_ELEMENTDATA添加第一个元素后容量为DEFAULT_CAPACITY = 10。
验证一下:
// 由于是无参构造函数,所以使用的是DEFAULTCAPACITY_EMPTY_ELEMENTDATA,此时容量为0
ArrayList<String> arrayList1 = new ArrayList<>();
// 添加第一个元素时扩容为10,此时容量为10。注意此时size=1,不要将size和容量混淆。容量就好比一个水桶能装多少水,size就好比实际上装了多少水
arrayList1.add("hello");
// 此时使用的是EMPTY_ELEMENTDATA,容量为0
ArrayList<String> arrayList2 = new ArrayList<>(0);
// 添加第一个元素时扩容为1,此时容量为1
arrayList2.add("world");
debug一下:
我们看到arrayList1的容量为10
arrayList2的容量为1
使用EMPTY_ELEMENTDATA 和 DEFAULTCAPACITY_EMPTY_ELEMENTDATA还有一个作用就是避免创建多余的空数组
2.2、elementData为什么用transient修饰?
我们知道elementData是真正存储元素的地方,用transient修饰就代表不能被序列化,那ArrayList序列化还有何意义?
支持序列化需要实现writeObject和readObject方法,真理就在这两个方法里:
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
s.defaultWriteObject();
// Write out size as capacity for behavioural compatibility with clone()
s.writeInt(size);
// Write out all elements in the proper order.
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
elementData = EMPTY_ELEMENTDATA;
// Read in size, and any hidden stuff
s.defaultReadObject();
// Read in capacity
s.readInt(); // ignored
if (size > 0) {
// be like clone(), allocate array based upon size not capacity
int capacity = calculateCapacity(elementData, size);
SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);
ensureCapacityInternal(size);
Object[] a = elementData;
// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
a[i] = s.readObject();
}
}
}
我们看到在序列化时将size和elementData里实际存在的元素写出,反序列化也就只需要反序列化实际存在的元素,提高了性能。为什么不直接将elementData序列化呢?
因为elementData的容量一般情况下大于里面实际存储的元素量,直接序列化浪费时间和空间。
3、构造函数
/**
* 无参构造函数
*/
public ArrayList() {
// 将DEFAULTCAPACITY_EMPTY_ELEMENTDATA赋值给elementData,当第一次添加元素时,扩容为10
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* 指定初始容量的构造函数
*/
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
// 将EMPTY_ELEMENTDATA赋值给elementData,当第一次添加元素时,扩容为1
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
/**
* 通过已有的集合创建ArrayList
*/
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;
}
}
4、添加元素的方法
四个添加元素的方法,分两类,一类是直接加,一类是带下标的加,主要逻辑差不多。我们分析下add(E e)
方法,理解ArrayList添加元素的逻辑以及扩容的逻辑
/**
* add方法
*/
public boolean add(E e) {
// 确定下集合的大小(在往水桶里装水前得确认容量是否足够吧,不够的话就扩容喽)
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
/**
* 确定集合的大小
*/
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// 这里可以证明new ArrayList()出来的集合,第一次添加元素时扩容为10(面试考点哦)
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
// modCount用于快速失败(fail-fast)机制
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
// 如果容量不够就扩容(这里没有hashMap里装载因子的概念哦,又是一个面试考点,哈哈)
grow(minCapacity);
}
/**
* 扩容方法(高频考点)
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
// 容量增加至原来的1.5倍(移位运算比乘除快,大家写代码可以借鉴)
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
// 进入到这里,则代表计算newCapacity时发生了溢出,直接扩容为所需要的容量
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
// 当计算的新容量比MAX_ARRAY_SIZE还大时,则调用hugeCapacity处理
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
// 将原数组拷贝到长度为newCapacity的新数组里
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
// 当所需要的容量大于MAX_ARRAY_SIZE时,扩容为Integer.MAX_VALUE
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
是不是有很多同学直接跳过了上面的代码?哈哈,这里总结下ArrayList的扩容机制:
5、删除元素的方法
/**
* 删除指定下标的元素
*/
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);
// 将最后一个元素设为null,方便垃圾回收
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
/**
* 删除指定的元素(注意这里只会删除第一次匹配到的该元素)
*/
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;
}
/**
* 删除与传入的集合里相同的元素
*/
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, false);
}
/**
* 删除符合条件的元素
*/
public boolean removeIf(Predicate<? super E> filter) {
// 太多我就不贴了
}
/**
* 删除指定下标之间的元素(protected修饰,我们一般用不到)
*/
protected void removeRange(int fromIndex, int toIndex) {
modCount++;
int numMoved = size - toIndex;
System.arraycopy(elementData, toIndex, elementData, fromIndex,
numMoved);
// clear to let GC do its work
int newSize = size - (toIndex-fromIndex);
for (int i = newSize; i < size; i++) {
elementData[i] = null;
}
size = newSize;
}
6、修改元素的方法
/**
* 修改指定下标的元素
*/
public E set(int index, E element) {
// 检查下标是否越界
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
7、查询元素的方法
/**
* 根据下标查询元素
*/
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
原文地址:https://www.cnblogs.com/liuwhut/p/13204691.html
- 网页中显示xml,直接显示xml格式的文件
- 【编程题】Java编程题一(10道)
- 【学习笔记】springboot教程(1)第一个demo
- Hyperledger Fabric 积分代币上链方案
- MySQL中的事务和锁简单测试(r10笔记第46天)
- 数据库无响应问题的紧急处理和分析 (r10笔记第42天)
- 【转载】记Golang数据库查询封装的坑
- Golang 基本语法学习笔记之流程控制
- Go 语言的演化历程
- JS 评分五角星随鼠标移动显示
- Golang标准库学习——buffio包 ---转
- 【Go 语言社区】Go语言条件变量的两个例子
- mysqlimport导入报错的排查(r10笔记第58天)
- 【Go 语言社区】POJ 1047 Round and Round We Go 循环数新解
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- MySQL数据库与JDBC编程
- 自动删除QQ空间指定好友的留言
- 在Ubuntu 18.04中安装VMware工具
- 微信小程序下拉刷新功能
- 详解Linux Screen让程序保持后台运行
- Python Des加密与解密实现软件注册码、机器码
- Excel VBA 在保留原单元格数据的情况下,将计算的百分比加在后面
- 入门级别的面试题——LeetCode题目19:删除链表的倒数第N个节点
- python做web接口测试零散笔记--1
- 要一遍做对——LeetCode题目20:有效的括号
- 双指针算法练习(一)
- 一般是面试的热身题——LeetCode题目21:合并两个有序链表
- LeetCode题目22:括号生成
- OpenGL ES 3.0 | 着色器编译器
- LeetCode题目23:合并K个排序链表