java集合之ArrayList(一)

1. 概述

1.1 接口继承关系和实现

集合类存放于 Java.util 包中,主要有 3 种:set(集)、list(列表包含 Queue)和 map(映射)。
  1. Collection:Collection 是集合 List、Set、Queue 的最基本的接口。
  2. Iterator:迭代器,可以通过迭代器遍历集合中的数据
  3. Map:是映射表的基础接口

1.2 集合框架底层数据结构总结

先来看一下 Collection 接口下面的集合。

List

  • ArraylistObject[] 数组
  • VectorObject[] 数组
  • LinkedList: 双向链表(JDK1.6 之前为循环链表,JDK1.7 取消了循环)

Set

  • HashSet(无序,唯一): 基于 HashMap 实现的,底层采用 HashMap 来保存元素
  • LinkedHashSet: LinkedHashSetHashSet 的子类,并且其内部是通过 LinkedHashMap 来实现的。有点类似于我们之前说的 LinkedHashMap 其内部是基于 HashMap 实现一样,不过还是有一点点区别的
  • TreeSet(有序,唯一): 红黑树(自平衡的排序二叉树)

Queue

  • PriorityQueue: Object[] 数组来实现二叉堆
  • ArrayQueue: Object[] 数组 + 双指针

再来看看 Map 接口下面的集合。

Map

  • HashMap: JDK1.8 之前 HashMap 由数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突)。JDK1.8 以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间
  • LinkedHashMapLinkedHashMap 继承自 HashMap,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。详细可以查看:《LinkedHashMap 源码详细分析(JDK1.8)》
  • Hashtable: 数组+链表组成的,数组是 Hashtable 的主体,链表则是主要为了解决哈希冲突而存在的
  • TreeMap: 红黑树(自平衡的排序二叉树)

1.3 如何选用集合?

  • 主要根据集合的特点来选用,比如我们需要根据键值获取到元素值时就选用 Map 接口下的集合,需要排序时选择 TreeMap,不需要排序时就选择 HashMap,需要保证线程安全就选用 ConcurrentHashMap
  • 当我们只需要存放元素值时,就选择实现Collection 接口的集合,需要保证元素唯一时选择实现 Set 接口的集合比如 TreeSetHashSet,不需要就选择实现 List 接口的比如 ArrayListLinkedList,然后再根据实现这些接口的集

1..4 为什么要使用集合?

  • 当我们需要保存一组类型相同的数据的时候,我们应该是用一个容器来保存,这个容器就是数组,但是,使用数组存储对象具有一定的弊端, 因为我们在实际开发中,存储的数据的类型是多种多样的,于是,就出现了“集合”,集合同样也是用来存储多个数据的。
  • 数组的缺点是一旦声明之后,长度就不可变了;同时,声明数组时的数据类型也决定了该数组存储的数据的类型;而且,数组存储的数据是有序的、可重复的,特点单一。 但是集合提高了数据存储的灵活性,Java 集合不仅可以用来存储不同类型不同数量的对象,还可以保存具有映射关系的数据。

2. Collection 子接口之 List

 

 

2.1 Arraylist 和 Vector 的区别?

  • ArrayListList 的主要实现类,底层使用 Object[ ]存储,适用于频繁的查找工作,线程不安全 ;
  • VectorList 的古老实现类,底层使用Object[ ] 存储,线程安全的。

