ArrayList简介

从上图可以看到,ArrayList是集合框架中List接口的一个实现类,它继承了AbstractList类,实现了List, RandomAccess, Cloneable, Serializable。

  • 实现List接口,对数组的基本增删改查操作。
  • 实现RandomAccess接口,快速随机访问功能。
  • 实现Cloneable接口,可以被复制,clone()方法。
  • 实现Serializable接口,支持序列化,可以序列化传输。保证类的一致。

在使用基本数组保存数据的时候,数组长度固定对于增删数据来说非常不方便,ArrayList可以动态的增删数组中的元素,和Vector但是ArrayList是不同步的,线程不安全的。

ArrayList成员变量

类的成员变量介绍:

//可序列化版本号
private static final long serialVersionUID = 8683452581122892189L; //初始化默认数组长度为10
private static final int DEFAULT_CAPACITY = 10; //空数组,用户指定数组长度为0时返回此数组
private static final Object[] EMPTY_ELEMENTDATA = {}; //空数组,当用户没有指定数组长度时返回此数组,
//用户第一次添加元素时,数组扩容成默认容量DEFAULT_CAPACITY
//此数组时默认返回的,而EMPTY_ELEMENTDATA数组返回是在用户指定数组长度为0时返回的。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; //用来缓存数组存储的元素,ArrayList的容量和他的长度相等,
//第一次添加元素时会被扩充至DEFAULT_CAPACITY
transient Object[] elementData; //ArrayList的长度,包含元素个数
private int size;

构造方法

