0%

Java数组扩容的三大方式

方案1:新建数组

这种方法新建的数组必须要比原先的长度要长,然后将原来的数组内容移到新的数组中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int[] a = {1, 2, 3, 4, 5};

// 创建新数组,长度为源数组的两倍
int[] b = new int[a.length * 2];

// 将旧数组内容复制到新数组
for (int i = 0; i < a.length; i++) {
b[i] = a[i];
}

// 新数组内容赋值给源数组
a = b;

// 打印结果
System.out.println(Arrays.toString(a));

输出结果

1
[1, 2, 3, 4, 5, 0, 0, 0, 0, 0]

方案2:Arrays.copyOf

1
2
3
4
5
6
7
int[] a = {1, 2, 3, 4, 5};

// 第一个参数是拷贝的数组,第二个参数是扩容长度,且返回一个新数组
a = Arrays.copyOf(a, a.length * 2);

// 打印结果
System.out.println(Arrays.toString(a));

输出结果

1
[1, 2, 3, 4, 5, 0, 0, 0, 0, 0]

Arrays.copyof是用于数组进行复制时常使用的方法,本身在Arrays类里面,而之所以能这么使用而不用创建对象在于该方法本身由static修饰,被static修饰的方法可以在该类创建对象前载入jvm。

1
2
3
4
5
6
public static long[] copyOf(long[] original, int newLength) {
long[] copy = new long[newLength];
System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
return copy;
}

通过上面的代码可以看出,其本质是调用了System.arraycopy方法。

先产生一个新的数组然后调用arraycopy方法最后在返回产生的新数组。但是我们进行数组扩容的时候禅城了新数组,但是原数组依然存在,造成了内存的浪费。

方案3:System.arraycopy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int[] a = {1, 2, 3, 4, 5};

// 定义新数组,长度为源数组的两倍
int[] b = new int[a.length * 2];

/**
* src 需要拷贝的源数组
* srcPos 源数组中的起始位置
* dest 目标数组
* destPos 目标数组中的起始位置
* length 要复制的数组元素数量
*/
System.arraycopy(a, 0, b, 0, a.length);

// 新数组内容赋值给原数组
a = b;

// 打印结果
System.out.println(Arrays.toString(a));

输出结果

1
[1, 2, 3, 4, 5, 0, 0, 0, 0, 0]

arraycopy源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* @param src the source array. 源数组
* @param srcPos starting position in the source array. 要复制的源数组的起始位置
* @param dest the destination array. 目标数组
* @param destPos starting position in the destination data. 目标数组的起始位置
* @param length the number of array elements to be copied. 要复制的长度

* @throws IndexOutOfBoundsException if copying would cause
* access of data outside array bounds.
* 如果复制会导致数据的访问超出数组边界。
* 则会报IndexOutOfBoundsException索引越界异常

* @throws ArrayStoreException if an element in the <code>src</code> array
* could not be stored into the
<code>dest</code> array
* because of a type mismatch.
* 如果由于类型不匹配而无法将src数组中的元素存储到dest数组中。
* 则会报 ArrayStoreException数组存储异常

* @throws NullPointerException if either <code>src</code> or
* <code>dest</code> is <code>null</code>.
* 如果src或dest为null。
* 则会报NullPointerException空指针异常
*/

public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);

System.arraycopy()的几种常用方法(普通for循环和arraycopy方法对比)

1. 从旧数组拷贝到新数组
1
2
3
4
5
//从旧数组拷贝到新数组
for (int i=0;i<size;i++){
arrayNew[i]=array[i];
}
System.arraycopy(array, 0, arrayNew, 0, array.length);
2. 从左向右循环,逐个元素向左挪一位。
1
2
3
4
5
//从左向右循环,逐个元素向左挪一位。
for (int i = index; i < size - 1; i++) {
array[i] = array[i + 1];
}
System.arraycopy(array, index + 1, array, index, size - 1 - index);
3. 从右向左循环,逐个元素向右挪一位。
1
2
3
4
5
6
//从右向左循环,逐个元素向右挪一位。
for (int i = size - 1; i >= index; i--) {
array[i + 1] = array[i];
}
System.arraycopy(array, index, array, index + 1, size - index);

System.arraycopy()深层理解

深复制还是浅复制

1
2
3
先说结论 :
当数组为一维数组,且元素为基本类型或String类型时,属于深拷贝,即原数组与新数组的元素不会相互影响。
当数组为多维数组,或一维数组中的元素为引用类型时,属于浅拷贝,原数组与新数组的元素引用指向同一个对象。
引用对象

构建一个User类型源数组,复制后至target数组,比较第一个元素的内存地址,判断结果是相同的,证明为浅复制;后修改target数组数组的随机元素,发现原来的值也变了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class SystemArrayCopyTestCase {

public static void main(String[] args) {
User[] users = new User[] {
new User(1),
new User(2),
new User(3) };// 初始化对象数组

User[] target = new User[users.length];// 新建一个目标对象数组

System.arraycopy(users, 0, target, 0, users.length);// 实现复制

System.out.println("源对象与目标对象的物理地址是否一样:" + (users[0] == target[0] ? "浅复制" : "深复制")); //浅复制

target[0].setId(5);

System.out.println("修改目标对象的属性值后源对象users:");
for (User user : users) {
System.out.println(user);
}

}
}

class User {


}

System.arraycopy() 在拷贝数组的时候,采用的使用潜复制,复制结果是一维的引用变量传递给新数组的一维数组,修改新数组时,会影响原来的数组。

一维数组和多维数组

将一维数组作源数组,进行拷贝,产生target数组;然后修改target数组中的元素,新数组没变,证明是值拷贝,修改新数组不会影响原来的值。

将多维数组作源数组,进行拷贝至目标数组,修改至目标数组的元素,新数组也变了,说明是二者是相同的引用,而这时改变其中任何一个数组的元素的值,其实都修改了共同数组元素的值,所以原数组和新数组的元素值都一样了

线程是否安全(摘自网络)

System.ayyaycopy是不安全的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
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.

1
2
3
4
5
run count: 1
Exception in thread "main" java.lang.RuntimeException: System.arraycopy is not thread safe
at ArrayCopyThreadSafe.doThreadSafeCheck(ArrayCopyThreadSafe.java:54)
at ArrayCopyThreadSafe.main(ArrayCopyThreadSafe.java:60)

结论 :System.ayyaycopy是不安全的。

System.arraycopy()和for()相比谁更高效

当测试数组的范围比较小的时候,两者相差的时间无几,当测试数组的长度达到百万级别,System.arraycopy的速度优势就开始体现了,根据对底层的理解,System.arraycopy是对内存直接进行复制,减少了for循环过程中的寻址时间,从而提高了效能。

如果有帮助到你的话,请关注我的公众号叭

个人博客 白都 (baidu2001.top)

CSDN 白.都

有帮助的话可以来打赏一些或者经常来看看我哦,我在这里等你!