2.2 Arraylist 与 LinkedList 区别?

  1. 是否保证线程安全: ArrayListLinkedList 都是不同步的,也就是不保证线程安全;
  2. 底层数据结构: Arraylist 底层使用的是 Object 数组LinkedList 底层使用的是 双向链表 数据结构(JDK1.6 之前为循环链表,JDK1.7 取消了循环。注意双向链表和双向循环链表的区别,下面有介绍到!)
  3. 插入和删除是否受元素位置的影响:
    • ArrayList 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。 比如:执行add(E e)方法的时候, ArrayList 会默认在将指定的元素追加到此列表的末尾,这种情况时间复杂度就是 O(1)。但是如果要在指定位置 i 插入和删除元素的话(add(int index, E element))时间复杂度就为 O(n-i)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执行向后位/向前移一位的操作。
    • LinkedList 采用链表存储,所以,如果是在头尾插入或者删除元素不受元素位置的影响(add(E e)addFirst(E e)addLast(E e)removeFirst()removeLast()),近似 O(1),如果是要在指定位置 i 插入和删除元素的话(add(int index, E element)remove(Object o)) 时间复杂度近似为 O(n) ,因为需要先移动到指定位置再插入。
  4. 是否支持快速随机访问: LinkedList 不支持高效的随机元素访问,而 ArrayList 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于get(int index)方法)。
  5. 内存空间占用: ArrayList 的空间浪费主要体现在在 list 列表的结尾会预留一定的容量空间,而 LinkedList 的空间花费则体现在它的每一个元素都需要消耗比 ArrayList 更多的空间(因为要存放直接后继和直接前驱以及数据)。

2.3 ArrayList源码详解

2.3.1 类图

  • 实现了RandomAccess接口,可以随机访问
  • 实现了Cloneable接口,可以克隆
  • 实现了Serializable接口,可以序列化、反序列化
  • 实现了List接口,是List的实现类之一
  • 实现了Collection接口,是Java Collections Framework成员之一
  • 实现了Iterable接口,可以使用for-each迭代

2.3.2 属性

// 序列化版本UID
private static final long
        serialVersionUID = 8683452581122892189L;
 
/**
 * 默认的初始容量
 */
private static final int
        DEFAULT_CAPACITY = 10;
 
/**
 * 用于空实例的共享空数组实例
 * new ArrayList(0);
 */
private static final Object[]
        EMPTY_ELEMENTDATA = {};
 
/**
 * 用于提供默认大小的实例的共享空数组实例
 * new ArrayList();
 */
private static final Object[]
        DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
 
/**
 * 存储ArrayList元素的数组缓冲区
 * ArrayList的容量,是数组的长度
 * 
 * non-private to simplify nested class access
 */
transient Object[] elementData;
 
/**
 * ArrayList中元素的数量
 */
private int size;

2.3.3 构造方法

带初始容量的构造方法

/**
 * 带一个初始容量参数的构造方法
 *
 * @param  initialCapacity  初始容量
 * @throws  如果初始容量非法就抛出
 *          IllegalArgumentException
 */
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 > 0,就创建一个新的长度是initialCapacity的数组

  • 如果initialCapacity == 0,就使用 EMPTY_ELEMENTDATA

  • 其他情况,initialCapacity不合法,抛出异常

无参构造方法

/**
 * 无参构造方法 将elementData 赋值为
 *   DEFAULTCAPACITY_EMPTY_ELEMENTDATA
 */
public ArrayList() {
    this.elementData =
            DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

使用无参构造创建ArrayList,就使用 DEFAULTCAPACITY_EMPTY_ELEMENTDATA;

带一个集合参数的构造方法

/**
 * 带一个集合参数的构造方法
 *
 * @param c 集合,代表集合中的元素会被放到list中
 * @throws 如果集合为空,抛出NullPointerException
 */
public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    // 如果 size != 0
    if ((size = elementData.length) != 0) {
        // c.toArray 可能不正确的,不返回 Object[]
        // https://bugs.openjdk.java.net/browse/JDK-6260652
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(
                    elementData, size, Object[].class);
    } else {
        // size == 0
        // 将EMPTY_ELEMENTDATA 赋值给 elementData
        this.elementData = EMPTY_ELEMENTDATA;
    }
}
  • 使用将集合转换为数组的方法

  • 为了防止c.toArray()方法不正确的执行,导致没有返回Object[],特殊做了处理

  • 如果数组大小等于0,则使用 EMPTY_ELEMENTDATA

