一、概述

ArrayList是java中十分常用的集合类,继承于AbstractList,并实现了List、RandomAccess、Cloneable和Serializable接口。ArrayList底层是使用数组来实现的,是一个动态的数组队列,它具有以下特点。

  • 可以动态扩容、缩容
  • ArrayList的操作不是线程安全的
  • 允许元素重复,允许元素为空

ArrayList初始默认大小是10,每次扩容时是原大小的1.5倍,如果一开始就知道需要的Lsit长度,可以指定ArrayList的长度,减少扩容操作。

二、源码分析(JDK 1.8)

 2.1 ArrayList属性

// 默认的容量
private static final int DEFAULT_CAPACITY = 10;
// 保存ArrayList中数据使用的数组
transient Object[] elementData
// ArrayList的元素个数
private int size;

此外还有一个成员变量modCount,这个变量用来记录修改次数,增删改的都会改变modCount的值。ArrayList是线程不安全的,如果在使用迭代器的过程中有其他线程修改了List,那么将抛出ConcurrentModificationException,是Fail-Fast策略的应用。

2.2 构造函数

  // 构造一个初始容量为10的空列表
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
// 构造具有指定容量的空列表
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);
}
} // 构造一个包含指定容器元素的列表,按照容器迭代器返回的顺序排列
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;
}
}

ArrayList有三个构造函数,如上代码所示。

2.3  trimToSize()

  public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}

  trimToSize()方法可以将ArrayList的底层数组缩容到当前ArrayList中元素的个数(ArrayList的size值),使用这个方法可以最小化ArrayList使用的存储空间。

2.4 添加

       // 在列表尾部添加元素
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
// 在指定位置添加指定元素
public void add(int index, E element) {
rangeCheckForAdd(index); ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
// 将集合c中元素添加到列表尾部
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;
}

  add方法中都用到了ensureCapacityInternal方法,这个方法用于对ArrayList进行扩容。

2.5 扩容

     // 扩容操作
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);
}
// 增加容量,使列表可以的容量至少满足minCapacity个元素
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
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);
}

ensureExplicitCapacity()方法先判断当前数组的容量是否可以满足需求,如果不满足就使用grow进行扩容操作,每次扩容为原来的1.5倍。

2.6 删除

     // 删除指定位置的元素,并返回被删除的元素
public E remove(int index) {
// 范围检查
rangeCheck(index); modCount++;
E oldValue = elementData(index);
// 需要移动的元素数
int numMoved = size - index - 1;
if (numMoved > 0)
// 等于0表示删除的是末尾的元素,不需要移动
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work return oldValue;
}

E remove(int index) 删除指定位置的元素

    // 范围检查
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

删除前进行边界检查

     // 删除列表中首次出现的指定元素,如果有多个只删除第一个
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; // clear to let GC do its work
}

public boolean remove(Object o) 删除首次出现的指定元素,若有多个只删除第一个

      // 删除指定范围的元素
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;
}

void removeRange(int fromIndex, int toIndex) 删除[fromIndex, toIndex)范围的元素

     // 从列表中删除指定集合c中的元素
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, false);
}
// 列表中保留指定集合c中的元素
public boolean retainAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, true);
}
// 从List的底层数组中删除或者保留指定集合中元素的值,complement为true是保留指定集合元素,为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++)
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;
}
// w==size表示全部元素保留,没有删除操作发生,否则返回true,并更改数组
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;
}

在try块中首先遍历数组,如果要保留集合c中的元素就把相同元素移到数组前段,如果要删除集合c中的数据就把不同元素移到数组前段,将剩下的数据保存在0到w之间,

第一个if语句是为了处理c.contains()抛出异常时执行;第二个if语句是处理w之后的空间。

