ArrayList

描述

  • 继承AbstractList类,实现List接口
  • 实现了RandmoAccess接口,即提供了随机访问功能
  • 实现了Cloneable接口,即覆盖了函数clone(),能被克隆。
  • 实现java.io.Serializable接口,这意味着ArrayList支持序列化,能通过序列化去传输。
  • 基于泛型动态数组(List)扩容(碰到数组先想到连续的内存空间,故空间效率不高)
  • ArrayList可以以O(1)的时间复杂度去根据下标访问元素。(时间效率很高)
  • 线程不安全的,允许元素为null

重要的对象

elementData (Object类型的数组)

size(int 类型)

遍历使用

使用for循环,通过索引随机访问效率最高,其次是foreach循环,最慢是迭代器循环

foreach 最终会被转换成迭代器遍历的形式,所以是不如for循环的

与Collection关系

ArrayList属性

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable {
// 序列化id
private static final long serialVersionUID = 8683452581122892189L;
// 默认初始的容量
private static final int DEFAULT_CAPACITY = 10;
// 一个空对象
private static final Object[] EMPTY_ELEMENTDATA = new Object[0];
// 一个空对象,如果使用默认构造函数创建,则默认对象内容默认是该值
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = new Object[0];
// 当前数据对象存放地方,当前对象不参与序列化
transient Object[] elementData;
// 当前数组长度
private int size;
// 数组最大长度
private static final int MAX_ARRAY_SIZE = 2147483639;
}

扩展:什么是序列化(这里序列化又是一大知识点)

序列化是指:将对象转换成以字节序列的形式来表示,以便用于持久化和传输。

实现方法:实现Serializable接口。

然后用的时候拿出来进行反序列化即可又变成Java对象。

transient关键字解析

Java中transient关键字的作用,简单地说,就是让某些被修饰的成员属性变量不被序列化。

有了transient关键字声明,则这个变量不会参与序列化操作,即使所在类实现了Serializable接口,反序列化后该变量为空值。

那么问题来了:ArrayList中数组声明:transient Object[] elementData;,事实上我们使用ArrayList在网络传输用的很正常,并没有出现空值。

原来:ArrayList在序列化的时候会调用writeObject()方法,将sizeelement写入ObjectOutputStream;反序列化时调用readObject(),从ObjectInputStream获取sizeelement,再恢复到elementData

那为什么不直接用elementData来序列化,而采用上诉的方式来实现序列化呢?

原因在于elementData是一个缓存数组,它通常会预留一些容量,等容量不足时再扩充容量,那么有些空间可能就没有实际存储元素,采用上诉的方式来实现序列化时,就可以保证只序列化实际存储的那些元素,而不是整个数组,从而节省空间和时间

所以ArrayList的设计者将elementData设计为transient,然后在writeObject方法中手动将其序列化,并且只序列化了实际存储的那些元素,而不是整个数组。

见源码:

// Write out all elements in the proper order.for (int i=0; i<size; i++) {    s.writeObject(elementData[i]);}

从源码中,可以观察到 循环时是使用i<size而不是 i<elementData.length,说明序列化时,只需实际存储的那些元素,而不是整个数组。

ArrayList构造方法

无参构造

public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

默认无参构建方法创建ArrayList对象

new ArrayList() 创建对象,数组长度size=0,而elementData数组的长度为size+1,

第一次add时,elementData会变成默认长度=10(DEFAULT_CAPACITY)

int类型参数的构造方法

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

initialCapacity指定初始数组长度。

Collection对象的构造方法

 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;
}
}
  • 将collection对象转换成数组,然后将数组的地址赋给elementData。
  • 更新size的值,同时判断size的大小,如果是size等于0,直接将空对象EMPTY_ELEMENTDATA的地址赋给elementData
  • 如果size的值大于0,则执行Arrays.copyOf方法,把collection对象的内容(可以理解为深拷贝)copy到elementData中。

注意:this.elementData = arg0.toArray(); 这里执行的简单赋值是浅拷贝,所以要执行Arrays.copyOf做深拷贝

ArrayList普通方法