我们知道ArrayList中elementData就是Object[]类型,那么问题来了,什么情况下c.toArray()会不返回Object[]呢?

这里涉及到了ArrayList的私有内部类 ArrayList

private static class ArrayList<E> extends AbstractList<E>
        implements RandomAccess, java.io.Serializable {
 
    // 存储元素的数组
    private final E[] a;
 
    ArrayList(E[] array) {
        // 直接把接收的数组 赋值 给 a
        a = Objects.requireNonNull(array);
    }
 
    /**
     * obj 为空抛出异常
     * 不为空 返回 obj
     */
    public static <T> T requireNonNull(T obj) {
        if (obj == null)
            throw new NullPointerException();
        return obj;
    }
 
    @Override
    public Object[] toArray() {
        // 返回 a 的克隆对象
        return a.clone();
    }
 
}

这是Arrays.asList()方法源码

public static <T> List<T> asList(T... a) {
    return new ArrayList<>(a);
}

从上述源码可以看出,私有内部类ArrayList是直接把接收到的数组赋值进去,接收到什么类型,就赋值什么类型 。

同时,在使用Arrays.asList时,常会出现一个异常 UnsupportedOperationException

public class Demo34 {
    public static void main(String[] args) {
        String[] array = {"1","2","3","4","5"};
        List<String> list = Arrays.asList(array);
        list.add("6");
    }
}
"C:\Program Files\Java\jdk1.8.0_101\bin\java.exe" "-javaagent:D:\idea\IntelliJ IDEA 2019.2.3\lib\idea_rt.jar=58871:D:\idea\IntelliJ IDEA 2019.2.3\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_101\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\rt.jar;F:\tower\springboot_jsp\target\classes;G:\repo\org\springframework\boot\spring-boot-starter\2.1.1.RELEASE\spring-boot-starter-2.1.1.RELEASE.jar;G:\repo\org\springframework\boot\spring-boot\2.1.1.RELEASE\spring-boot-2.1.1.RELEASE.jar;G:\repo\org\springframework\spring-context\5.1.3.RELEASE\spring-context-5.1.3.RELEASE.jar;G:\repo\org\springframework\boot\spring-boot-autoconfigure\2.1.1.RELEASE\spring-boot-autoconfigure-2.1.1.RELEASE.jar;G:\repo\org\springframework\boot\spring-boot-starter-logging\2.1.1.RELEASE\spring-boot-starter-logging-2.1.1.RELEASE.jar;G:\repo\ch\qos\logback\logback-classic\1.2.3\logback-classic-1.2.3.jar;G:\repo\org\apache\logging\log4j\log4j-to-slf4j\2.11.1\log4j-to-slf4j-2.11.1.jar;G:\repo\org\apache\logging\log4j\log4j-api\2.11.1\log4j-api-2.11.1.jar;G:\repo\org\slf4j\jul-to-slf4j\1.7.25\jul-to-slf4j-1.7.25.jar;G:\repo\javax\annotation\javax.annotation-api\1.3.2\javax.annotation-api-1.3.2.jar;G:\repo\org\springframework\spring-core\5.1.3.RELEASE\spring-core-5.1.3.RELEASE.jar;G:\repo\org\springframework\spring-jcl\5.1.3.RELEASE\spring-jcl-5.1.3.RELEASE.jar;G:\repo\org\yaml\snakeyaml\1.23\snakeyaml-1.23.jar;G:\repo\org\springframework\boot\spring-boot-starter-web\2.4.2\spring-boot-starter-web-2.4.2.jar;G:\repo\org\springframework\boot\spring-boot-starter-json\2.1.1.RELEASE\spring-boot-starter-json-2.1.1.RELEASE.jar;G:\repo\com\fasterxml\jackson\core\jackson-databind\2.9.7\jackson-databind-2.9.7.jar;G:\repo\com\fasterxml\jackson\core\jackson-annotations\2.9.0\jackson-annotations-2.9.0.jar;G:\repo\com\fasterxml\jackson\core\jackson-core\2.9.7\jackson-core-2.9.7.jar;G:\repo\com\fasterxml\jackson\datatype\jackson-datatype-jdk8\2.9.7\jackson-datatype-jdk8-2.9.7.jar;G:\repo\com\fasterxml\jackson\datatype\jackson-datatype-jsr310\2.9.7\jackson-datatype-jsr310-2.9.7.jar;G:\repo\com\fasterxml\jackson\module\jackson-module-parameter-names\2.9.7\jackson-module-parameter-names-2.9.7.jar;G:\repo\org\springframework\boot\spring-boot-starter-tomcat\2.1.1.RELEASE\spring-boot-starter-tomcat-2.1.1.RELEASE.jar;G:\repo\org\apache\tomcat\embed\tomcat-embed-core\9.0.13\tomcat-embed-core-9.0.13.jar;G:\repo\org\apache\tomcat\embed\tomcat-embed-el\9.0.13\tomcat-embed-el-9.0.13.jar;G:\repo\org\apache\tomcat\embed\tomcat-embed-websocket\9.0.13\tomcat-embed-websocket-9.0.13.jar;G:\repo\org\springframework\spring-web\5.1.3.RELEASE\spring-web-5.1.3.RELEASE.jar;G:\repo\org\springframework\spring-beans\5.1.3.RELEASE\spring-beans-5.1.3.RELEASE.jar;G:\repo\org\springframework\spring-webmvc\5.1.3.RELEASE\spring-webmvc-5.1.3.RELEASE.jar;G:\repo\org\springframework\spring-aop\5.1.3.RELEASE\spring-aop-5.1.3.RELEASE.jar;G:\repo\org\springframework\spring-expression\5.1.3.RELEASE\spring-expression-5.1.3.RELEASE.jar;G:\repo\com\baomidou\mybatis-plus-boot-starter\3.1.0\mybatis-plus-boot-starter-3.1.0.jar;G:\repo\com\baomidou\mybatis-plus\3.1.0\mybatis-plus-3.1.0.jar;G:\repo\org\springframework\boot\spring-boot-starter-jdbc\2.1.1.RELEASE\spring-boot-starter-jdbc-2.1.1.RELEASE.jar;G:\repo\com\zaxxer\HikariCP\3.2.0\HikariCP-3.2.0.jar;G:\repo\org\springframework\spring-jdbc\5.1.3.RELEASE\spring-jdbc-5.1.3.RELEASE.jar;G:\repo\com\alibaba\druid\1.1.6\druid-1.1.6.jar;G:\repo\mysql\mysql-connector-java\5.1.47\mysql-connector-java-5.1.47.jar;G:\repo\com\baomidou\mybatis-plus-generator\3.1.1\mybatis-plus-generator-3.1.1.jar;G:\repo\com\baomidou\mybatis-plus-extension\3.1.1\mybatis-plus-extension-3.1.1.jar;G:\repo\com\baomidou\mybatis-plus-core\3.1.1\mybatis-plus-core-3.1.1.jar;G:\repo\com\baomidou\mybatis-plus-annotation\3.1.1\mybatis-plus-annotation-3.1.1.jar;G:\repo\com\github\jsqlparser\jsqlparser\1.2\jsqlparser-1.2.jar;G:\repo\org\mybatis\mybatis\3.5.1\mybatis-3.5.1.jar;G:\repo\org\mybatis\mybatis-spring\2.0.1\mybatis-spring-2.0.1.jar;G:\repo\org\apache\velocity\velocity-engine-core\2.1\velocity-engine-core-2.1.jar;G:\repo\org\apache\commons\commons-lang3\3.8.1\commons-lang3-3.8.1.jar;G:\repo\org\slf4j\slf4j-api\1.7.25\slf4j-api-1.7.25.jar;G:\repo\org\apache\activemq\activemq-all\5.15.8\activemq-all-5.15.8.jar;G:\repo\org\jxls\jxls\2.6.0\jxls-2.6.0.jar;G:\repo\org\apache\commons\commons-jexl3\3.1\commons-jexl3-3.1.jar;G:\repo\commons-logging\commons-logging\1.2\commons-logging-1.2.jar;G:\repo\commons-beanutils\commons-beanutils\1.9.3\commons-beanutils-1.9.3.jar;G:\repo\commons-collections\commons-collections\3.2.2\commons-collections-3.2.2.jar;G:\repo\org\slf4j\jcl-over-slf4j\1.7.25\jcl-over-slf4j-1.7.25.jar;G:\repo\ch\qos\logback\logback-core\1.2.3\logback-core-1.2.3.jar;G:\repo\org\jxls\jxls-poi\1.2.0\jxls-poi-1.2.0.jar;G:\repo\org\apache\poi\poi\4.0.1\poi-4.0.1.jar;G:\repo\commons-codec\commons-codec\1.11\commons-codec-1.11.jar;G:\repo\org\apache\commons\commons-collections4\4.2\commons-collections4-4.2.jar;G:\repo\org\apache\commons\commons-math3\3.6.1\commons-math3-3.6.1.jar;G:\repo\org\apache\poi\poi-ooxml\4.0.1\poi-ooxml-4.0.1.jar;G:\repo\org\apache\poi\poi-ooxml-schemas\4.0.1\poi-ooxml-schemas-4.0.1.jar;G:\repo\org\apache\xmlbeans\xmlbeans\3.0.2\xmlbeans-3.0.2.jar;G:\repo\org\apache\commons\commons-compress\1.18\commons-compress-1.18.jar;G:\repo\com\github\virtuald\curvesapi\1.05\curvesapi-1.05.jar;G:\repo\org\jxls\jxls-jexcel\1.0.8\jxls-jexcel-1.0.8.jar;G:\repo\net\sourceforge\jexcelapi\jxl\2.6.10\jxl-2.6.10.jar;G:\repo\log4j\log4j\1.2.14\log4j-1.2.14.jar;G:\repo\org\apache\commons\commons-jexl\2.1.1\commons-jexl-2.1.1.jar;G:\repo\org\jxls\jxls-reader\2.0.5\jxls-reader-2.0.5.jar;G:\repo\org\apache\commons\commons-digester3\3.2\commons-digester3-3.2-with-deps.jar;G:\repo\cglib\cglib\2.2.2\cglib-2.2.2.jar;G:\repo\asm\asm\3.3.1\asm-3.3.1.jar;G:\repo\com\alibaba\fastjson\1.2.47\fastjson-1.2.47.jar;G:\repo\org\springframework\boot\spring-boot-starter-data-mongodb\2.1.1.RELEASE\spring-boot-starter-data-mongodb-2.1.1.RELEASE.jar;G:\repo\org\mongodb\mongodb-driver\3.8.2\mongodb-driver-3.8.2.jar;G:\repo\org\mongodb\bson\3.8.2\bson-3.8.2.jar;G:\repo\org\mongodb\mongodb-driver-core\3.8.2\mongodb-driver-core-3.8.2.jar;G:\repo\org\springframework\data\spring-data-mongodb\2.1.3.RELEASE\spring-data-mongodb-2.1.3.RELEASE.jar;G:\repo\org\springframework\spring-tx\5.1.3.RELEASE\spring-tx-5.1.3.RELEASE.jar;G:\repo\org\springframework\data\spring-data-commons\2.1.3.RELEASE\spring-data-commons-2.1.3.RELEASE.jar;G:\repo\joda-time\joda-time\2.9.1\joda-time-2.9.1.jar;G:\repo\org\jodd\jodd-http\3.7.1\jodd-http-3.7.1.jar;G:\repo\org\jodd\jodd-core\3.7.1\jodd-core-3.7.1.jar;G:\repo\org\jodd\jodd-upload\3.7.1\jodd-upload-3.7.1.jar;G:\repo\org\assertj\assertj-core\3.11.1\assertj-core-3.11.1.jar" com.dada.springboot_jsp.study.controller.Demo34
Exception in thread "main" java.lang.UnsupportedOperationException
	at java.util.AbstractList.add(AbstractList.java:148)
	at java.util.AbstractList.add(AbstractList.java:108)
	at com.dada.springboot_jsp.study.controller.Demo34.main(Demo34.java:15)
 