1. 参数为指定容量:
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
//创建传入大小的elementData数组
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
//传入长度为0时空数组
this.elementData = EMPTY_ELEMENTDATA;
} else {
//传入负数报错
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
2. 空参构造
public ArrayList() {
//创建默认空数组,初始化长度为10
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
3. 参数为集合元素
public ArrayList(Collection<? extends E> c) {
//传入集合转换为数组赋给elementData
elementData = c.toArray();
if ((size = elementData.length) != 0) {
//如果不是Object类型的数组,转化为Object类型数组
//调用了Arrays的copyof方法,在Arrays中用System.arraycopy拷贝数组实现
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
//长度为0,空数组
this.elementData = EMPTY_ELEMENTDATA;
}
}

ArrayList关于容量的方法

1. trimToSize改变数组长度为实际长度

集合中实际的元素个数size和数组长度elementData.length可能并不相等,在不需要长度大于实际元素个数时,可以使用此方法,减少内存浪费。

public void trimToSize() {
//每次对数组进行修改操作都会modCount自加,理解为数组版本号
modCount++;
if (size < elementData.length) {
//实际元素个数小于数组长度并且实际元素个数为0时返回空数组
//否则复制数组并且长度为以及元素实际个数的数组
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
2. ensureCapacity确认数组长度如果需要就扩容
public void ensureCapacity(int minCapacity) {
//传入的最小容量值大于目前数组长度,并且当前数组不是默认空数组,
//并且传入的容量值大于默认容量值(10),对数组增加为传入的容量值
if (minCapacity > elementData.length
&& !(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
&& minCapacity <= DEFAULT_CAPACITY)) {
//对数组操作,数组版本自加
modCount++;
//增加数组长度
grow(minCapacity);
}
}
3. grow扩容
private Object[] grow(int minCapacity) {
//返回数组扩容后的数组,具体会不会扩充到传入的容量newCapacity方法再做判断
return elementData = Arrays.copyOf(elementData,
newCapacity(minCapacity));
}
private Object[] grow() {
//返回一个增加一个容量的数组
return grow(size + 1);
}
4. newCapacity容量判断

传入一个数组的容量,这个方法会判断这个容量,

private int newCapacity(int minCapacity) {
int oldCapacity = elementData.length;
//新的容量为原容量1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果这个1.5倍的容量小于传入的容量
if (newCapacity - minCapacity <= 0) {
//如果数组是空数组
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
//返回默认容量10和传入的容量中的大值
return Math.max(DEFAULT_CAPACITY, minCapacity);
//如果传入的容量为负数报错
if (minCapacity < 0)
throw new OutOfMemoryError();
//返回传入的容量(1.5倍的容量小于传入的容量并且数组不是空数组并且传入的容量大于0)
return minCapacity;
}
//传入的容量是不是小于最大容量返回传入的容量,否则返回最大容量判断
return (newCapacity - MAX_ARRAY_SIZE <= 0)
? newCapacity
: hugeCapacity(minCapacity);
}
5. hugeCapacity最大容量判断

判断传入的容量值是不是大于最大容量值

private static int hugeCapacity(int minCapacity) {
//如果传入的容量小于0报错
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
//如果传入的容量值大于最大容量值返回最大的Integer值2^31次方,2147483648
return (minCapacity > MAX_ARRAY_SIZE)
? Integer.MAX_VALUE
: MAX_ARRAY_SIZE;
}

最大容量值定义:

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

最大的Integer数-8,为什么要减去8?

  • 为了存储数组的头信息
  • 为了防止内存溢出

增删改查及其他方法

1. size返回集合长度
public int size() {
return size;
}
2. isEmpty是否为空
public boolean isEmpty() {
return size == 0;
}
3. contains是否包含某个元素
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
4. indexOf查找元素索引
public int indexOf(Object o) {
//如果传入的元素是空的判断
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
//遍历数组查找值相同的元素返回其索引
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
//没有这个元素返回-1
return -1;
}
5. lastIndexOf元素最后出现的索引
public int lastIndexOf(Object o) {
//传入元素是空的判断
if (o == null) {
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
//从后向前遍历数组如果值相等返回索引
for (int i = size-1; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
//没有找到返回-1
return -1;
}
6. clone克隆ArrayuList
public Object clone() {
try {
//声明一个新的ArrayList v
ArrayList<?> v = (ArrayList<?>) super.clone();
//调用Arrays类的copyOf方法复制数组传给v的elementData
v.elementData = Arrays.copyOf(elementData, size);
//将v的modCount初始化为0
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
throw new InternalError(e);
}
}
7. toArray转换为数组
public Object[] toArray() {
//集合的elementData复制一份返回
//为了安全没有直接返回这个集合的elementData数组而是新复制一份
return Arrays.copyOf(elementData, size);
}
8. toArray(T[] a)转换为数组
public <T> T[] toArray(T[] a) {
if (a.length < size)
//传入的数组长度小于集合长度,创建一个新的数组复制元素返回
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
//复制集合元素给a数组
System.arraycopy(elementData, 0, a, 0, size);
//如果a的长度大于集合的长度 将最后数组一位置null
if (a.length > size)
a[size] = null;
return a;
}
9. get获取索引处元素
public E get(int index) {
//判断传入索引是否合法再0到size之间
Objects.checkIndex(index, size);
//返回对应索引处的元素
return elementData(index);
}
10.set替换指定索引处的元素
public E set(int index, E element) {
//判断传入索引是否合法
Objects.checkIndex(index, size);
//先取到索引处原来的元素
E oldValue = elementData(index);
//给索引处赋新元素
elementData[index] = element;
//返回索引处原来的元素
return oldValue;
}
11.add添加一个元素
private void add(E e, Object[] elementData, int s) {
//数组长度是否等于集合size
if (s == elementData.length)
//要添加元素数组长度不够所以扩容
elementData = grow();
//给size处赋传进来的元素e
elementData[s] = e;
size = s + 1;
}

直接向集合尾部添加元素方法:

public boolean add(E e) {
//对数组做修改操作modCount先自加
modCount++;
add(e, elementData, size);
return true;
}

向指定的索引位置添加一个元素:

public void add(int index, E element) {
//传入索引检查是否合法
rangeCheckForAdd(index);
//对元素修改操作modCount先自加
modCount++;
final int s;
Object[] elementData;
//判断数组长度与集合size是否相等
if ((s = size) == (elementData = this.elementData).length)
//相等的情况数组需要增容
elementData = grow();
//拷贝数组,将原数组索引处(包括索引)后的元素拷贝到新数组中,从index+1位置开始
System.arraycopy(elementData, index,
elementData, index + 1,
s - index);
//向索引处添加元素
elementData[index] = element;
//集合元素个数加一
size = s + 1;
}
12.remove删除元素

删除指定索引处的元素:

public E remove(int index) {
//判断传入索引是否合法
Objects.checkIndex(index, size);
//对元素修改操作modCount先自加
modCount++;
//取到索引处原来的元素
E oldValue = elementData(index);
//索引处元素删除后,需要向前移动的元素个数
int numMoved = size - index - 1;
if (numMoved > 0)
//将索引+1到数组结尾的元素复制到索引处到数组结尾
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//将size-1处的索引置为null垃圾回收
elementData[--size] = null;
//返回删除的元素
return oldValue;
}

删除指定元素:

public boolean remove(Object o) {
//需要删除的元素为null时
if (o == null) {
//遍历数组找到null元素的索引
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
//将索引位置删除
fastRemove(index);
return true;
}
} else {
//遍历数组找到o元素的索引
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
//将索引位置删除
fastRemove(index);
return true;
}
}
return false;
}

快速删除:

private void fastRemove(int index) {
//对元素修改操作modCount先自加
modCount++;
//索引处元素删除后,需要向前移动的元素个数
int numMoved = size - index - 1;
if (numMoved > 0)
//将索引+1到数组结尾的元素复制到索引处到数组结尾
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//将size-1处的索引置为null垃圾回收
elementData[--size] = null;
}
13.删除所有元素
public void clear() {
//对元素修改操作modCount先自加
modCount++;
final Object[] es = elementData;
for (int to = size, i = size = 0; i < to; i++)
es[i] = null;
}
14.addAll添加集合到集合

传入参数是要添加的集合:

public boolean addAll(Collection<? extends E> c) {
//将传入的集合转换为数组
Object[] a = c.toArray();
//对元素修改操作modCount先自加
modCount++;
int numNew = a.length;
//传入集合为空返回false
if (numNew == 0)
return false;
Object[] elementData;
final int s;
//当前数组长度不够保存新的数组元素时数组扩容
if (numNew > (elementData = this.elementData).length - (s = size))
elementData = grow(s + numNew);
//复制数组到扩容后的数组
System.arraycopy(a, 0, elementData, s, numNew);
//集合个数增加
size = s + numNew;
return true;
}

向指定索引处复制一个集合

public boolean addAll(int index, Collection<? extends E> c) {
//索引检查是否合法
rangeCheckForAdd(index);
//将传入的集合转换为数组
Object[] a = c.toArray();
//对元素修改操作modCount先自加
modCount++;
int numNew = a.length;
//传入集合为空返回false
if (numNew == 0)
return false;
Object[] elementData;
final int s;
//当前数组长度不够保存新的数组元素时数组扩容
if (numNew > (elementData = this.elementData).length - (s = size))
elementData = grow(s + numNew);
//将要复制到的索引位置到数组结尾的元素复制到当前位置+要复制的数组长度
int numMoved = s - index;
if (numMoved > 0)
System.arraycopy(elementData, index,
elementData, index + numNew,
numMoved);
//将要复制的数组复制到指定的索引处
System.arraycopy(a, 0, elementData, index, numNew);
//集合个数增加
size = s + numNew;
return true;
}
15.removeRange删除指定范围的元素
protected void removeRange(int fromIndex, int toIndex) {
//起始位置索引大于结束位置索引报错
if (fromIndex > toIndex) {
throw new IndexOutOfBoundsException(
outOfBoundsMsg(fromIndex, toIndex));
}
//对元素修改操作modCount先自加
modCount++;
shiftTailOverGap(elementData, fromIndex, toIndex);
}
private void shiftTailOverGap(Object[] es, int lo, int hi) {
//复制指定一段位置的元素向前滑动
System.arraycopy(es, hi, es, lo, size - hi);
//将复制过的元素置null
for (int to = size, i = (size -= hi - lo); i < to; i++)
es[i] = null;
}

ArrayList的迭代器

ArrayList的迭代器部分的源码分析见这里

总结

  1. 什么是ArrayList?

    是集合框架中List的一个实现类,底层是用数组实现的,可以动态的对数组进行增删改查,当容量不足时自动扩容,默认容量为10,自动扩充容量为当前容量的1.5倍,是用System.arraycopy()复制为新的数组实现的。查找快,但是增删性能较查,因为每次增删数据都会需要复制数组,线程不安全。

  2. ArrayList如何实现自动扩容?

    在grow()方法中实现扩容,具体的是在newCapacity()方法中来确定扩容的大小,自动扩容1.5倍,实现自动扩容是通过Arrays.copyof()复制原数组到新的容量的数组中去。

  3. ArrayList扩容最大容量?

    ArrayList不能无线扩容,最大容量为

    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    但是如果需要的容量大于上面的值,ArrayList会扩容到Integer.MAX_VALUE = 2 ^ 31,关于MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8为什么需要减8,首先数组需要保存头信息,其次是为了放置溢出。

Java集合源码分析之ArrayList的更多相关文章

  1. Java集合源码分析之ArrayList(JDK1.8)

    package annoction; import java.util.*; import java.util.function.Consumer; import java.util.function ...

  2. Java集合源码分析(二)ArrayList

    ArrayList简介 ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长,类似于C语言中的动态申请内存,动态增长内存. ArrayList不是线程安全的,只能用在单线程环境下,多线 ...

  3. java集合源码分析(三):ArrayList

    概述 在前文:java集合源码分析(二):List与AbstractList 和 java集合源码分析(一):Collection 与 AbstractCollection 中,我们大致了解了从 Co ...

  4. java集合源码分析(六):HashMap

    概述 HashMap 是 Map 接口下一个线程不安全的,基于哈希表的实现类.由于他解决哈希冲突的方式是分离链表法,也就是拉链法,因此他的数据结构是数组+链表,在 JDK8 以后,当哈希冲突严重时,H ...

  5. Java 集合源码分析(一)HashMap

    目录 Java 集合源码分析(一)HashMap 1. 概要 2. JDK 7 的 HashMap 3. JDK 1.8 的 HashMap 4. Hashtable 5. JDK 1.7 的 Con ...

  6. Java集合源码分析(四)Vector<E>

    Vector<E>简介 Vector也是基于数组实现的,是一个动态数组,其容量能自动增长. Vector是JDK1.0引入了,它的很多实现方法都加入了同步语句,因此是线程安全的(其实也只是 ...

  7. Java集合源码分析(三)LinkedList

    LinkedList简介 LinkedList是基于双向循环链表(从源码中可以很容易看出)实现的,除了可以当做链表来操作外,它还可以当做栈.队列和双端队列来使用. LinkedList同样是非线程安全 ...

  8. 集合源码分析[3]-ArrayList 源码分析

    历史文章: Collection 源码分析 AbstractList 源码分析 介绍 ArrayList是一个数组队列,相当于动态数组,与Java的数组对比,他的容量可以动态改变. 继承关系 Arra ...

  9. java集合源码分析几篇文章

    java集合源码解析https://blog.csdn.net/ns_code/article/category/2362915

随机推荐

  1. Get Random number

    , int pMaxVal = int.MaxValue) { int m = pMaxVal - pMinVal; int rnd = int.MinValue; decimal _base = ( ...

  2. Docker Machine批量安装docker host

    Dokcer Machine Docker Machine 可以批量安装和配置 docker host   提高docker的安装效率   同时减少人工安装操作的失误 [root@localhost ...

  3. box-sizing:border-boxing的使用

    <div class="box"></div> .box { margin-top: 200px; margin-left: 200px; backgrou ...

  4. Xpath语法详解

    1.简介 XPath是一门在XML和HTML文档中查找信息的语言,可以用来在XML和HTML文档中对元素和属性进行遍历 XPath的安装 Chrome插件XPath Helper 点Chrome浏览器 ...

  5. 关于优先队列浅析(priority_queue)

    优先队列 greater与less,自定义还有结构体(可以设置2层优先级)  模板; 下面废话不多说直接上程序 注释的很明白 #include<iostream> #include< ...

  6. 简介一下 i++和++i&&i=i+i,i+=1;的区别

    首先: int i=2; i++=2; ++i=3; 前者先显示当前的值,而后者则是先自增在显示值: second i=i+1和i+=1; 输出结果虽然一样,但是 1.byte i=2; i+=2; ...

  7. C# string 常用功能的方法扩展

    #region Usings using System; using System.Text; using System.Data; using System.Data.SqlClient; usin ...

  8. java操作JacocClient下载dump文件

    记录瞬间 import org.jacoco.core.data.ExecutionDataWriter; import org.jacoco.core.runtime.RemoteControlRe ...

  9. centos7安装git

    1.安装git依赖包 yum install curl-devel expat-devel gettext-devel openssl-devel zlib-devel gcc perl-ExtUti ...

  10. kubernetes 源码安装部署 1.12

    一. 前期准备 参考文档 https://jimmysong.io/kubernetes-handbook/practice/create-tls-and-secret-key.html 1. 安装g ...