概述

  本文是基于jdk8_271版本进行分析的。

  ArrayList是Java集合中出场率最多的一个类。底层是基于数组实现,根据元素的增加而动态扩容,可以理解为它是加强版的数组。ArrayList允许元素为null。它是线程不安全的。

数据结构

  • 实现继承关系

1 public class ArrayList<E> extends AbstractList<E>
2 implements List<E>, RandomAccess, Cloneable, java.io.Serializable
  1. AbstractList:继承AbstractList抽象类,使用实现的公共方法
  2. List:实现List接口操作规范,增、删、遍历等操作
  3. RandomAccess:提供随机访问功能
  4. Cloneable:提供可拷贝功能
  5. Serializable:提供可序列化功能

  这里有人会问为什么继承了AbstractList还要实现List接口(这个问题无关紧要,可以忽略),我在stackoverflow一个帖子看到了这个问题,有兴趣可以点击查看

  • 静态变量

1     // 默认初始化容量的大小
2 private static final int DEFAULT_CAPACITY = 10;
3 // 空数组
4 private static final Object[] EMPTY_ELEMENTDATA = {};
5 // 空数组,与EMPTY_ELEMENTDATA 区分开,无参构造时候会使用该空数组(jdk1.7是只有EMPTY_ELEMENTDATA的)
6 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
7 // 容量最大值。-8是因为一些虚拟机在数组中保留一些标题字,尝试分配更大的数组可能会导致OOM
8 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
  • 成员变量

1     transient Object[] elementData;    //存放数组元素,transient表示该字段不进行序列化操作
2 private int size; //数组中实际存放元素的个数
  • 构造方法

  无参构造使用的是DEFAULTCAPACITY_EMPTY_ELEMENTDATA空数组;带参构造初始化长度为0,使用的是EMPTY_ELEMENTDATA空数组。

 1     // 带参构造,指定初始化容量大小
2 public ArrayList(int initialCapacity) {
3 if (initialCapacity > 0) {
4 this.elementData = new Object[initialCapacity];
5 } else if (initialCapacity == 0) {
6 this.elementData = EMPTY_ELEMENTDATA;
7 } else {
8 throw new IllegalArgumentException("Illegal Capacity: "+
9 initialCapacity);
10 }
11 }
12
13 // 无参构造,elementData 使用默认空数组
14 public ArrayList() {
15 this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
16 }
17
18 // 有参构造。传入一个集合
19 public ArrayList(Collection<? extends E> c) {
20 Object[] a = c.toArray();
21 if ((size = a.length) != 0) {
22 if (c.getClass() == ArrayList.class) {
23 elementData = a;
24 } else {
25 elementData = Arrays.copyOf(a, size, Object[].class);
26 }
27 } else {
28 // 如果传入集合长度为0,elementData 用EMPTY_ELEMENTDATA替换
29 elementData = EMPTY_ELEMENTDATA;
30 }
31 }