Process finished with exit code 1

Arrays.asList的底层调用的实际上也是ArrayList的私有内部类ArrayList,它并不是java.util.ArrayList,他可以调用add方法,完全是因为它们的父类,都是AbstractList,而我们可以看到Arrays的内部类ArrayList并没有重写add和remove方法,所以当我调用Arrays的内部类ArrayList的add方法时实际上是集成父类AbstractList的add方法。

 所以就会抛出异常了。

而java.util.ArrayList的add方法重写了父类的方法,所以不会报错。

2.3.4  add插入方法

在列表最后添加指定元素

/**
 * 在列表最后添加指定元素
 *
 * @param e 要添加的指定元素
 * @return true
 */
public boolean add(E e) {
    // 增加 modCount !!
    ensureCapacityInternal(size + 1); 
    elementData[size++] = e;
    return true;
}
  • 在父类AbstractList上,定义了modCount 属性,用于记录数组修改的次数。

在指定位置添加指定元素

/**
 * 在指定位置添加指定元素
 * 如果指定位置已经有元素,就将该元素和随后的元素移动到右面一位
 *
 * @param index 待插入元素的下标
 * @param element 待插入的元素
 * @throws 可能抛出 IndexOutOfBoundsException
 */
public void add(int index, E element) {
    //判断index下标是否超过了size,IndexOutOfBoundsException就是在这里发生
    rangeCheckForAdd(index);
 
 
    // 增加 modCount !!
    ensureCapacityInternal(size + 1);
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    elementData[index] = element;
    size++;
}

