1. 概述
1.1 接口继承关系和实现
- Collection:Collection 是集合 List、Set、Queue 的最基本的接口。
- Iterator:迭代器,可以通过迭代器遍历集合中的数据
- Map:是映射表的基础接口
1.2 集合框架底层数据结构总结
先来看一下 Collection
接口下面的集合。
List
Arraylist
:Object[]
数组Vector
:Object[]
数组LinkedList
: 双向链表(JDK1.6 之前为循环链表,JDK1.7 取消了循环)
Set
HashSet
(无序,唯一): 基于HashMap
实现的,底层采用HashMap
来保存元素LinkedHashSet
:LinkedHashSet
是HashSet
的子类,并且其内部是通过LinkedHashMap
来实现的。有点类似于我们之前说的LinkedHashMap
其内部是基于HashMap
实现一样,不过还是有一点点区别的TreeSet
(有序,唯一): 红黑树(自平衡的排序二叉树)
Queue
PriorityQueue
:Object[]
数组来实现二叉堆ArrayQueue
:Object[]
数组 + 双指针
再来看看 Map
接口下面的集合。
Map
HashMap
: JDK1.8 之前HashMap
由数组+链表组成的,数组是HashMap
的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突)。JDK1.8 以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间LinkedHashMap
:LinkedHashMap
继承自HashMap
,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap
在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。详细可以查看:《LinkedHashMap 源码详细分析(JDK1.8)》Hashtable
: 数组+链表组成的,数组是Hashtable
的主体,链表则是主要为了解决哈希冲突而存在的TreeMap
: 红黑树(自平衡的排序二叉树)
1.3 如何选用集合?
- 主要根据集合的特点来选用,比如我们需要根据键值获取到元素值时就选用
Map
接口下的集合,需要排序时选择TreeMap
,不需要排序时就选择HashMap
,需要保证线程安全就选用ConcurrentHashMap
。 - 当我们只需要存放元素值时,就选择实现
Collection
接口的集合,需要保证元素唯一时选择实现Set
接口的集合比如TreeSet
或HashSet
,不需要就选择实现List
接口的比如ArrayList
或LinkedList
,然后再根据实现这些接口的集
1..4 为什么要使用集合?
- 当我们需要保存一组类型相同的数据的时候,我们应该是用一个容器来保存,这个容器就是数组,但是,使用数组存储对象具有一定的弊端, 因为我们在实际开发中,存储的数据的类型是多种多样的,于是,就出现了“集合”,集合同样也是用来存储多个数据的。
- 数组的缺点是一旦声明之后,长度就不可变了;同时,声明数组时的数据类型也决定了该数组存储的数据的类型;而且,数组存储的数据是有序的、可重复的,特点单一。 但是集合提高了数据存储的灵活性,Java 集合不仅可以用来存储不同类型不同数量的对象,还可以保存具有映射关系的数据。
2. Collection 子接口之 List
2.1 Arraylist 和 Vector 的区别?
ArrayList
是List
的主要实现类,底层使用Object[ ]
存储,适用于频繁的查找工作,线程不安全 ;Vector
是List
的古老实现类,底层使用Object[ ]
存储,线程安全的。
2.2 Arraylist 与 LinkedList 区别?
- 是否保证线程安全:
ArrayList
和LinkedList
都是不同步的,也就是不保证线程安全; - 底层数据结构:
Arraylist
底层使用的是Object
数组;LinkedList
底层使用的是 双向链表 数据结构(JDK1.6 之前为循环链表,JDK1.7 取消了循环。注意双向链表和双向循环链表的区别,下面有介绍到!) - 插入和删除是否受元素位置的影响:
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) ,因为需要先移动到指定位置再插入。
- 是否支持快速随机访问:
LinkedList
不支持高效的随机元素访问,而ArrayList
支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于get(int index)
方法)。 - 内存空间占用: ArrayList 的空间浪费主要体现在在 list 列表的结尾会预留一定的容量空间,而 LinkedList 的空间花费则体现在它的每一个元素都需要消耗比 ArrayList 更多的空间(因为要存放直接后继和直接前驱以及数据)。
2.3 ArrayList源码详解
2.3.1 类图
![](https://img-blog.csdnimg.cn/aa0cf7cd00fe4e1c97fdf336c0cc7fc7.png)
- 实现了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修饰,可以简化内部类的访问