主要方法解析

  • calculateCapacity--扩容方法
    // 计算需要的最小容量大小
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
} // 确保内部容量,如果容量不足则进行扩容处理
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
} private void ensureExplicitCapacity(int minCapacity) {
modCount++;
if (minCapacity - elementData.length > 0)
// 如果所需的最小容量值大于现在数组长度,表示需要进行扩容处理
grow(minCapacity);
} // 真正扩容方法
private void grow(int minCapacity) {
int oldCapacity = elementData.length; // 原容量大小
int newCapacity = oldCapacity + (oldCapacity >> 1); // 预计扩容的大小;位运算(1.5倍向下取整)
// 预计扩容的值与通过计算需要的最小容量值比较,取最大值
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
} private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
// 容量最大值为MAX_ARRAY_SIZE,为什么还会返回 Integer.MAX_VALUE呢?
// 如果minCapacity > MAX_ARRAY_SIZE,说明此时容器大小已经为MAX_ARRAY_SIZE。静态变量说到MAX_ARRAY_SIZE=Integer.MAX_VALUE-8,一些虚拟机在数组中保留一些标题字,尝试分配更大的数组可能会导致OOM。注意只是可能,不是所有的,因为此时数组长度已经是MAX_ARRAY_SIZE了,不如尝试去进行扩容到Integer.MAX_VALUE,说不定就成功了
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
  • add--添加元素

  注意:在进行添加操作之前,都会先进行判断数组是否需要扩容;入参如果涉及索引,还会判断索引是否越界。

 1     public boolean add(E e) {
2 ensureCapacityInternal(size + 1); // Increments modCount!!
3 elementData[size++] = e;
4 return true;
5 }
6 public void add(int index, E element) {
7 rangeCheckForAdd(index);
8
9 ensureCapacityInternal(size + 1); // Increments modCount!!
10 System.arraycopy(elementData, index, elementData, index + 1,size - index);
11 elementData[index] = element;
12 size++;
13 }
14
15 public boolean addAll(Collection<? extends E> c) {
16 Object[] a = c.toArray();
17 int numNew = a.length;
18 ensureCapacityInternal(size + numNew); // Increments modCount
19 System.arraycopy(a, 0, elementData, size, numNew);
20 size += numNew;
21 return numNew != 0;
22 }
23 public boolean addAll(int index, Collection<? extends E> c) {
24 rangeCheckForAdd(index);
25
26 Object[] a = c.toArray();
27 int numNew = a.length;
28 ensureCapacityInternal(size + numNew); // Increments modCount
29
30 int numMoved = size - index;
31 if (numMoved > 0)
32 System.arraycopy(elementData, index, elementData, index + numNew,
33 numMoved);
34
35 System.arraycopy(a, 0, elementData, index, numNew);
36 size += numNew;
37 return numNew != 0;
38 }
  • remove--删除元素

  删除元素时,如果该索引位后面有元素,则将后面元素向前移动一位(如果删除n个,则向后面元素向前移动n位),同时将数组最后一位数值置为null。

  1     public E remove(int index) {
2 rangeCheck(index);
3
4 modCount++;
5 E oldValue = elementData(index);
6
7 int numMoved = size - index - 1;
8 if (numMoved > 0)
9 System.arraycopy(elementData, index+1, elementData, index,
10 numMoved);
11 elementData[--size] = null; // clear to let GC do its work
12
13 return oldValue;
14 }
15 public boolean remove(Object o) {
16 if (o == null) {
17 for (int index = 0; index < size; index++)
18 if (elementData[index] == null) {
19 fastRemove(index);
20 return true;
21 }
22 } else {
23 for (int index = 0; index < size; index++)
24 if (o.equals(elementData[index])) {
25 fastRemove(index);
26 return true;
27 }
28 }
29 return false;
30 }
31
32 private void fastRemove(int index) {
33 modCount++;
34 int numMoved = size - index - 1;
35 if (numMoved > 0)
36 System.arraycopy(elementData, index+1, elementData, index,
37 numMoved);
38 elementData[--size] = null; // clear to let GC do its work
39 }
40
41 public void clear() {
42 modCount++;
43
44 // clear to let GC do its work
45 for (int i = 0; i < size; i++)
46 elementData[i] = null;
47
48 size = 0;
49 }
50
51 protected void removeRange(int fromIndex, int toIndex) {
52 modCount++;
53 int numMoved = size - toIndex;
54 System.arraycopy(elementData, toIndex, elementData, fromIndex,
55 numMoved);
56
57 // clear to let GC do its work
58 int newSize = size - (toIndex-fromIndex);
59 for (int i = newSize; i < size; i++) {
60 elementData[i] = null;
61 }
62 size = newSize;
63 }
64
65 // 从此列表中删除指定集合c中包含的所有元素
66 public boolean removeAll(Collection<?> c) {
67 Objects.requireNonNull(c);
68 return batchRemove(c, false);
69 }
70 // 此列表中仅保留包含在指定集合中的元素
71 public boolean retainAll(Collection<?> c) {
72 Objects.requireNonNull(c);
73 return batchRemove(c, true);
74 }
75 // ArrayList的批量删除算法
76 private boolean batchRemove(Collection<?> c, boolean complement) {
77 final Object[] elementData = this.elementData;
78 int r = 0, w = 0;
79 boolean modified = false;
80 try {
81 for (; r < size; r++)
82 if (c.contains(elementData[r]) == complement)
83 elementData[w++] = elementData[r];
84 } finally {
85 // Preserve behavioral compatibility with AbstractCollection,
86 // even if c.contains() throws.
87 if (r != size) {
88 System.arraycopy(elementData, r,
89 elementData, w,
90 size - r);
91 w += size - r;
92 }
93 if (w != size) {
94 // clear to let GC do its work
95 for (int i = w; i < size; i++)
96 elementData[i] = null;
97 modCount += size - w;
98 size = w;
99 modified = true;
100 }
101 }
102 return modified;
103 }
  • clone--拷贝方法
    public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
  • writeObject/readObject--序列化方法
 1     private void writeObject(java.io.ObjectOutputStream s)
