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. v语言怎么玩

    直接上github: https://github.com/vlang/v 前戏 大概是在6月份的时候,在github上看到了这个玩意,我以为是??? 我下意识的去查了一下有没有人在讨论这个语言,但是 ...

  2. 【JVM从小白学成大佬】5.垃圾收集器及内存分配策略

    前面介绍了垃圾回收算法,接下来我们介绍垃圾收集器和内存分配的策略.有没有一种牛逼的收集器像银弹一样适配所有场景?很明显,不可能有,不然我也没必要单独搞一篇文章来介绍垃圾收集器了.熟悉不同收集器的优缺点 ...

  3. MapDB使用入门

    背景 MapDB官网:http://www.mapdb.org 官方翻译之后的话:MapDB基于堆外存储.磁盘存储提供了Java的Maps.Sets.Lists.Queues等功能.它混合了Java集 ...

  4. [开源] FreeSql.AdminLTE 功能升级

    前言 FreeSql 发布至今已经有9个月,功能渐渐完善,自身的生态也逐步形成,早在几个月前写过一篇文章<ORM 开发环境之利器:MVC 中间件 FreeSql.AdminLTE>,您可以 ...

  5. C#开发BIMFACE系列12 服务端API之文件转换

    系列目录     [已更新最新开发文章,点击查看详细] 在代表模型的源文件上传到BIMFACE后,一般会进行三种API调用操作: 发起模型转换 查询转换状态 如转换成功,获取模型转换后的BIM数据 在 ...

  6. 利用poi包装一个简单的Excel读取器.一(适配一个Reader并提供readLine方法)

    通常,读文本我们会使用BufferedReader,它装饰或者说管理了InputStreamReader,同时提供readLine()简化了我们对文本行的读取.就像从流水线上获取产品一样,每当取完一件 ...

  7. Linux配置使用SSH Key登录并禁用root密码登录

    Linux系统大多数都支持OpenSSH,生成公钥.私钥的最好用ssh-keygen命令,如果用putty自带的PUTTYGEN.EXE生成会不兼容OpenSSH,从而会导致登录时出现server r ...

  8. 【selenium】- 常见浏览器的启动

    本文由小编根据慕课网视频亲自整理,转载请注明出处和作者. 1. Firefox启动 webdriver自带了firefox浏览器的驱动,所以不需要设置它的驱动. 如果firefox没有安装在默认路径, ...

  9. HDU-6053 TrickGCD

    题目连接: https://vjudge.net/problem/HDU-6053 Description You are given an array A , and Zhu wants to kn ...

  10. Ada and Coins

    Ada and Coins 题意:钱包里有n种钱,然后有m次询问,询问[l,r]区间内能被表示的个数有几个. 题解:这道题是群主推荐我写的,然后让我用bitset去写,他说 操作32个bitset需要 ...