Java容器学习之ArrayList的更多相关文章

  1. Java容器学习——List

    Java容器学习--List 基础知识 数组: ​ 优点:随机存取,可以快速访问元素 ​ 缺点:静态分配内存,存在空间闲置或者溢出现象:不适合进行插入和删除操作,需要移动大量元素. 链表: ​ 优点: ...

  2. java容器学习

    容器是java中重要的一部分,其接口的结构如下 Collection | ------------------ Map | | | Set List HashMap | HashSet 顾名思义,容器 ...

  3. Java API学习(一) ArrayList源码学习

    ArrayList在平常用的还挺多的,用起来十分舒服,顺手.这里来学习一下它的源码. 类定义 下面是类的定义: public class ArrayList<E> extends Abst ...

  4. java容器的数据结构-ArrayList,LinkList,HashMap

    ArrayList: 初始容量为10,底层实现是一个数组,Object[] elementData 自动扩容机制,当添加一个元素时,数组长度超过了elementData.leng,则会按照1.5倍进行 ...

  5. java—容器学习笔记

    一:迭代器 刚开始学容器,做了个简单的练习题.. import java.util.ArrayList; import java.util.Collection; import java.util.I ...

  6. java容器学习笔记

    容器 容器的组成 容器有两个接口Map和Collection. collection接口有List类和set类. List类可以分为:Vector.LinkedList.ArrayList.CopyO ...

  7. Java容器学习之List

    List接口继承了Collcetion接口,Collection接口又继承了超级接口Iterable,List是有序列表,实现类有ArrayList.LinkedList.Vector.Stack等. ...

  8. Java集合类学习-LinkedList, ArrayList, Stack, Queue, Vector

    Collection List 在Collection的基础上引入了有序的概念,位置精确:允许相同元素.在列表上迭代通常优于索引遍历.特殊的ListIterator迭代器允许元素插入.替换,双向访问, ...

  9. Java基础学习笔记六 Java基础语法之类和ArrayList

    引用数据类型 引用数据类型分类,提到引用数据类型(类),其实我们对它并不陌生,如使用过的Scanner类.Random类.我们可以把类的类型为两种: 第一种,Java为我们提供好的类,如Scanner ...

随机推荐

  1. 超简单集成HMS Scan Kit扫码SDK,轻松实现扫码购

    前言   在前面的文章中,我们向大家介绍了HMS Scan Kit 的快速集成方法以及HMS Scan Kit和其他开源扫码工具的竞争力对比分析,如果没有看到也没关系,文章下方的往期链接中有文章入口. ...

  2. Syntax error, insert "}" to complete MethodBody

    jsp中代码在Eclipse中打开正常,导入项目导入MyEclipse后显示如下异常: Syntax error, insert "}" to complete MethodBod ...

  3. rhel7 rpmbuild 制作二进制程序安装包(.rpm) 简单示例

    下载rpm-build: # yum install rpm-build 如果上述方式无法安装(没配置网络源,虚拟机下是安装媒介源) 可以用下列方式下载后再安装(实践结果可能版本问题引起的缺少太多的* ...

  4. CSS粘性定位

    粘性定位(position:sticky) 1.定义 粘性定位可以被认为是相对定位和固定定位的混合.元素在跨越特定阈值前为相对定位,之后为固定定位.(MDN传送门) 这个特定阈值指的是 top, ri ...

  5. POI2014 FAR-FarmCraft

    [Farm Craft] [题目描述] mhy住在一棵有n个点的树的1号结点上,每个结点上都有一个妹子. mhy从自己家出发,去给每一个妹子都送一台电脑,每个妹子拿到电脑后就会开始安装zhx牌杀毒软件 ...

  6. 【区间DP】低价回文

    [区间DP]低价回文 标签(空格分隔): 区间DP 回文词 [题目描述] 追踪每头奶牛的去向是一件棘手的任务,为此农夫约翰安装了一套自动系统.他在每头牛身上安装了一个电子身份标签,当奶牛通过扫描器的时 ...

  7. [SpringBoot]SpringBoot中使用redis事务

    本文基于SpringBoot 2.X 事务在关系型数据库的开发中经常用到,其实非关系型数据库,比如redis也有对事务的支持,本文主要探讨在SpringBoot中如何使用redis事务. 事务的相关介 ...

  8. 使用Splunk监控SAP Dump

    最近在尝试使用Splunk对SAP系统进行监控,以Dump监控为例,总结了一点相关信息,记录在这里. 本文链接:https://www.cnblogs.com/hhelibeb/p/13260385. ...

  9. MCU 51-3定时器

    51定时/计数器简介 51单片机有2个16位定时器/计数器:定时器0(T0为P3.4)和定时器1(T1为P3.5).这里所说的16位是指定时/计数器内部分别有16位的计数寄存器. 当工作在定时模式时, ...

  10. CMDB02/ 单例模式、资产采集参考命令、日志处理

    CMDB02/单例模式.资产采集参考命令.日志处理 目录 CMDB02/单例模式.资产采集参考命令.日志处理 1. 单例模式 1.1 多例模式 1.2 单例模式 1.2.1 单例模式/错误 1.2.2 ...