2 throws java.io.IOException{
3 // Write out element count, and any hidden stuff
4 int expectedModCount = modCount;
5 s.defaultWriteObject();
6
7 // Write out size as capacity for behavioural compatibility with clone()
8 s.writeInt(size);
9
10 // Write out all elements in the proper order.
11 for (int i=0; i<size; i++) {
12 s.writeObject(elementData[i]);
13 }
14
15 if (modCount != expectedModCount) {
16 throw new ConcurrentModificationException();
17 }
18 }
19
20 private void readObject(java.io.ObjectInputStream s)
21 throws java.io.IOException, ClassNotFoundException {
22 elementData = EMPTY_ELEMENTDATA;
23
24 // Read in size, and any hidden stuff
25 s.defaultReadObject();
26
27 // Read in capacity
28 s.readInt(); // ignored
29
30 if (size > 0) {
31 // be like clone(), allocate array based upon size not capacity
32 ensureCapacityInternal(size);
33
34 Object[] a = elementData;
35 // Read in all elements in the proper order.
36 for (int i=0; i<size; i++) {
37 a[i] = s.readObject();
38 }
39 }
40 }

Java集合详解(二):ArrayList原理解析的更多相关文章

  1. Java集合详解二

    前一篇我们已经讲过了Collectin是存放单值的最大接口, 可以看到Map接口和其主要之类的关系图如下: 快速访问 HashMap    HashTable  TreeMap  Map输出 那Map ...

  2. Java集合详解1:一文读懂ArrayList,Vector与Stack使用方法和实现原理

    本文非常详尽地介绍了Java中的三个集合类 ArrayList,Vector与Stack <Java集合详解系列>是我在完成夯实Java基础篇的系列博客后准备开始写的新系列. 这些文章将整 ...

  3. Java集合详解3:Iterator,fail-fast机制与比较器

    Java集合详解3:Iterator,fail-fast机制与比较器 今天我们来探索一下LIterator,fail-fast机制与比较器的源码. 具体代码在我的GitHub中可以找到 https:/ ...

  4. Java集合详解6:这次,从头到尾带你解读Java中的红黑树

    <Java集合详解系列>是我在完成夯实Java基础篇的系列博客后准备开始写的新系列. 这些文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查 ...

  5. Java集合详解3:一文读懂Iterator,fail-fast机制与比较器

    <Java集合详解系列>是我在完成夯实Java基础篇的系列博客后准备开始写的新系列. 这些文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查 ...

  6. Java集合详解2:一文读懂Queue和LinkedList

    <Java集合详解系列>是我在完成夯实Java基础篇的系列博客后准备开始写的新系列. 这些文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查 ...

  7. Java集合详解8:Java的集合类细节精讲

    Java集合详解8:Java集合类细节精讲 今天我们来探索一下Java集合类中的一些技术细节.主要是对一些比较容易被遗漏和误解的知识点做一些讲解和补充.可能不全面,还请谅解. 本文参考:http:// ...

  8. Java集合详解6:TreeMap和红黑树

    Java集合详解6:TreeMap和红黑树 初识TreeMap 之前的文章讲解了两种Map,分别是HashMap与LinkedHashMap,它们保证了以O(1)的时间复杂度进行增.删.改.查,从存储 ...

  9. Java集合详解8:Java集合类细节精讲,细节决定成败

    <Java集合详解系列>是我在完成夯实Java基础篇的系列博客后准备开始写的新系列. 这些文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查 ...

  10. Java集合详解4:一文读懂HashMap和HashTable的区别以及常见面试题

    <Java集合详解系列>是我在完成夯实Java基础篇的系列博客后准备开始写的新系列. 这些文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查 ...

