前言

今天学习一个Java集合类使用最多的类 ArrayList , ArrayList 继承了 AbstractList,并实现了ListRandomAccess 等接口,

public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable

是一个以数组形式存储数据的集合,它具有以下的特点:

集合中的数组是有序排列的;

允许元素为null;

允许重复的数据;

非线程安全;

针对它的这些特点,我们一步步跟进源码进行解析。

源码解析

基本成员变量

先看下ArrayList 的基本成员变量

transient Object[] elementData;
private static final int DEFAULT_CAPACITY = 10;
private int size;
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

elementData :一个object数组,组成了ArrayList 的底层数据结构,由于数组类型为 Object,所以允许添加 null 。值得注意的是,变量的修饰符是 transient ,这说明这个数组无法被序列化,但是之前ArrayList却实现了序列化接口,是可以被初始化,这不是互相矛盾了吗,关于这个原因,下面会说到,别急。

DEFAULT_CAPACITY : 数组的初识容量

size : 数组元素个数

MAX_ARRAY_SIZE: 数组最大容量

说完变量后,开始学习ArrayList 的基本方法,我们都知道,一个集合最重要的方法就是对元素的 增删改查操作,了解了这些操作的方法,也就基本了解了容器的运作机制。

添加元素

ArrayList 中最基础的添加元素方法是 add(E e)

public boolean add(E e) {
//调整容量
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}

源码逻辑比较简单,主要是先做了调整容量处理,并把元素存入数组的最后一个位置。

来看一下调整容量的方法 ensureCapacityInternal(),源码如下:

private void ensureCapacityInternal(int minCapacity) {
//如果是空数组,初始化容量,取默认容量和 当前元素个数 最大值
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
} ensureExplicitCapacity(minCapacity);
} private void ensureExplicitCapacity(int minCapacity) {
modCount++; // overflow-conscious code
if (minCapacity - elementData.length > 0)
//容量不够,做扩容
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 = 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);
}

可以看到,调整容量的过程其实是扩容了数组的容量,并在最后调用了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;
}

用一张图来表示是这样的

除了上面的add 方法外,ArrayList还提供了几个添加操作的方法,分别是

//在指定位置添加元素
public void add(int index, E element) {
//判断index是否在size范围内
rangeCheckForAdd(index); ensureCapacityInternal(size + 1); // Increments modCount!!
//整体复制,并后移一位
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
//添加整个集合
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;
}

这三个方法虽然也是插入的操作,但其实最后都是调用 System 的 arraycopy 方法做一个整体的复制,这是一个native的方法,功能就是把数组做一个整体的复制,并向后移了一位。如果要复制的元素很多,那么就比较耗费性能,这是比较不好的一点。

所以,一般情况下,ArrayList适合顺序添加的情景。

查询元素

E elementData(int index) {
return (E) elementData[index];
} public E get(int index) {
rangeCheck(index); return elementData(index);
}

查询的代码比较简单,都是直接返回数组对应位置的元素,充分利用了数组根据索引查询元素的优势吗,因此效率较高。

扩展:说到查询,来提一个知识点,那就是遍历元素的效率问题,前面说了,ArrayList 实现了RandomAccess 接口,所以 遍历ArrayList 的元素 get() 获取元素在效率上是优于迭代器的,至于原因,在 《Java集合类:"随机访问" 的RandomAccess接口》有介绍,这里不进行叙述了。

修改元素

public E set(int index, E element) {
rangeCheck(index); E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}

也是根据索引操作数组 ,不多说。

删除元素

ArrayList删除元素的方法比较多,但说起来无非是三类,

1、按照下标删除元素

2、按照元素删除,这会删除ArrayList中与指定要删除的元素匹配的第一个元素

3、清除数组元素

前面两种虽然功能不同,但代码的最终调用是差不多的,都是引用类似这段代码来解决问题:

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

这段代码的逻辑大概就是把指定元素后面的所有元素整体复制并向前移动一个位置,然后最后一个元素置为null,跟插入中的部分方法逻辑很像,都是调用 System.arraycopy 来做数组操作,所以说,删除元素的效率其实也是不高的。

而第三类删除的方法其实是调用 clear

public void clear() {
modCount++; // clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null; size = 0;
}

把数组的每个元素都置为null,并把size置为0,就这么简单。

为什么用 "transient" 修饰数组变量

最后一个问题,之前说了ArrayList 可以被序列化,但其数组变量却是用transient 修饰,这是为什么呢?按照 五月的仓颉 大神这篇文章的解释就是:

序列化ArrayList的时候,ArrayList里面的elementData未必是满的,比方说elementData有10的大小,但是我只用了其中的3个,那么是否有必要序列化整个elementData呢?显然没有这个必要,因此ArrayList中重写了writeObject方法:

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();
}
}

每次序列化的时候调用这个方法,先调用defaultWriteObject()方法序列化ArrayList中的非transient元素,elementData不去序列化它,然后遍历elementData,只序列化那些有的元素,这样:

1、加快了序列化的速度

2、减小了序列化之后的文件大小

不得不说,设计者还是很用心良苦的。

总结

ArrayList 的源码分析就到这里了,因为其底层只有数组,所以源码的逻辑还是比较简单,比起HashMap这样的学习起来要轻松多了 (HashMap源码学习过程回想起来真是痛苦啊

Java集合类源码解析:ArrayList的更多相关文章

  1. Java集合类源码解析:Vector

    [学习笔记]转载 Java集合类源码解析:Vector   引言 之前的文章我们学习了一个集合类 ArrayList,今天讲它的一个兄弟 Vector.为什么说是它兄弟呢?因为从容器的构造来说,Vec ...

  2. Java集合类源码解析:HashMap (基于JDK1.8)

    目录 前言 HashMap的数据结构 深入源码 两个参数 成员变量 四个构造方法 插入数据的方法:put() 哈希函数:hash() 动态扩容:resize() 节点树化.红黑树的拆分 节点树化 红黑 ...

  3. Java集合类源码解析:AbstractMap

    目录 引言 源码解析 抽象函数entrySet() 两个集合视图 操作方法 两个子类 参考: 引言 今天学习一个Java集合的一个抽象类 AbstractMap ,AbstractMap 是Map接口 ...

  4. Java集合类源码解析:LinkedHashMap

    前言 今天继续学习关于Map家族的另一个类 LinkedHashMap .先说明一下,LinkedHashMap 是继承于 HashMap 的,所以本文只针对 LinkedHashMap 的特性学习, ...

  5. Java集合类源码解析:AbstractList

    今天学习Java集合类中的一个抽象类,AbstractList. 初识AbstractList AbstractList 是一个抽象类,实现了List<E>接口,是隶属于Java集合框架中 ...

  6. JDK8集合类源码解析 - ArrayList

    ArrayList主要要注意以下几点: 1构造方法 2添加add(E e) 3 获取 get(int index) 4 删除 remove(int index)    ,   remove(Objec ...

  7. 【转】Java HashMap 源码解析(好文章)

    ­ .fluid-width-video-wrapper { width: 100%; position: relative; padding: 0; } .fluid-width-video-wra ...

  8. JDK8集合类源码解析 - HashSet

    HashSet 特点:不允许放入重复元素 查看源码,发现HashSet是基于HashMap来实现的,对HashMap做了一次“封装”. private transient HashMap<E,O ...

  9. Java——LinkedHashMap源码解析

    以下针对JDK 1.8版本中的LinkedHashMap进行分析. 对于HashMap的源码解析,可阅读Java--HashMap源码解析 概述   哈希表和链表基于Map接口的实现,其具有可预测的迭 ...

随机推荐

  1. 常用的js效果

    使用jquery实现鼠标悬停显示层 <!DOCTYPE html> <html> <head> <meta charset="utf-8" ...

  2. elasticsearch 占CPU过高

    一.线上有一台服务器cpu一直跑满,最终定位导是elasticsearch导致的 二.通过一波查找更改jvm和删除 修改后没有生效笔记尴尬 然后网友说删除索引试了试就可以了  哈哈 curl http ...

  3. HTML块元素,行内元素,类,头部元素

    总结HTML块元素,行内元素,类,头部元素 块元素: 在HTML中,块级元素的高度为其内容的高度,宽度会扩展到与父容器同宽.默认情况下,块级元素会独占一行,并且元素前后行留空. 示例:<h1&g ...

  4. 安卓startActivityForResult用法

    startActivityForResult的作用就是它可以回传数据,假如我们有两个页面A和B,点击A页面的一个按钮,进入下一个页面B,进入页面B后,进行设置操作,并在finish()或者back后, ...

  5. RabbitMQ 集群原理和完善

    一.RabbitMQ集群方案的原理 RabbitMQ这款消息队列中间件产品本身是基于Erlang编写,Erlang语言天生具备分布式特性(通过同步Erlang集群各节点的magic cookie来实现 ...

  6. js发送post请求,实现下载文件

    由于业务需求要下载文件的功能: <!DOCTYPE html> <html> <head> <meta charset="utf-8"&g ...

  7. MapReduce计算模型的优化

    MapReduce 计算模型的优化涉及了方方面面的内容,但是主要集中在两个方面:一是计算性能方面的优化:二是I/O操作方面的优化.这其中,又包含六个方面的内容. 1.任务调度 任务调度是Hadoop中 ...

  8. [Swift]LeetCode8. 字符串转整数 (atoi) | String to Integer (atoi)

    Implement atoi which converts a string to an integer. The function first discards as many whitespace ...

  9. [Swift]LeetCode214. 最短回文串 | Shortest Palindrome

    Given a string s, you are allowed to convert it to a palindrome by adding characters in front of it. ...

  10. [Swift]LeetCode529. 扫雷游戏 | Minesweeper

    Let's play the minesweeper game (Wikipedia, online game)! You are given a 2D char matrix representin ...