2.3.5 扩容方法

/**
 * 数组可以分配的最大size
 * 一些虚拟机在数组中预留一些header words
 * 如果尝试分配更大的size,可能导致OutOfMemoryError
 */
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
 
/**
 * 增加容量,至少保证比minCapacity大
 * @param minCapacity 期望的最小容量
 */
private void grow(int minCapacity) {
    // 有可能溢出的代码
    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);
}
 
/**
 * 最大容量返回 Integer.MAX_VALUE
 */
private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}

这里先对数组的长度进行一系列的判断和计算,得出最终的长度后,使用Arrays.copyOf(int[] original, int newLength),对数组进行扩容操作

  • 通常情况新容量是原来容量的1.5倍

  • 如果原容量的1.5倍比minCapacity小,那么就扩容到minCapacity

  • 特殊情况扩容到Integer.MAX_VALUE

new ArrayList()会将elementData 赋值为 DEFAULTCAPACITY_EMPTY_ELEMENTDATA,new ArrayList(0)会将elementData 赋值为 EMPTY_ELEMENTDATA,EMPTY_ELEMENTDATA添加元素会扩容到容量为1,而DEFAULTCAPACITY_EMPTY_ELEMENTDATA扩容之后容量为10。

2.3.6 序列化方法

/**
 * 将ArrayLisy实例的状态保存到一个流里面
 */
