JDK源码解析——集合(一)数组 ArrayList
JDK源码解析——ArrayList
本文针对JDK1.8
概述
ArrayList ,基于 []
数组实现的,支持自动扩容的动态数组。相比数组来说,因为其支持自动扩容的特性,成为我们日常开发中,最常用的集合类,没有之一。
类图
属性
private static final long serialVersionUID = 8683452581122892189L;
/**
* Default initial capacity.默认容量是10
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* Shared empty array instance used for empty instances.
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* Shared empty array instance used for default sized empty instances. We
* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
* first element is added.
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData; // non-private to simplify nested class access
/**
* The size of the ArrayList (the number of elements it contains).
*
* @serial
*/
private int size;
elementData
属性:元素数组。其中,图中红色空格代表我们已经添加元素,白色空格代表我们并未使用。size
属性:数组大小。注意,size
代表的是 ArrayList 已使用elementData
的元素的数量,对于开发者看到的#size()
也是该大小。并且,当我们添加新的元素时,恰好其就是元素添加到elementData
的位置(下标)。当然,我们知道 ArrayList 真正的大小是elementData
的大小。
构造方法
ArrayList 一共有三个构造方法,我们分别来看看。
① #ArrayList(int initialCapacity)
#ArrayList(int initialCapacity)
构造方法,根据传入的初始化容量,创建 ArrayList 数组。如果我们在使用时,如果预先指到数组大小,一定要使用该构造方法,可以避免数组扩容提升性能,同时也是合理使用内存。代码如下:
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);
}
}
- 如果初始化容量为 0 时,使用
EMPTY_ELEMENTDATA
空数组。在添加元素的时候,会进行扩容创建需要的数组。笔者会在下文中介绍扩容机制。
② #ArrayList(Collection<? extends E> c)
#ArrayList(Collection<? extends E> c)
构造方法,使用传入的 c
集合,作为 ArrayList 的 elementData
。代码如下:
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
//JDK8存在BUG,它在 JDK9 中被解决,。
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
③ #ArrayList()
无参数构造方法 #ArrayList()
构造方法,也是我们使用最多的构造方法。代码如下:
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
Q: public ArrayList(0)
和public ArrayList()
两个构造方法有什么区别?
回答这个问题之前,先看一下ArrayList
的两个成员变量:
这是两个空的Object数组,但需注意这是两个数组对象。
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
可以看到,public ArrayList(0)
会将elementData
数组赋值为EMPTY_ELEMENTDATA
public ArrayList()
会将elementData
数组赋值为DEFAULTCAPACITY_EMPTY_ELEMENTDATA
,虽然都是空的数组,但这是两个对象!
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);
}
}
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
在下文中,我们会看到 DEFAULTCAPACITY_EMPTY_ELEMENTDATA
首次扩容为 10 ,而 EMPTY_ELEMENTDATA
按照 1.5 倍扩容从 0 开始而不是 10 。两者的起点不同。
添加单个元素
#add(E e)
方法,顺序添加单个元素到数组,该方法返回boolean变量。代码如下:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
#add(int index, E element)
方法,顺序插入单个元素到数组。前提是index > size || index < 0
代码如下:
public void add(int index, E element) {
rangeCheckForAdd(index);//先检查index是否合法
ensureCapacityInternal(size + 1); // Increments modCount!!
// 将 index + 1 位置开始的元素,进行往后挪
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
- 值得注意的是这里的
#ensureCapacityInternal(int minCapacity)
方法。它的作用是保证elementData
数组容量至少有minCapacity
,minCapacity
至少为1
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//如果是DEFAULTCAPACITY_EMPTY_ELEMENTDATA对象,则将minCapacity设为DEFAULT_CAPACITY(10)
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
其中#ensureExplicitCapacity(int minCapacity)
方法
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)//如果minCapacity超出elementData数组长度,则扩容
grow(minCapacity);
}
数组扩容
#grow()
方法,扩容数组,并返回它。整个的扩容过程,首先创建一个新的更大的数组,一般是 1.5 倍大小(为什么说是一般呢,稍后我们会看到,会有一些小细节),然后将原数组复制到新数组中,最后返回新数组。代码如下:
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);//1.5倍扩容
if (newCapacity - minCapacity < 0)
//此时有一种情况:如果elementData为空,newCapacity仍为0,newCapacity置为1
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);
}
其中,#hugeCapacity(int 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;
}
Q:ArrayList最大容量问题?
rivate static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
最大容量为Integer.MAX_VALUE-8,
public ArrayList(int initialCapacity)
通过以上构造函数指定初始容量,而int最大值就是Integer.MAX_VALUE,-8是为了避免oom,因为有些vm可能存储头信息在数组里。因此最大容量为Integer.MAX_VALUE-8。但是当MAX_ARRAY_SIZE仍不够时,容量会扩展至HugeCapacity,为Integer.MAX_VALUE。
private void grow(int minCapacity) {//保证 elementData 数组容量至少有 minCapacity
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);//1.5倍扩容
if (newCapacity - minCapacity < 0)//若扩容之后,新容量还不够,就将新容量设为minCapacity
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);
}
数组缩容
#trimToSize()
,将elementData数组长度裁剪到当前size大小。代码如下:
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA//如果elementData已空,则将EMPTY_ELEMENTDATA赋值过去
: Arrays.copyOf(elementData, size);
}
}
- 这点和
HashMap
不同,HashMap
未提供缩容方法。
添加多个元素
#addAll(Collection<? extends E> c)
方法,批量添加多个元素。在我们明确知道会添加多个元素时,推荐使用该该方法而不是添加单个元素,避免可能多次扩容。代码如下:
//向elementData数组末尾添加元素
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount,保证 elementData 数组容量够用
System.arraycopy(a, 0, elementData, size, numNew);//直接在elementData数组末尾添加元素
size += numNew;
return numNew != 0;//若添加的Collection为空,则返回false
}
#addAll(int index, Collection<? extends E> c)
方法,从指定位置开始插入多个元素,前提是index > size || index < 0
。代码如下:
//向elementData数组中间(index处)插入元素
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);//先检查index是否合法
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount,保证 elementData 数组容量够用
int numMoved = size - index;
if (numMoved > 0)
//elementData数组中间(index处)插入元素
//例如elementData数组原来为{"a","b","c","d"},需要在index=1处添加数据{"q","w"}
//插入之后,elementData数组变为{"a","q","w","b","c","d"}
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);//elementData数组index及以后元素往后移位index + numNew
System.arraycopy(a, 0, elementData, index, numNew);//将a数组填入
size += numNew;
return numNew != 0;
}
移除单个元素
#remove(int index)
方法,移除指定位置的元素,并返回该位置的原元素。代码如下:
public E remove(int index) {
rangeCheck(index);//先检查index是否合法,index >= size则抛异常
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;
}
#remove(Object o)
方法会移除数组中第一个出现的元素。代码如下:
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;
}
- 其中,
#fastRemove(int index)
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
}
移除多个元素
#removeAll(Collection<?> c)
,批量移除elementData
中指定的多个元素。
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, false);
}
#retainAll(Collection<?> c)
,保留elementData
中指定的多个元素,其余的全部删除。
public boolean retainAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, true);
}
- 上述两个方法都是用
#batchRemove(Collection<?> c, boolean complement)
来实现的,他们的区别就是boolean complement
是否为true。
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
int r = 0, w = 0;
boolean modified = false;
try {
for (; r < size; r++)
if (c.contains(elementData[r]) == complement)// 判断符合条件
// 移除的方式,通过将当前值 写入到 w 位置,然后 w 跳到下一个位置。
elementData[w++] = elementData[r];
} finally {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
if (r != size) {
//如果 contains 方法发生异常,则将 elementData的元素从 r 位置的数据写入到 es 从 w 开始的位置
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
if (w != size) {
// clear to let GC do its work
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;
modified = true;
}
}
return modified;
}
- 如果觉得绕,多调试,可以手绘点图,辅助理解下哈。
查找单个元素
#indexOf(Object o)
方法,查找首个为指定元素的位置,如果没有找到,则返回-1
。代码如下:
public int indexOf(Object o) {
//分为o为null和不为null两种情况
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;
}
而 #contains(Object o)
方法,就是基于该方法实现。代码如下:
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
获得指定位置的元素
#get(int index)
方法,获得指定位置的元素。代码如下:
public E get(int index) {
rangeCheck(index);//先检查index是否合法,index >= size则抛异常
return elementData(index);
}
设置指定位置的元素
#set(int index, E element)
方法,设置指定位置的元素,返回旧值。代码如下:
public E set(int index, E element) {
rangeCheck(index);//先检查index是否合法,index >= size则抛异常
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
转换成数组
#toArray()
方法,将 ArrayList 转换成 []
数组。代码如下:
// ArrayList.java
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}
// Arrays.java
public static <T> T[] copyOf(T[] original, int newLength) {
return (T[]) copyOf(original, newLength, original.getClass());
}
- 注意,返回的是
Object[]
类型。
实际场景下,我们可能想要指定 T
泛型的数组,那么我们就需要使用到 #toArray(T[] a)
方法。代码如下:
public <T> T[] toArray(T[] a) {
if (a.length < size)//如果传入的数组小于 size 大小,则直接复制一个新数组返回
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
//将 elementData 复制到 a 中
System.arraycopy(elementData, 0, a, 0, size);
//如果传入的数组大于 size 大小,则将 size 赋值为 null
if (a.length > size)
a[size] = null;
return a;
}
- 所以有两种方法获取指定
T
泛型的数组:(假设我们想要将list转为String类型的数组)list.toArray(new Sring[0]);
返回一个新数组list.toArray(new Sring[list.size()]);
返回的就是a数组,这个性能略高一点~
创建子数组
#subList(int fromIndex, int toIndex)
方法,创建 ArrayList 的子数组,注意不包含toIndex
哦。代码如下:
public List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);//检查参数是否合法
return new SubList(this, 0, fromIndex, toIndex);
}
private static class SubList<E> extends AbstractList<E> implements RandomAccess {
/**
* 根 ArrayList
*/
private final ArrayList<E> root;
/**
* 父 SubList
*/
private final SubList<E> parent;
/**
* 起始位置
*/
private final int offset;
/**
* 大小
*/
private int size;
// ... 省略代码
}
- 实际使用时,一定要注意,SubList 不是一个只读数组,而是和根数组
root
共享相同的elementData
数组,只是说限制了[fromIndex, toIndex)
的范围。
System.arraycopy() :
public static void arraycopy(
Object src, //源数组
int srcPos, //源数组的起始位置
Object dest, //目标数组
int destPos, //目标数组的起始位置
int length //复制长度
)
下文参考:https://segmentfault.com/a/1190000009922279
深复制还是浅复制
代码:对象数组的复制:
public class SystemArrayCopyTestCase {
public static void main(String[] args) {
User[] users = new User[] {
new User(1, "seven", "seven@qq.com"),
new User(2, "six", "six@qq.com"),
new User(3, "ben", "ben@qq.com") };// 初始化对象数组
User[] target = new User[users.length];// 新建一个目标对象数组
System.arraycopy(users, 0, target, 0, users.length);// 实现复制
System.out.println("源对象与目标对象的物理地址是否一样:" + (users[0] == target[0] ? "浅复制" : "深复制")); //浅复制
target[0].setEmail("admin@sina.com");
System.out.println("修改目标对象的属性值后源对象users:");
for (User user : users) {
System.out.println(user);
}
//
//
//
}
}
class User {
private Integer id;
private String username;
private String email;
// 无参构造函数
public User() {
}
// 有参的构造函数
public User(Integer id, String username, String email) {
super();
this.id = id;
this.username = username;
this.email = email;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
@Override
public String toString() {
return "User [id=" + id + ", username=" + username + ", email=" + email + "]";
}
}
图示:对象复制的图示
所以,得出的结论是,System.arraycopy()
在拷贝数组的时候,采用的使用浅复制,复制结果是一维的引用变量传递给副本的一维数组,修改副本时,会影响原来的数组。
一维数组和多维数组的复制的区别
代码:一维数组的复制
String[] st = {"A","B","C","D","E"};
String[] dt = new String[5];
System.arraycopy(st, 0, dt, 0, 5);
//改变dt的值
dt[3] = "M";
dt[4] = "V";
System.out.println("两个数组地址是否相同:" + (st == dt)); //false
for(String str : st){
System.out.print(" " + str +" "); // A B C D E
}
System.out.println();
for(String str : dt){
System.out.print(" " + str +" "); // A B C M V
}
使用该方法对一维数组在进行复制之后,目标数组修改不会影响原数据,这种复制属性值传递,修改副本不会影响原来的值。
但是,请重点看以下代码:
String[] st = {"A","B","C","D","E"};
String[] dt = new String[5];
System.arraycopy(st, 0, dt, 0, 5);
for(String str : st){
System.out.print(" " + str +" "); // A B C D E
}
System.out.println();
for(String str : dt){
System.out.print(" " + str +" "); // A B C D E
}
System.out.println("数组内对应位置的String地址是否相同:" + st[0] == dt[0]); // true
既然是属性值传递,为什么 st[0] == dt[0]
会相等呢? 我们再深入验证一下:
String[] st = {"A","B","C","D","E"};
String[] dt = new String[5];
System.arraycopy(st, 0, dt, 0, 5);
dt[0] = "F" ;
for(String str : st){
System.out.print(" " + str +" "); // A B C D E
}
System.out.println();
for(String str : dt){
System.out.print(" " + str +" "); // F B C D E
}
System.out.println("数组内对应位置的String地址是否相同:" + st[0] == dt[0]); // false
为什么会出现以上的情况呢?
通过以上两段代码可以推断,在System.arraycopy()
进行复制的时候,首先检查了字符串常量池是否存在该字面量,一旦存在,则直接返回对应的内存地址,如不存在,则在内存中开辟空间保存对应的对象。
代码:二维数组的复制
String[][] s1 = {
{"A1","B1","C1","D1","E1"},
{"A2","B2","C2","D2","E2"},
{"A3","B3","C3","D3","E3"}
};
String[][] s2 = new String[s1.length][s1[0].length];
System.arraycopy(s1, 0, s2, 0, s2.length);
for(int i = 0;i < s1.length ;i++){
for(int j = 0; j< s1[0].length ;j++){
System.out.print(" " + s1[i][j] + " ");
}
System.out.println();
}
// A1 B1 C1 D1 E1
// A2 B2 C2 D2 E2
// A3 B3 C3 D3 E3
s2[0][0] = "V";
s2[0][1] = "X";
s2[0][2] = "Y";
s2[0][3] = "Z";
s2[0][4] = "U";
System.out.println("----修改值后----");
for(int i = 0;i < s1.length ;i++){
for(int j = 0; j< s1[0].length ;j++){
System.out.print(" " + s1[i][j] + " ");
}
System.out.println();
}
// Z Y X Z U
// A2 B2 C2 D2 E2
// A3 B3 C3 D3 E3
上述代码是对二维数组进行复制,数组的第一维装的是一个一维数组的引用,第二维里是元素数值。对二维数组进行复制后,第一维的引用被复制给新数组的第一维,也就是两个数组的第一维都指向相同的“那些数组”。而这时改变其中任何一个数组的元素的值,其实都修改了“那些数组”的元素的值,所以原数组和新数组的元素值都一样了。
线程安全,还是不安全
代码:多线程对数组进行复制 (java中System.arraycopy
是线程安全的吗? )
public class ArrayCopyThreadSafe {
private static int[] arrayOriginal = new int[1024 * 1024 * 10];
private static int[] arraySrc = new int[1024 * 1024 * 10];
private static int[] arrayDist = new int[1024 * 1024 * 10];
private static ReentrantLock lock = new ReentrantLock();
private static void modify() {
for (int i = 0; i < arraySrc.length; i++) {
arraySrc[i] = i + 1;
}
}
private static void copy() {
System.arraycopy(arraySrc, 0, arrayDist, 0, arraySrc.length);
}
private static void init() {
for (int i = 0; i < arraySrc.length; i++) {
arrayOriginal[i] = i;
arraySrc[i] = i;
arrayDist[i] = 0;
}
}
private static void doThreadSafeCheck() throws Exception {
for (int i = 0; i < 100; i++) {
System.out.println("run count: " + (i + 1));
init();
Condition condition = lock.newCondition();
new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
condition.signalAll();
lock.unlock();
copy();
}
}).start();
lock.lock();
// 这里使用 Condition 来保证拷贝线程先已经运行了.
condition.await();
lock.unlock();
Thread.sleep(2); // 休眠2毫秒, 确保拷贝操作已经执行了, 才执行修改操作.
modify();
if (!Arrays.equals(arrayOriginal, arrayDist)) {
throw new RuntimeException("System.arraycopy is not thread safe");
}
}
}
public static void main(String[] args) throws Exception {
doThreadSafeCheck();
}
}
这个例子的具体操作是:
- arrayOriginal 和 arraySrc 初始化时是相同的, 而 arrayDist 是全为零的.
- 启动一个线程运行 copy() 方法来拷贝 arraySrc 到 arrayDist 中.
- 在主线程执行 modify() 操作, 修改 arraySrc 的内容. 为了确保 copy() 操作先于 modify() 操作, 我使用 Condition, 并且延时了两毫秒, 以此来保证执行拷贝操作(即System.arraycopy) 先于修改操作.
- 根据第三点, 如果 System.arraycopy 是线程安全的, 那么先执行拷贝操作, 再执行修改操作时, 不会影响复制结果, 因此 arrayOriginal 必然等于 arrayDist; 而如果 System.arraycopy 是线程不安全的, 那么 arrayOriginal 不等于 arrayDist.
根据上面的推理, 运行一下程序, 有如下输出:
run count: 1
run count: 2
Exception in thread "main" java.lang.RuntimeException: System.arraycopy is not thread safe
at com.test.ArrayCopyThreadSafe.doThreadSafeCheck(ArrayCopyThreadSafe.java:62)
at com.test.ArrayCopyThreadSafe.main(ArrayCopyThreadSafe.java:68)
所以,System.arraycopy
是不安全的。
高效还是低效
代码:for vs System.arraycopy
复制数组
String[] srcArray = new String[1000000];
String[] forArray = new String[srcArray.length];
String[] arrayCopyArray = new String[srcArray.length];
//初始化数组
for(int index = 0 ; index < srcArray.length ; index ++){
srcArray[index] = String.valueOf(index);
}
long forStartTime = System.currentTimeMillis();
for(int index = 0 ; index < srcArray.length ; index ++){
forArray[index] = srcArray[index];
}
long forEndTime = System.currentTimeMillis();
System.out.println("for方式复制数组:" + (forEndTime - forStartTime));
long arrayCopyStartTime = System.currentTimeMillis();
System.arraycopy(srcArray,0,arrayCopyArray,0,srcArray.length);
long arrayCopyEndTime = System.currentTimeMillis();
System.out.println("System.arraycopy复制数组:" + (arrayCopyEndTime - arrayCopyStartTime));
通过以上代码,当测试数组的范围比较小的时候,两者相差的时间无几,当测试数组的长度达到百万级别,System.arraycopy的速度优势就开始体现了,根据对底层的理解,System.arraycopy是对内存直接进行复制,减少了for循环过程中的寻址时间,从而提高了效能。
总结
-
ArrayList 是基于
[]
数组实现的 List 实现类,支持在数组容量不够时,一般按照 1.5 倍自动扩容。同时,它支持手动扩容、手动缩容。 -
源码当中涉及到许多数组的复制迁移,
System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
这个方法属于浅复制,线程不安全,它减少了for循环过程中的寻址时间,相较于for
循环来说,提高了性能。
如果文中有描述不对的地方,敬请批评指正。
以上。