add(E e)方法

  /**
*增加指定的元素到ArrayList的最后位置
* @param e 要添加的元素
* @return
*/
public boolean add(E e) {
// 确定ArrayList的容量大小---严谨
// 注意:size + 1,保证资源空间不被浪费,
// ☆☆☆按当前情况,保证要存多少个元素,就只分配多少空间资源
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
/**
* 私有方法:明确 ArrayList 的容量,提供给本类使用的方法
* - 用于内部优化,保证空间资源不被浪费:尤其在 add() 方法添加时起效
* @param minCapacity 指定的最小容量
*/
private void ensureCapacityInternal(int minCapacity) {
// 若 elementData == {},则取 minCapacity 为 默认容量和参数 minCapacity 之间的最大值
// 注:ensureCapacity() 是提供给用户使用的方法,在 ArrayList 的实现中并没有使用
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity= Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
/**
* 私有方法:明确 ArrayList 的容量
* - 用于内部优化,保证空间资源不被浪费:尤其在 add() 方法添加时起效
* @param minCapacity 指定的最小容量
*/
private void ensureExplicitCapacity(int minCapacity) {
// 将“修改统计数”+1,该变量主要是用来实现fail-fast机制的
modCount++;
// 防止溢出代码:确保指定的最小容量 > 数组缓冲区当前的长度
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
} /**
* 数组缓冲区最大存储容量
* - 一些 VM 会在一个数组中存储某些数据--->为什么要减去 8 的原因
* - 尝试分配这个最大存储容量,可能会导致 OutOfMemoryError(当该值 > VM 的限制时)
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; /**
* 私有方法:扩容,以确保 ArrayList 至少能存储 minCapacity 个元素
* - 扩容计算:newCapacity = oldCapacity + (oldCapacity >> 1); 扩充当前容量的1.5倍
* @param minCapacity 指定的最小容量
*/
private void grow(int minCapacity) {
// 防止溢出代码
int oldCapacity = elementData.length;// 旧容量大小
int newCapacity = oldCapacity + (oldCapacity >> 1); // 新容量为旧容量的1.5倍
if (newCapacity - minCapacity < 0) // 若 newCapacity新容量小于参数指定容量,修改新容量
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0) // 若 newCapacity 大于最大存储容量,则进行大容量分配
newCapacity = hugeCapacity(minCapacity);
// 拷贝扩容
elementData = Arrays.copyOf(elementData, newCapacity);
}
/**
* 私有静态方法:大容量分配,最大分配 Integer.MAX_VALUE
* @param minCapacity
*/
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}

1)确保数组已使用长度(size)加1之后足够存下 下一个数据

2)修改次数modCount 标识自增1,如果当前数组已使用长度(size)加1后的大于当前的数组长度,则调用grow方法,增长数组,grow方法会将当前数组的长度变为原来容量的1.5倍。

3)确保新增的数据有地方存储之后,则将新元素添加到位于size的位置上。

4)返回添加成功布尔值。

remove()方法

删除指定位置的元素。

//删除指定位置的元素,返回被删除的元素
public E remove(int index) {
rangeCheck(index); modCount++; //被删除时 modCount也会改变
E oldValue = elementData(index); int numMoved = size - index - 1;
if (numMoved > 0)
// 将 index + 1 及之后的元素向前移动一位,覆盖被删除值
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//将最后一个元素置空,并将 size 值减1
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;
}

快速删除方法

没有做边界检查,直接进行删除操作

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
}

删除操作的步骤:

  1. 获取指定位置 index 处的元素值
  2. 将 index + 1 及之后的元素向前移动一位
  3. 将最后一个元素置空,并将 size 值减 1
  4. 返回被删除值,完成删除操作

快速失败机制

在 Java 集合框架中,很多类都实现了快速失败机制。该机制被触发时,会抛出并发修改异常ConcurrentModificationException

  • < p > < name = "快速失败" >

