转自:https://segmentfault.com/a/1190000009922279
看 JDK 源码的时候,Java 开发设计者在对数组的复制时,通常都会使用 System.arraycopy() 方法。
其实对数组的复制,有四种方法:
-
for
-
clone
-
System.arraycopy
-
arrays.copyof
本文章主要分析 System.arraycopy() ,带着几个问题去看这个方法:
-
深复制,还是浅复制
-
String 的一维数组和二维数组复制是否有区别
-
线程安全,还是不安全
-
高效还是低效
System.arraycopy() 的 API :
public static void arraycopy(
Object src, //源数组
int srcPos, //源数组的起始位置
Object dest, //目标数组
int destPos, //目标数组的起始位置
int length //复制长度
)
深复制还是浅复制
代码:对象数组的复制:
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循环过程中的寻址时间,从而提高了效能。
参考java核心技术中System.arraycopy用法:
public static void main(String[] args) { int[] a = {5,4,3,2,1}; Arrays.sort(a); a = (int[])goodCopyOf(a,10); System.out.println(Arrays.toString(a)); String[] s = {"a","b","c"}; s = (String[])goodCopyOf(s,10); System.out.println(Arrays.toString(s)); } public static Object goodCopyOf(Object o,int newLength){ Class cl = o.getClass(); if(!cl.isArray()) return null; Class ct = cl.getComponentType(); int length = Array.getLength(o); Object newArray = Array.newInstance(ct,newLength); System.arraycopy(o,0,newArray,0,Math.min(length,newLength)); return newArray; }
相关推荐
在Java中有多种方式可以实现数组合并,本文将介绍两种常见的实现方式,即使用System.arraycopy()方法和ArrayUtils.addAll()方法。 1. System.arraycopy()方法 System.arraycopy()方法是Java提供的一种本地方法,...
在Java中,可以使用`System.arraycopy()`方法来复制数组。 **1. 方法签名** ```java public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length) ``` - **参数解释**: - `src...
Java 字符数组合并方法详解 Java 语言中,数组合并是指将多个数组元素合并到一个新的数组中。这种操作在实际开发中非常常见,本文将介绍三种字符数组合并的方法。 方法一:使用循环遍历 在第一个方法中,我们使用...
需要注意的是,在 add(int index, E element) 方法中,我们使用 System.arraycopy 方法来复制数组元素,这是一个 native 方法,它的实现是基于 C++ 语言的。很多人认为 System.arraycopy 方法是调用底层的 memcpy ...
- `System.arraycopy()`:高效地复制一个数组的一部分到另一个数组。 5. **JVM操作**: - `System.gc()`:触发垃圾收集,虽然不推荐直接使用,但在某些场景下可能需要手动调用。 - `System.runFinalization()`:...
3. 数组的复制:使用 System.arraycopy() 方法或 Array.copyOf() 方法对当前数组进行复制操作。 4. 数组的排序:使用 Arrays.sort() 方法或冒泡排序法等对数组进行排序。 五、数组的使用 数组在 Java 编程中具有...
- 数组截取:`System.arraycopy(源数组, 源起始位置, 目标数组, 目标起始位置, 复制长度);` - 数组扩容:`ary = Arrays.copyOf(ary, ary.length + 1);` #### 面向对象的三大原则 - **封装**:隐藏对象的属性和...
- 使用`System.arraycopy`方法: ```java System.arraycopy(src, srcPos, dest, destPos, length); ``` - 使用`Arrays.copyOf`方法(JDK 1.6+): ```java Arrays.copyOf(src, length); ``` #### 八、访问...
- `JUtils.arrayCopy(Object[] src, Object[] dest)`: 实现数组的复制,类似于System.arraycopy()。 - `JUtils.equals(Object[] array1, Object[] array2)`: 比较两个数组的内容是否完全相同。 6. **IO操作**: ...
16. 数组复制:`System.arraycopy()` 方法用于复制数组的一部分到另一个数组。 17. 字符串操作:`str.charAt(n-1)` 获取字符串中的最后一个字符。 18. 双重循环:`for` 循环常用于处理二维数组。`i=0;i;i++` 和 `i...
### Java常用API详解 #### 一、Java面向对象基本概念 Java是一种主要基于面向对象编程(OOP)的语言,除了原始数据类型(如int、double等),几乎所有其他元素都是对象。对象是类的一个实例,拥有其特定的状态和行为...
Android实现连续点击多次事件的代码详解 Android实现连续点击多次事件的代码...Android实现连续点击多次事件的代码详解涵盖了数组、时间戳、System.arraycopy()方法等多种知识点,为Android开发者提供了有价值的参考。
**优化说明**: `System.arraycopy()`方法提供了高效的数组复制功能,其内部实现利用了平台特定的优化技术。因此,相比于使用循环逐个复制数组元素,这种方法能够显著提高性能。 #### 五、让访问实例内变量的getter/...
- **复制**: `System.arraycopy()`或`Arrays.copyOf()`用于复制数组。 - **比较**: `Arrays.equals()`用于比较两个数组是否相等。 - **排序**: `Arrays.sort()`可以对数组进行排序。 - **查找**: `Arrays.binary...
3. **数组复制**:Java中,可以通过for循环或者System.arraycopy()方法来复制数组。例如,将`array1`复制到`array2`,可以这样实现: ```java int[] array1 = {1, 2, 3}; int[] array2 = new int[array1.length];...
在Java中,我们可以使用`System.arraycopy()`方法来实现字符串的复制。这里是完成此功能的代码: ```java public char[] strcpy(char[] src) { // 创建一个新的字符数组,长度与源数组相同 char[] dest = new ...
**数组拷贝**:`System.arraycopy()`方法用于将一个数组的一部分复制到另一个数组中,语法如下: ```java System.arraycopy(copyFrom, 2, copyTo, 0, 7); ``` 这里将`copyFrom`数组从索引2开始的7个元素复制到`copy...
clone方法是Object类的protected方法,用于创建对象的副本,而arraycopy是System类的静态方法,用于将一个数组的内容复制到另一个数组中。 1. clone方法: clone方法是Object类的protected方法,用于创建对象的...
8. System类的arraycopy方法: 将字符数组{'i','t','c','a','s','a'}的第二个元素复制到末尾,覆盖原有元素:`char[] chars = {'i','t','c','a','s','a'}; System.arraycopy(chars, 1, chars, 5, 1);` 然后遍历数组...
- **数组复制**:学会使用`System.arraycopy()`方法复制数组。 **实验步骤详解**: 1. **生成随机数组**: - 用户输入数组长度N。 - 生成N个100以内的随机正整数并存入数组。 2. **显示数组**: - 输出数组s[]的...