private void writeObject(java.io.ObjectOutputStream s)
    throws java.io.IOException{
    // Write out element count, and any hidden stuff
    int expectedModCount = modCount;
    s.defaultWriteObject();
 
    // Write out size as capacity for behavioural compatibility with clone()
    s.writeInt(size);
 
    // 按照顺序写入所有的元素
    for (int i=0; i<size; i++) {
        s.writeObject(elementData[i]);
    }
 
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
}

反序列化方法

/**
 * 根据一个流(参数)重新生成一个ArrayList
 */
private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    elementData = EMPTY_ELEMENTDATA;
 
    // Read in size, and any hidden stuff
    s.defaultReadObject();
 
    // Read in capacity
    s.readInt();
 
    if (size > 0) {
        // be like clone(), allocate array based upon size not capacity
        ensureCapacityInternal(size);
 
        Object[] a = elementData;
        // Read in all elements in the proper order.
        for (int i=0; i<size; i++) {
            a[i] = s.readObject();
        }
    }
}

elementData之所以用transient修饰,是因为JDK不想将整个elementData都序列化或者反序列化,而只是将size和实际存储的元素序列化或反序列化,从而节省空间和时间。

2.3.7 迭代器

创建迭代器方法

public Iterator<E> iterator() {
    return new Itr();
}