随机推荐

  1. 剑指 Offer 03. 数组中重复的数字

    剑指 Offer 03. 数组中重复的数字 找出数组中重复的数字. 在一个长度为 n 的数组 nums 里的所有数字都在 0-n-1 的范围内.数组中某些数字是重复的,但不知道有几个数字重复了,也不知 ...

  2. 前端er必须知道的Git地址及常用工具地址

    商城篇(找工作必练) 开源商城 推荐指数:5星,掌握了它,可以说,今后工作中的各种需求都不是问题,工作1~2年的也可以学习其中的思路(建议收藏). 这是一个集小程序/公众号/app为一体的商城系统,包 ...

  3. [Fundamental of Power Electronics]-PART I-3.稳态等效电路建模,损耗和效率-3.2 考虑电感铜损

    3.2 考虑电感铜损 可以拓展图3.3的直流变压器模型,来对变换器的其他属性进行建模.通过添加电阻可以模拟如功率损耗的非理想因素.在后面的章节,我们将通过在等效电路中添加电感和电容来模拟变换器动态. ...

  4. OO_Unit2_Summary

    经过三周的自己电梯瞎设计,下次坐电梯想我想的可能就不是如何优化调度算法,而是千万别把自己死锁在电梯里了(手动狗头) 一.设计策略 1. 需求分析: 作业一:单部多线程可稍带电梯,一部电梯,固定楼层,不 ...

  5. 宝塔linux7.4.2/windows6.8 的版本中的安全随笔

    在2020/8.23宝塔官方发布了一条关于宝塔linux7.4.2和Windows6.8版本中存在的重大的安全隐患 通知来源https://www.bt.cn/bbs/thread-54644-1-1 ...

  6. java面试一日一题:讲对mysql的MVCC的理解

    问题:请讲下对mysql中MVCC的理解 分析:这个问题要回答的是对MVCC的理解,以及MVCC解决了什么问题这几个方面入手. 回答要点: 主要从以下几点去考虑, 1.什么是MVCC? 2.MVCC用 ...

  7. Java(195-214)【final、权限、内部类】

    1.final关键字的概念与四种方法 今天是基础学习的最后一天!~ 2.final关键字用来修饰一个类 3.final关键字来修饰成员方法 4.final用于修饰局部变量 package cn.itc ...

  8. SpringBoot 使用逆向工程 构建Mapper.xml Dao层(持久层) 实体类

    逆向工程 注: 有数据库表即可 第一步为创建数据库表 (可选)使用PowerDesigner设计数据库表,物理模型构建 添加pom.xml 逆向工程生成代码插件 <!--plugin 逆向工程生 ...

  9. Android+Java Web+MySQL实现登录注册

    1 前言&概述 这篇文章是基于此处文章的更新,更新了一些技术栈,更加贴近实际需要,以及修复了若干的错误. 这是一个前端Android+后端Java/Kotlin通过Servelt进行后台数据库 ...

  10. Go-26-Json

    JSON(JavaScript Object Notation,JavaScript对象表示法)是一种轻量级的数据交换格式,因简单.可读性强被广泛使用. Go的标准包encoding/json对JSO ...