*该类的{@link #iterator() iterator}和返回的迭代器

  • {@link #listIterator(int) listIterator}方法是fail-fast:

*如果列表在迭代器之后的任何时候在结构上被修改

*创建的,除了通过迭代器自己创建之外的任何方式

  • {@link ListIterator#remove() remove}或

  • {@link ListIterator#add(Object) add}方法,迭代器将抛出一个

  • {@link ConcurrentModificationException}。于是,面对

*并发修改,迭代器会快速而干净地失败

*而不是冒着不确定行为的风险

未来的时间。

注意,不能保证迭代器的快速故障行为

*一般来说,在……方面是不可能作出任何硬性保证的

*存在不同步的并发修改。快速失败迭代器

*尽最大努力抛出{@code ConcurrentModificationException}。

因此,编写依赖于此的程序是错误的

*正确性异常:迭代器的快速故障行为

*应该只用于检测bug。

这个类是

  • < a href = " {@docRoot} / . . /技术说明/指导/收藏/ index . html " >

  • Java集合框架。

源码注释总结:

在遇到并发修改时,迭代器会尽最大努力抛出ConcurrentModificationException这个异常,

但不能百分之百的保证,而迭代器的快速失败,会避免程序在将来不确定的时间里出现不确定的行为。

思考

为什么ArrayList继承了AbstractList还要实现List接口 ?

防止在代理时出错

其实没有必要,实现与不实现都是可以的。

System.arraycopy()和Arrays.copyof()

  1. Arrays.copyOf()内部还调用了System.arraycopy()这个方法
  2. System.arraycopy()需要传一个数组的参数进行拷贝。
  3. 而Arrays.copyof()自己内部会创建一个新的数组进行拷贝

集合ArrayList分析的更多相关文章

  1. Java 集合 ArrayList和LinkedList的几种循环遍历方式及性能对比分析 [ 转载 ]

    Java 集合 ArrayList和LinkedList的几种循环遍历方式及性能对比分析 @author Trinea 原文链接:http://www.trinea.cn/android/arrayl ...

  2. Java入门系列之集合ArrayList源码分析(七)

    前言 上一节我们通过排队类实现了类似ArrayList基本功能,当然还有很多欠缺考虑,只是为了我们学习集合而准备来着,本节我们来看看ArrayList源码中对于常用操作方法是如何进行的,请往下看. A ...

  3. Java ArrayList和Vector、LinkedList与ArrayList、数组(Array)和列表集合(ArrayList)的区别

    ArrayList和Vector的区别ArrayList与Vector主要从二方面来说.  一.同步性:   Vector是线程安全的,也就是说是同步的,而ArrayList是线程序不安全的,不是同步 ...

  4. 集合ArrayList

    /*集合ArrayList * 例如: * 1.创建:ArrayList<Egg> myList = new ArrayList<Egg>(); *      Egg类型的集合 ...

  5. 面向对象之集合ArrayList

    using System; using System.Collections; using System.Collections.Generic; using System.Linq; using S ...

  6. 集合及特殊集合arrayList

    1,运用集合  arrayList 首先复制Colections加  : 创建arrayList ar =new arrayList(); ArrayList具体提供的功能:属性            ...

  7. 数组Array和列表集合ArrayList、LinkedList和Vector的区别

    一.ArrayList和Vector的区别 ArrayList与Vector主要从以下方面来说. 1.同步性: Vector是线程安全的,也就是说是同步的,而ArrayList是线程序不安全的,不是同 ...

  8. 第三章泛型集合ArrayList 和Hashtable

    第三章泛型集集合 ArrayList 变量名 = new ArrayList();  //相当与一个容器 他的执行using 是  using System.Collections; 变量名.ADD( ...

  9. JAVA基础——集合——ArrayList

    ArrayList集合 ArrayList的一些方法(JDK1.8): 将指定的元素附加到列表的末尾,true:添加成功,false:添加失败: public boolean add(E e)    ...

随机推荐

  1. 使用webpack---安装webpack和webpack-dev-server

    1.先确保安装了最新版的Node.js和NPM,并已经了解NPM的基本用法 (以下使用cmd命令行进行) 2.安装webpack (1)全局安装 $ npm install webpack -g   ...

  2. 二.安全NA之ASA基础

    一.ASA常用命令 show run interface #查看接口配置 show ip address #查看IP地址 show conn #查看防火墙状态信息,U代表up:I,代表进流量:O,代表 ...

  3. Linux use apktool problem包体变大GLIBC2.14等问题

    Linux服务器在线打包遇到的问题 转载请标明出处: https://dujinyang.blog.csdn.net/article/details/80110942 本文出自:[奥特曼超人的博客] ...

  4. JSON格式提取相同属性的某个值

    [ {UID:"222",value:"111"}, {UID:"222",value:"103"}, {UID:&qu ...

  5. HBase的安装和使用

    文章作者:foochane  原文链接:https://foochane.cn/article/2019062801.html 1 Hbase基本介绍 Hbase是一个分布式数据库,可以提供数据的实时 ...

  6. Python 03 整型、字符串

    1. 整型和布尔值 1.1 整型——数字(int) 用于比较和运算.  整型32位:-2**31 ~ -2**31-1 整型64位:-2**63 ~ -2**63-1 python2 :整型 int ...

  7. 对IOC和DI的通俗理解

    学习过spring框架的人一定都会听过Spring的IoC(控制反转) .DI(依赖注入)这两个概念,对于初学Spring的人来说,总觉得IoC .DI这两个概念是模糊不清的,是很难理解的,今天和大家 ...

  8. (二)快速搭建 ASP.net core Web 应用

    目录 1. 新建项目并上传Github 2. 关联Jenkins实现持续集成 3. 已经磨好枪了,开始写代码 1. 新建项目并上传Github 新建 ASP.NET Core Web 应用程序,勾选“ ...

  9. Zookeeper之Leader选举过程

    Leader在集群中是一个非常重要的角色,负责了整个事务的处理和调度,保证分布式数据一致性的关键所在.既然Leader在ZooKeeper集群中这么重要所以一定要保证集群在任何时候都有且仅有一个Lea ...

  10. P3317 [SDOI2014]重建 变元矩阵树定理 高斯消元

    传送门:https://www.luogu.org/problemnew/show/P3317 这道题的推导公式还是比较好理解的,但是由于这个矩阵是小数的,要注意高斯消元方法的使用: #include ...