Itr属性

// 下一个要返回的元素的下标
int cursor;
// 最后一个要返回元素的下标 没有元素返回 -1
int lastRet = -1;
// 期望的 modCount
int expectedModCount = modCount;

Itr的hasNext() 方法

public boolean hasNext() {
    return cursor != size;
}

Itr的next()方法

public E next() {
    //在迭代的时候,会校验modCount是否等于expectedModCount,不等于就会抛出著名的ConcurrentModificationException异常
    checkForComodification();
    int i = cursor;
    if (i >= size)
        throw new NoSuchElementException();
    Object[] elementData = ArrayList.this.elementData;
    if (i >= elementData.length)
        throw new ConcurrentModificationException();
    cursor = i + 1;
    return (E) elementData[lastRet = i];
}
 
final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

问题:上述的并发修改异常什么场景下会发生呢?

public static void remove(ArrayList<Integer> list) {
    Iterator<Integer> iterator = list.iterator();
    while (iterator.hasNext()) {
        Integer number = iterator.next();
        if (number % 2 == 0) {
            // 抛出ConcurrentModificationException异常
            list.remove(number);
        }
    }
}

解决方式:将list.remove()换成iterator.remove()即可

Itr的remove()方法

public void remove() {
    if (lastRet < 0)
        throw new IllegalStateException();
    checkForComodification();
 
    try {
        ArrayList.this.remove(lastRet);
        cursor = lastRet;
        lastRet = -1;
        // 移除之后将modCount 重新赋值给 expectedModCount
        expectedModCount = modCount;
    } catch (IndexOutOfBoundsException ex) {
        throw new ConcurrentModificationException();
    }
}

原因就是因为Itr的remove()方法,移除之后将modCount重新赋值给 expectedModCount。这就是源码,不管单线程还是多线程,只要违反了规则,就会抛异常。

2.3.8 trimToSize方法

当一个ArrayList数组元素被存满之,它会自动将当前数组修改1.5倍大小,
如果我们不使用trimToSize调整,则会使ArrayList未存满,但是占用了多余的空间
此时使用trimToSize()方法就可以删除已分配,但是不需要使用的空间

2.3.9 1.7和1.8版本初始化的区别

1.7的时候是初始化就创建一个容量为10的数组,1.8后是初始化先创建一个空数组,第一次add时才扩容为10

总结:

  • ArrayList底层的数据结构是数组
  • ArrayList可以自动扩容,不传初始容量或者初始容量是0,都会初始化一个空数组,但是如果添加元素,会自动进行扩容,所以,创建ArrayList的时候,给初始容量是必要的
  • Arrays.asList()方法返回的是的Arrays内部的ArrayList,用的时候需要注意
  • subList()返回内部类,不能序列化,和ArrayList共用同一个数组
  • 迭代删除要用,迭代器的remove方法,或者可以用倒序的for循环
  • ArrayList重写了序列化、反序列化方法,避免序列化、反序列化全部数组,浪费时间和空间
  • elementData不使用private修饰,可以简化内部类的访问
     
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值