目录
2.3.4 举个栗子:给一个空数组添加11个元素,查看数组扩容情况
2.4.5 copyOf()和arraycopy()联系与区别
前言
上一节内容,我们分析总结了集合的顶层的Collection和Iterator接口,下图可以清晰的看到集合的各个接口和实现类。
自上而下,逐步分析原则,本节主要分析总结一下Collection接口的子类接口List集合,并对List集合的一个实现类ArrayList源码进行深度解析。
1、List集合和它的方法们
List集合代表一个元素有序、可重复的集合。集合中的每个元素都有对应的顺序索引。List集合允许使用重复元素,可存放多个null元素,有的元素是以一种线性方式进行存储的,在过程中可以通过索引来访问集合中的指定元素,可以通过索引来访问指定位置的集合元素,且默认按照元素的添加顺序设置元素的索引。
List作为Collection接口的子接口,当然可以使用Collection接口里的全部方法。而且由于List是有序集合,因此List集合里增加了一些根据索引来操作集合元素的方法。这里还是总结下List集合常用的一些方法:
方法 | 描述 |
添加元素 | |
void add(int index, Object element) | 将元素element插入到List集合的index处 |
boolean addAll(int index, Collection col) | 将集合col所包含的所有元素都插入到List集合的index处 |
获取功能 | |
Object get(int index) | 返回集合index处的元素 |
Iterator iterator() | 用来获取集合中每一个元素。 |
索引操作 | |
int indexOf(Object obj) | 返回对象obj在List集合中第一次出现的位置索引 |
int lastIndexOf(Object obj) | 返回对象obj在List集合中最后一次出现的位置索引 |
设值功能 | |
Object set(int index, Object element) | 将index索引处的元素替换成element对象,返回被替换的旧元素 |
元素操作 | |
List subList(int fromIndex, int toIndex) | 返回从索引fromIndex(包含)到索引toIndex(不包含)处的所有集合元素组成的子集合 |
void replaceAll(UnartOperator operator) | 根据operator指定的计算规则重新设置List集合的所有元素 |
排序功能 | |
void sort(Comparator c) | 根据Comparator参数对List集合的元素排序 |
删除功能 | |
boolean remove(Object o) | 从集合中移除指定的元素 |
boolean removeAll(Collection<?> c) | 从集合中移除一个指定的集合元素(有一个就返回true) |
Object remove(int index) | 根据索引删除元素,返回被删除的元素 |
集合转数组 | |
Object[] toArray() | 集合转换为数组 |
判断功能 | |
boolean isEmpty() | 判断集合是否为空 |
boolean contains(Object o) | 判断集合中是否包含指定元素 |
boolean containsAll(Collection<?> c) | 判断集合中是否包含指定的一个集合中的元素 |
列表迭代器 | |
ListIterator listIterator() | List集合特有的迭代器 |
1.1 举个栗子
针对上面的方法,这里举个栗子运行测试:
/**
* E set(int index,E);
* 修改指定索引上的元素
* 返回被修改的元素
* <p>
* add(int index,Object obj)
* 将元素插入到list集合指定索引index处,
* 注意:带有索引的操作,防止索引越界
* java.lang.IndexOutOfBoundsException
* java.lang.ArrayIndexOutOfBoundsException
* java.lang.StringIndexOutOfBoundsException
*/
@Test
public void test_set() {
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(4);
System.out.println("[0]原始的集合:" + list);
//[1]修改指定索引上的元素
Integer a = list.set(0, 5);
System.out.println("[1]修改后的集合:" + list);
//[2]返回被修改的元素
System.out.println("[2]返回被修改的元素:" + a);
//[3]获取指定索引上的元素
Integer value = list.get(0);
System.out.println("[3]获取指定索引上的元素:" + value);
//[4]移除指定索引位置上的元素
Integer removeValue = list.remove(0);
System.out.println("[4]返回删除指定索引上的元素:" + removeValue);
//[5]将新元素插入到List集合中指定索引index处
list.add(0, 123456);
System.out.println("[5]新增指定索引元素后的集合:" + list);
//[6]判断集合是否为空
boolean isEmpty = list.isEmpty();
System.out.println("[6]判断集合是否为空:" + list);
//[7]获取集合长度/元素个数
int size = list.size();
System.out.println("[7]集合的长度/元素个数:" + size);
//[8]将集合转为数组
Object[] objects = list.toArray();
for (Object values : objects) {
System.out.println("[8]将集合转换为数组,遍历集合元素:" + values);
}
//[9]判断集合是否包含某个元素
boolean contains = list.contains("456");
System.out.println("[9]判断集合是否包含指定元素:" + contains);
//[10]遍历当前集合
for (int value2:list) {
System.out.println("[10]遍历当前集合的元素:" + value2);
}
}
1.2 测试结果
[0]原始的集合:[1, 2, 3, 4, 4]
[1]修改后的集合:[5, 2, 3, 4, 4]
[2]返回被修改的元素:1
[3]获取指定索引上的元素:5
[4]返回删除指定索引上的元素:5
[5]新增指定索引元素后的集合:[123456, 2, 3, 4, 4]
[6]判断集合是否为空:[123456, 2, 3, 4, 4]
[7]集合的长度/元素个数:5
[8]将集合转换为数组,遍历集合元素:123456
[8]将集合转换为数组,遍历集合元素:2
[8]将集合转换为数组,遍历集合元素:3
[8]将集合转换为数组,遍历集合元素:4
[8]将集合转换为数组,遍历集合元素:4
[9]判断集合是否包含指定元素:false
[10]遍历当前集合的元素:123456
[10]遍历当前集合的元素:2
[10]遍历当前集合的元素:3
[10]遍历当前集合的元素:4
[10]遍历当前集合的元素:4
2、List接口的实现类们
在前言集合结构图中,我们可以清楚看到List接口的两个重要的实现类: ArrayList、LinkedList
2.1 ArrayList
ArrayList是List接口的实现类,它是一个数组队列(动态数组)。与Java中的数组相比,它的容量能动态增长。在 Java 1.2 引入了强大丰富的 Collection 框架,其中用 ArrayList 来作为可动态扩容数组的列表实现来代替 Array 在日常开发的使用,ArrayList 实现所有列表的操作方法,方便开发者操作列表集合。
为了更好地认识 ArrayList,看下从 ArrayList 的UML类图:
ArrayList继承了AbstractList类,实现了List、 Cloneable、RandomAccess、java.io.Serializable这些接口。
AbstractList 作为列表的抽象实现,将元素的增删改查都交给了具体的子类去实现,在元素的迭代遍历的操作上提供了默认实现。
- 实现Cloneable接口:表示了ArrayList 支持调用 Object 的 clone ()方法,实现 ArrayList 的拷贝。
- 实现Serializable接口:说明 ArrayList 还支持序列化和反序列操作,具有固定的 serialVersionUID 属性值,通过序列化输出。
- 实现RandomAccess接口:表示 ArrayList 里的元素可以被高效效率的随机访问,以索引下标数字的方式获取元素。实现 RandomAccess 接口的列表上在遍历时可直接使用普通的for循环方式,并且执行效率上给迭代器方式更高。
2.2 ArrayList源码解读
进入正题,对ArrayList源码逐一注释解读:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
private static final long serialVersionUID = 8683452581122892189L;
/**
* 默认初始化容量:10
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 用于空实例的共享数组实例
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* 用于默认大小空实例的共享空数组实例。
* 把它从EMPTY_ELEMENTDATA数组中区分出来,以便在添加第一个元素时容量需要增加多少。
* 实际上,这个空数组默认为0,增加第一个元素时数组容量将变为10
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* 保存ArrayList元素的数组缓冲区。
* ArrayList 的容量就是这个数组缓冲区的长度。
* 当添加第一个元素时,任何具有 elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 的空 ArrayList 都将扩展为 DEFAULT_CAPACITY。
*/
transient Object[] elementData;
/**
* ArrayList数组的长度(包含的元素个数)
*/
private int size;
/**
* 构造一个指定初始容量的空列表
*/
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
//构造一个容量为initialCapacity的空数组
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
//构造一个空的数组
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
/**
* 构造一个初始容量为10的空列表。
* 默认为0,初始化为10,即增加第一个元素时数组容量将变为10
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* 构造一个包含指定集合元素的List集合
* 按照指定集合迭代器返回元素的顺序排序
*/
public ArrayList(Collection<? extends E> c) {
//将集合转为数组
elementData = c.toArray();
if ((size = elementData.length) != 0) {
//返回的数组可能不是Object[]
//反射方法的getClass()
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// 用空数组代替
this.elementData = EMPTY_ELEMENTDATA;
}
}
/**
* 修改这个ArrayList实例的容量以达到当前列表集合的长度
* 应用程序可以操作最小化存储的ArrayList实例
*/
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
/**
* ===================> ArrayList扩容机制 <===================
*
* 如有必要,增加此ArrayList实例的容量,以确保它至少能容纳元素的数量
* @param minCapacity 所需最小容量
*/
public void ensureCapacity(int minCapacity) {
//判断保存ArrayList数据的数组长度与初始容量为10的空List集合(默认为10,初始化时为10)长度的对比
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
// any size if not default element table
? 0
// larger than default for default empty table. It's already
// 默认初始化容量:10
: DEFAULT_CAPACITY;
//如果最小容量 > 最小扩容大小
if (minCapacity > minExpand) {
//判断是否需要扩容,以取到最小的扩容量
ensureExplicitCapacity(minCapacity);
}
}
//得到最小的扩容量,用在add新增元素方法中。
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//取到默认容量和最小容量间较大值者
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
//判断是否需要扩容
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
//调用grow方法进行扩容,此处表示已经开始扩容
grow(minCapacity);
}
/**
* 要分配的最大数组的长度:2147483639
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/**
* ===================> ArrayList扩容的核心方法:grow()<===================
*
* 扩大容量,以确保它能够装填被最小容量参数所指定的最少元素个数
* @param minCapacity 所需的最小容量
*/
private void grow(int minCapacity) {
// oldCapacity:旧容量
// newCapacity:新容量
int oldCapacity = elementData.length;
//扩容,将旧容量右移一位,相当于在旧容量基础上再次扩大0.5倍,然后组成新的容量
int newCapacity = oldCapacity + (oldCapacity >> 1);
//判断新容量与所需容量的大小,如果小于最小容量,取大值者
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//判断新容量与最大数组长度的大小,如果大于最大数组长度,就对所需的最小容量进行最大容量值检测
//所需的最小容量<0时,内存异常,将交给GC处理
//如果所需的最小容量>最大数组容量,则取容量最大值作为新的容量
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);
}
//比较minCapacity 和 MAX_ARRAY_SIZE
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
/**
* 返回List列表元素个数
*/
public int size() {
return size;
}
/**
* 判断集合是否为空,如果空,返回true;反之,返回false
*/
public boolean isEmpty() {
return size == 0;
}
/**
* 判断集合是否包含指定对象
*
*/
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
/**
* 返回指定对象第一次出现的索引;如果元素为空或不存在则返回-1
*/
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
/**
* 返回指定对象最后一次出现的位置索引值
*/
public int lastIndexOf(Object o) {
if (o == null) {
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
for (int i = size-1; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
/**
* 克隆模式
*/
public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
/**
* 数组转换
*/
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}
/**
* 数组转换
*/
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
if (a.length < size)
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
// Positional Access Operations
@SuppressWarnings("unchecked")
E elementData(int index) {
return (E) elementData[index];
}
/**
* 获取指定位置索引上的元素
*/
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
/**
* 给指定位置索引元素赋值,替换原来的元素
* 返回被替代的原始元素
*/
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
/**
* 重写collection接口方法,新增元素,
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
/**
* ArrayList自己的方法,在指定位置索引出插入元素element
*/
public void add(int index, E element) {
//校验新增元素指定索引值,如果指定的index值>数组最大长度,或者index值<0,抛索引越界异常
rangeCheckForAdd(index);
//取最小容量
ensureCapacityInternal(size + 1); // Increments modCount!!
//数组复制,实际上在index处将数组分割为两段,然后将index后半段数组往后移动一位
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
//然后将element元素放在index索引位置处
elementData[index] = element;
//完成元素插入操作后,数组总长度+1
size++;
}
/**
* 移除指定位置索引上的元素,并返回被移出的元素
*/
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
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
return oldValue;
}
/**
* 移除对象,操作成功,返回true;操作失败,返回false
*/
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
}
/**
* 清空数组元素
*/
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
/**
* 添加集合
*/
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
/**
* 将指定集合元素插入在在指定位置索引上
*/
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
int numMoved = size - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
/**
* 移除指定索引范围的元素
*/
protected void removeRange(int fromIndex, int toIndex) {
modCount++;
int numMoved = size - toIndex;
System.arraycopy(elementData, toIndex, elementData, fromIndex,
numMoved);
// clear to let GC do its work
//改变数组长度,交给GC处理,
int newSize = size - (toIndex-fromIndex);
for (int i = newSize; i < size; i++) {
elementData[i] = null;
}
//处理完成后,赋予数组最新长度
size = newSize;
}
/**
* 对指定索引的范围检测,并给出越界异常信息
*/
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
/**
* 数组调用add方法和addAll时,将使用下面这个检测索引是否越界的方法,并给出越界异常信息
*/
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
/**
* 输出索引越界提示信息
*/
private String outOfBoundsMsg(int index) {
return "Index: "+index+", Size: "+size;
}
2.3 ArrayList源码分析
2.3.1 ArrayList的构造方法
ArrayList构造方法的源码设计:
/**
* 构造一个指定容量大小的空数组
*/
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
//构造一个容量为initialCapacity的空数组
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
//构造一个空的数组
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
/**
* 构造一个默认容量为0的空List集合。
* 默认容量为0,初始化容量为10,即增加第一个元素时数组容量将变为10
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* 构造一个包含指定集合元素的List集合
* 按照指定集合迭代器返回元素的顺序排序
*/
public ArrayList(Collection<? extends E> c) {
//将集合转为数组
elementData = c.toArray();
if ((size = elementData.length) != 0) {
//返回的数组可能不是Object[]
//反射方法的getClass()
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// 用空数组代替
this.elementData = EMPTY_ELEMENTDATA;
}
}
在上边源码中,可以看到ArrayList提供了三个构造方法:
- 一个int类型参数的构造方法,指定了初始数组容量大小,即初始时数组元素的个数;
- 一个无参构造方法,无参默认是空集合,当调用add()方法添加第一个元素时集合容量/长度会自动填充为10;
- 一个包含指定集合参数的构造方法 。
以上三个构造方法最后均对elementData进行了初始化,那这个elementData是什么呢?看源码中定义:
/**
* 保存ArrayList数据的数组
*/
transient Object[] elementData;
elementData是一个保存ArrayList元素的数组缓冲区,事实上,当我们操作ArrayList的时候,其本质上就是在对elementData这数组进行操作,由此,可以解开ArrayList底层是数组结构真相。
2.3.2 ArrayList的扩容机制
我们知道,当定义一个静态数组时,需要指定数组的初始元素,并不需要去指定数组长度,由底层自动指定容量大小即可;当定义一个动态数组时,此时并不知道数组的初始元素,此时必须要指定数组容量大小,才能够完成动态数组初始化。
接下来,给数组任意增加元素,看数组长度(容量)是如何变化的
测试程序:
@Test
public void test_add(){
ArrayList<String> list = new ArrayList<>();
list.add("a");
System.out.println(list);
}
上面就是常见的数组添加元素方式,这里调用List接口的add(E e)方法,在ArrayList类中做了实现。OK,我们看下ArrayList类中关于add(E e)这个方法是如何设计的:
/**
* ArrayList数组的长度(容器的元素个数)
*/
private int size;
/**
* 重写collection接口方法,新增元素,
*/
public boolean add(E e) {
//调用获取最小扩容量方法
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
增加一个元素时,调用获取最小扩容方法,同时将原来ArrayList数组元素的个数(或者数组长度)增加1,我们进入ensureCapacityInternal(size + 1);这个方法一探究竟:
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
在这里,将调用数组判断扩容的方法。同时,这里有传递了一个计算数组容量的方法,实际上判断数组扩容就是在calculateCapacity()这个方法中执行的。
接下来,剥丝抽茧,继续看calculateCapacity()方法:
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
先看这个方法的返回值,最终返回一个最小容量的整数值,这个最小容量咋来的呢?
原来,在新元素添加到数组前,传递了一个用来存储实际内容的数组(elementData),以及这个数组最新的元素个数(数组长度);这里,我们在回顾一下DEFAULTCAPACITY_EMPTY_ELEMENTDATA:
//用于默认大小空实例的共享空数组实例。
//把它从EMPTY_ELEMENTDATA数组中区分出来,以知道在添加第一个元素时容量需要增加多少。
//实际上,这个空数组默认为0,增加第一个元素时数组容量将变为10
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* 默认初始化容量:10
*/
private static final int DEFAULT_CAPACITY = 10;
调用数学函数二者比大小,取大值。这里做了初始化操作,如果为数组为空的话(即初始空数组),则容量填充为10 ;如果开始添加第二个元素(或者说这个数组原本是有元素的),则返回原数组长度+1的计算值,即minCapacity(最小容量)。
继续下一步:
private void ensureExplicitCapacity(int minCapacity) {
//记录数组修改的次数
modCount++;
//拿数组当前长度与原来的数组长度比大小,如果比原来容量大则进行扩容。
if (minCapacity - elementData.length > 0)
//扩容的核心方法,此处表示已经开始扩容
grow(minCapacity);
}
此时,拿数组当前长度与原来的数组长度比大小,如果比原来容量大则进行扩容。
OK,上面详细分析了数组添加元素从开始到即将触发数组扩容机制的完整流程。一起来看下数组扩容时,核心扩容方法grow()是如何处理的。
2.3.3 ArrayList扩容的核心方法
/**
* 要分配的最大数组的长度:2147483639
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/**
* ===================> ArrayList扩容的核心方法:grow()<===================
*
* 扩大容量,以确保它能够装填被最小容量参数所指定的最少元素个数
* @param minCapacity 所需的最小容量
*/
private void grow(int minCapacity) {
// oldCapacity:旧容量
// newCapacity:新容量
int oldCapacity = elementData.length;
//扩容,将旧容量右移一位,相当于在旧容量基础上再次扩大0.5倍,然后组成新的容量,新容量是旧容量的1.5倍大小
int newCapacity = oldCapacity + (oldCapacity >> 1);
//判断新容量与所需容量的大小,如果小于最小容量,取大值者
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//判断新容量与最大数组长度的大小,如果大于最大数组长度,就对所需的最小容量进行最大容量值检测
//所需的最小容量<0时,内存异常,将交给GC处理
//如果所需的最小容量>最大数组容量,则取容量最大值作为新的容量
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 数组复制
elementData = Arrays.copyOf(elementData, newCapacity);
}
//比较minCapacity 和 MAX_ARRAY_SIZE
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
ArrayList的扩容机制提高了性能,如果每次只扩充一个(也就是每次只新增一个元素),那么频繁的插入会导致频繁的数组拷贝,降低性能,而ArrayList的扩容机制避免了这种情况。
而且,ArrayList相当于在没指定initialCapacity(初始容量)时就是会使用延迟分配对象数组空间,数组第一次插入元素时会分配容量为10的元素空间。
2.3.4 举个栗子:给一个空数组添加11个元素,查看数组扩容情况
测试程序:
@Test
public void test_add(){
ArrayList list = new ArrayList();
for (int i = 0; i < 11; i++) {
list.add(i);
}
}
- 在第一次添加一个元素时,ArrayList的容量由0变为10,此时数组成功添加了第一个元素;下图所示:
- 在数组添加第一个元素开始扩容,一直到数组尚未复制前这个阶段,我们来DEBUG看下数组容量情况:
这里可以清楚的看到,第一个元素添加到数组,数组旧容量还是0(因为此时数组元素尚未完成添加操作,数组长度还是0状态),新容量将变为初始化容量10;然后开始数组拷贝,彻底完成空数组添加第一个元素及第一次扩容操作 。
- 数组扩容为10后,以此作为旧容量去继续添加元素,且数组新容量按照旧容量的1.5倍旧容量值进行增长。
- 在数组增加第10个元素时,容量还是10:
- 在输入增加第11个元素时,容量将又一次扩容,新容量将变为1.5*10:
- 扩容操作后,数组将通过Arrays.copyOf(elementData, newCapacity) 这样的方式完成数组复制,进而复制成最新数组。
2.4 数组复制问题
上面源码,我们发现数组在经过扩容处理后,将对数组进行复制操作。
在ArrayList数组中,我们可以看到三种数组复制方法:Arrays.copyOf()、System.arraycopy()、clone()方法
clone()源码:
实际上也是通过Arrays.copyOf(elementData, size)方法,因此,这里我们只需要了解:Arrays.copyOf()、System.arraycopy()
2.4.1 Arrays.copyOf()方法
数组扩容核心方法grow()完成扩容后对数组的处理:很巧妙的用到了Arrays.copyOf()方法将复制数组至指定长度的新容量长度;
- Arrays 类的 copyOf() 方法的语法格式:
- Arrays.copyOf(dataType[] srcArray, int length);
- 参数解释:
- srcArray 表示要进行复制的数组;
- length 表示复制后的新数组的长度。
private void grow(int minCapacity) {
// overflow-conscious code
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:要复制的数组;newCapacity:要复制的长度
elementData = Arrays.copyOf(elementData, newCapacity);
}
这里,我们再进入到copyOf()方法的源码中一探究竟:
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
return copy;
}
原来,Arrays.copyOf()复制数组的底层还是用的System.arraycopy()方法实现的。
2.4.2 Arrays.copyOf()栗子
@Test
public void test_copyOf() {
//源数组
int[] srcArray = {1, 2, 3, 4, 5};
System.out.print("原始数组:");
for (int i = 0; i < srcArray.length; i++) {
System.out.print(srcArray[i] + "\t");
}
//目标数组
int[] destArray = Arrays.copyOf(srcArray, 8);
System.out.println();
System.out.print("目标数组:");
for (int i = 0; i < destArray.length; i++) {
System.out.print(destArray[i] + "\t");
}
}
测试结果:
原始数组:1 2 3 4 5
目标数组:1 2 3 4 5 0 0 0
上边栗子,源数组长度5,元素已知,将其复制到长度为8的目标数组中,在复制完源数组的5个元素后,还有三个索引空间如何处理,这里将会采用数组类型的默认值填充剩余 3 个元素的内容,即填充为0。
使用这种方法复制数组时,默认从源数组的第一个元素(索引0)开始复制,目标数组的长度将为 length。
- 如果 length 大于源数组长度arr.length,则目标数组dest采用默认值填充;
- 如果 length 小于arr.length,则复制到第 length个元素(索引值为 length-1)即止。
- 注意:目标数组如果已经存在,将会被重构。
2.4.3 System.arraycopy()方法
ArrayList中实现数组复制的方法使用很巧妙,以add(int index, E element)为例,巧妙地用到了arraycopy()方法让数组自己复制自己实现让索引index开始之后的所有成员后移一个位置:
//在指定索引处添加元素
public void add(int index, E element) {
//索引范围检测
rangeCheckForAdd(index);
//数组容量伸缩处理
ensureCapacityInternal(size + 1); // Increments modCount!!
//复制数组
System.arraycopy(elementData, index, elementData, index + 1, size - index);
//将新增元素放在数组中指定索引位置 ,也就是给只给索引处元素赋值
elementData[index] = element;
//同步更新数组的长度
size++;
}
arraycopy() 方法位于 java.lang.System 类中,定义如下
- 语法格式:
System.arraycopy(dataType[] srcArray, int srcIndex, int destArray, int destIndex, int length)
- 参数解释:
- srcArray 表示源数组;
- srcIndex 表示源数组中的起始索引(包含);
- destArray 表示目标数组;
- destIndex 表示目标数组中的起始索引(不包含,);
- length表示要复制的源数组长度。
- 注意:
- length + srcIndex的长度须 <= 源数组长度srcArray.length
- length+destIndex的长度须 <= 目标数组长度destArray.length
2.4.4 System.arraycopy()栗子
@Test
public void test_arraycopy(){
//源数组
int[] srcArray = {1, 2, 3, 4, 5,6};
System.out.print("原始数组:");
for (int i = 0; i < srcArray.length; i++) {
System.out.print(srcArray[i] + "\t");
}
//目标数组
int[] destArray = {99, 98, 97, 96, 95, 94, 93, 92, 91};
System.out.println();
System.out.print("目标数组:");
for (int i = 0; i < destArray.length; i++) {
System.out.print(destArray[i] + "\t");
}
//复制源数组中的一部分到目标数组中
System.out.println();
System.arraycopy(srcArray,0,destArray, 6, 3);
System.out.print("复制数组:");
for (int i = 0; i < destArray.length; i++) {
System.out.print(destArray[i] + "\t");
}
}
测试结果:
原始数组:1 2 3 4 5 6
目标数组:99 98 97 96 95 94 93 92 91
复制数组:99 98 97 96 95 94 1 2 3
这里定义一个长度5,元素已知的int源数组srcArray;然后,定义一个长度9,元素已知的目标数组destArray;执行数组复制System.arraycopy(srcArray,0,destArray,6,3)方法,将源数组索引0开始3个元素复制到目标数组索引开始的3个位置索引上。
2.4.5 Arrays.copyOf()和System.arraycopy()联系与区别
- 联系:
- 分析源码,Arrays.copyOf()方法内部调用了System.arraycopy()方法
- 区别:
- System.arraycopy()实现将目标数组拷贝到自己定义的目标数组中,并能够选择拷贝的起点和长度以及放入新数组中的位置
- Arrays.copyOf()是将源数组复制为一个指定长度的目标新数组,并能够返回该目标数组。
3、总结
List集合:
- 【1】List集合代表一个元素有序、可重复的集合,集合中的每个元素都有对应的顺序索引。
- 【2】List集合允许使用重复元素,可存放多个null元素,有的元素是以一种线性方式进行存储的。
- 【3】可以通过索引来访问集合中的指定元素,可以通过索引来访问指定位置的集合元素,且默认按照元素的添加顺序设置元素的索引。
ArrayList数组:
- 【1】 ArrayList是一个保存数据的数组,ArrayList实现java.io.Serializable的方式。当写入到输出流时,先写入“容量”,再依次写入“每一个元素”;当读出输入流时,先读取“容量”,再依次读取“每一个元素”。
- 【2】使用ArrayList的默认构造函数时,ArrayList默认容量大小:10
- 【3】当ArrayList容量小于所需元素个数时,ArrayList将扩容:新的容量=“1.5 * 原始容量”
- 【4】ArrayList的克隆函数,就是将全部源数组元素克隆到一个新的目标数组中。