码迷,mamicode.com
首页 > 其他好文 > 详细

集合ArrayList分析

时间:2019-09-21 13:09:28      阅读:98      评论:0      收藏:0      [点我收藏+]

标签:iter   capacity   机制   rac   math   tor   vat   空值   ali   

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分析

标签:iter   capacity   机制   rac   math   tor   vat   空值   ali   

原文地址:https://www.cnblogs.com/alige/p/11562070.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!