1.概述

转载:System.arraycopy为什么快

作者:RednaxelaFX 链接:知乎大佬

转载这篇文章的原因是,我看Java源码,发现有好多地方用到了System.arraycopy,比如hashMap,当时很纳闷的是,为何改变一个值就要拷贝,而且数据量那么大,不会消耗性能吗?为啥不是改变一下数据地址之类的。

然后就去研究了一下System.arraycopy为什么快

2.前言

在 Java 编程中经常会遇到数组拷贝操作,一般会有如下四种方式对数组进行拷贝。

  1. for遍历,遍历源数组并将每个元素赋给目标数组。
  2. clone方法,原数组调用clone方法克隆新对象赋给目标数组,更深入的克隆可以看之前的文章《从JDK角度看对象克隆》。
  3. System.arraycopy,JVM 提供的数组拷贝实现。
  4. Arrays.copyof,实际也是调用System.arraycopy。

2.1 for遍历

这种情况下是在 Java 层编写 for 循环遍历数组每个元素并进行拷贝,如果没有被编译器优化,它对应的就是遍历数组操作的字节码,执行引擎就根据这些字节码循环获取数组的每个元素再执行拷贝操作

2.2 clone方法

2.3 arraycopy的使用

使用很简单,比如如下方式进行数组拷贝。

int size = 10000;
int[] src = new int[size];
int[] des = new int[size];
System.arraycopy(src, 0, des, 0, size);

2.3.1 arraycopy方法

该方法用于从指定源数组中进行拷贝操作,可以指定开始位置,拷贝指定长度的元素到指定目标数组中。该方法是一个本地方法,声明如下:

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

2.3.2 关于@HotSpotIntrinsicCandidate

这个注解是 HotSpot VM 标准的注解,被它标记的方法表明它为 HotSpot VM 的固有方法, HotSpot VM 会对其做一些增强处理以提高它的执行性能,比如可能手工编写汇编或手工编写编译器中间语言来替换该方法的实现。虽然这里被声明为 native 方法,但是它跟 JDK 中其他的本地方法实现地方不同固有方法会在 JVM 内部实现,而其他的会在 JDK 库中实现。在调用方面,由于直接调用 JVM 内部实现,不走常规 JNI lookup,所以也省了开销

2.3.3 本地arraycopy方法

Java 的 System 类有个静态块在类加载时会执行,它对应执行了 registerNatives 本地方法。

public final class System {private static native void registerNatives();static {registerNatives();}
}

而在对应的 System.c 中的 Java_java_lang_System_registerNatives方法如下,可以看到有三个本地方法绑定到 JVM 的固有方法了,其中一个就是 arraycopy,它对应的函数为(void *)&JVM_ArrayCopy

JNIEXPORT void JNICALL
Java_java_lang_System_registerNatives(JNIEnv *env, jclass cls)
{(*env)->RegisterNatives(env, cls,methods, sizeof(methods)/sizeof(methods[0]));
}#define OBJ "Ljava/lang/Object;"
static JNINativeMethod methods[] = {{"currentTimeMillis", "()J",              (void *)&JVM_CurrentTimeMillis},{"nanoTime",          "()J",              (void *)&JVM_NanoTime},{"arraycopy",     "(" OBJ "I" OBJ "II)V", (void *)&JVM_ArrayCopy},
};

那么通过以上就将arraycopy方法绑定到下面的JVM_ArrayCopy函数,前面的逻辑主要用于检查源数组和目标数组是否为空,为空则抛空指针;接着分别将源数组对象和目标数组对象转换成arrayOop,即数组对象描述,assert用于判断它们是否为对象;最后的s->klass()->copy_array才是真正的数组拷贝操作。

JVM_ENTRY(void, JVM_ArrayCopy(JNIEnv *env, jclass ignored, jobject src, jint src_pos,jobject dst, jint dst_pos, jint length))JVMWrapper("JVM_ArrayCopy");// Check if we have null pointersif (src == NULL || dst == NULL) {THROW(vmSymbols::java_lang_NullPointerException());}arrayOop s = arrayOop(JNIHandles::resolve_non_null(src));arrayOop d = arrayOop(JNIHandles::resolve_non_null(dst));assert(s->is_oop(), "JVM_ArrayCopy: src not an oop");assert(d->is_oop(), "JVM_ArrayCopy: dst not an oop");// Do copys->klass()->copy_array(s, src_pos, d, dst_pos, length, thread);
JVM_END

2.3.4 基本类型和普通类型

上面说到的通过s->klass()->copy_array完成拷贝操作,处理过程根据Java的不同类型其实有不同的处理,数组根据里面元素类型可分为基本类型和普通类型,对应到 JVM 分别为TypeArrayKlassObjArrayKlass

2.3.4.1 TypeArrayKlass

这里将一些校验源码去掉,留下核心代码,这里因为涉及到内存中指针的移动,所以为了提高赋值操作的效率将起始结束位置转成char*,log2_element_size就是计算数组元素类型长度的log值,后面通过位移操作快速计算位置。而array_header_in_bytes计算第一个元素的偏移。

void TypeArrayKlass::copy_array(arrayOop s, int src_pos, arrayOop d, int dst_pos, int length, TRAPS) {....int l2es = log2_element_size();int ihs = array_header_in_bytes() / wordSize;char* src = (char*) ((oop*)s + ihs) + ((size_t)src_pos << l2es);char* dst = (char*) ((oop*)d + ihs) + ((size_t)dst_pos << l2es);Copy::conjoint_memory_atomic(src, dst, (size_t)length << l2es);
}

接着到Copy::conjoint_memory_atomic函数,这个函数的主要逻辑就是判断元素属于哪种基本类型再调用各自的函数因为已经有起始和结尾的指针,所以可以根据不同类型进行快速的内存操作。这里以整型类型为例,将调用Copy::conjoint_jints_atomic函数。

void Copy::conjoint_memory_atomic(void* from, void* to, size_t size) {address src = (address) from;address dst = (address) to;uintptr_t bits = (uintptr_t) src | (uintptr_t) dst | (uintptr_t) size;if (bits % sizeof(jlong) == 0) {Copy::conjoint_jlongs_atomic((jlong*) src, (jlong*) dst, size / sizeof(jlong));} else if (bits % sizeof(jint) == 0) {Copy::conjoint_jints_atomic((jint*) src, (jint*) dst, size / sizeof(jint));} else if (bits % sizeof(jshort) == 0) {Copy::conjoint_jshorts_atomic((jshort*) src, (jshort*) dst, size / sizeof(jshort));} else {// Not aligned, so no need to be atomic.Copy::conjoint_jbytes((void*) src, (void*) dst, size);}
}

conjoint_jints_atomic函数主要是调用pd_conjoint_jints_atomic函数,该函数在不同的操作系统有自己的实现,这里看下windows_x86的实现,

static void conjoint_jints_atomic(jint* from, jint* to, size_t count) {assert_params_ok(from, to, LogBytesPerInt);pd_conjoint_jints_atomic(from, to, count);}

主要逻辑是分成两种情况复制:向前复制和向后复制并且是通过指针遍历数组来赋值,这里进行的是值拷贝,有些人称之为所谓的“深拷贝”。

static void pd_conjoint_jints_atomic(jint* from, jint* to, size_t count) {if (from > to) {while (count-- > 0) {// Copy forwards*to++ = *from++;}} else {from += count - 1;to   += count - 1;while (count-- > 0) {// Copy backwards*to-- = *from--;}}
}

对于long、short、byte等类型也是做类似的处理,但在某些操作系统的某些cpu架构上会使用汇编来实现。

2.3.4.2 ObjArrayKlass

再看普通类型对象作为数组元素时候的拷贝操作,这里将一些校验源码去掉,留下核心代码。UseCompressedOops标识表示对 JVM 中Java对象指针压缩,主要表示用32位还是64位作为对象指针。这里忽略它,直接看未压缩的情况,即会调用do_copy<oop>函数。

void ObjArrayKlass::copy_array(arrayOop s, int src_pos, arrayOop d,int dst_pos, int length, TRAPS) {...if (UseCompressedOops) {narrowOop* const src = objArrayOop(s)->obj_at_addr<narrowOop>(src_pos);narrowOop* const dst = objArrayOop(d)->obj_at_addr<narrowOop>(dst_pos);do_copy<narrowOop>(s, src, d, dst, length, CHECK);} else {oop* const src = objArrayOop(s)->obj_at_addr<oop>(src_pos);oop* const dst = objArrayOop(d)->obj_at_addr<oop>(dst_pos);do_copy<oop> (s, src, d, dst, length, CHECK);}
}

这块代码较长,同样地,我去掉一部分代码,留下能说明问题的一小部分代码。这里会进行s==d的判断是因为源数组和目标数组可能是相等的,而如果不相等的情况下则要判断源数组元素类型是否和目标数组元素类型一样,如果一样的话处理也做类似处理,另外这里还添加了是否为子类的判断。以上两种情况核心赋值算法都是Copy::conjoint_oops_atomic

template <class T> void ObjArrayKlass::do_copy(arrayOop s, T* src,arrayOop d, T* dst, int length, TRAPS) {BarrierSet* bs = Universe::heap()->barrier_set();if (s == d) {bs->write_ref_array_pre(dst, length);Copy::conjoint_oops_atomic(src, dst, length);} else {Klass* bound = ObjArrayKlass::cast(d->klass())->element_klass();Klass* stype = ObjArrayKlass::cast(s->klass())->element_klass();if (stype == bound || stype->is_subtype_of(bound)) {bs->write_ref_array_pre(dst, length);Copy::conjoint_oops_atomic(src, dst, length);} else {...}}bs->write_ref_array((HeapWord*)dst, length);
}

该函数也跟操作系统和cpu架构相关,这里看windows_x86的实现,很简单也是直接通过指针遍历赋值,oop是JVM层的对象类,而且该类也没有重写operator=操作符的,默认情况下是拷贝地址的,所以它们还是指向同一块内存,这反应到 Java 层也是这样的。即所谓的“浅拷贝”。

static void conjoint_oops_atomic(oop* from, oop* to, size_t count) {pd_conjoint_oops_atomic(from, to, count);
}
static void pd_conjoint_oops_atomic(oop* from, oop* to, size_t count) {if (from > to) {while (count-- > 0) {*to++ = *from++;}} else {from += count - 1;to   += count - 1;while (count-- > 0) {// Copy backwards*to-- = *from--;}}
}

4.案例

4.1 System.arrayCopy与for对比

package JCF.ArrayList;import java.util.Date;public class ArrayCopyCompare {public static void main(String[] args) {int length = 1000000;//initSystem.out.println("array length : "+length);int[] array = new int[length];for(int i = 0 ; i < array.length ; i ++){array[i] = i;}//use method by systemlong begin1 = new Date().getTime();int[] arrayCopyBySystem = new int[length];System.arraycopy(array, 0, arrayCopyBySystem, 0, array.length);long end1 = new Date().getTime();System.out.println("use time by system method : "+(end1 - begin1));//use method normallong begin2 = new Date().getTime();int[] arrayCopyByNormal = new int[length];for(int i = 0 ; i < arrayCopyByNormal.length ; i ++){arrayCopyByNormal[i] = array[i];}long end2 = new Date().getTime();System.out.println("use time by narmal method : " +(end2 - begin2));}
}

常用的数组复制方法就按照循环赋值的防范, 下面列举有不多长度数组的效率比较:

array length : 10000
use time by system method : 0
use time by narmal method : 0array length : 100000
use time by system method : 0
use time by narmal method : 2array length : 1000000
use time by system method : 3
use time by narmal method : 5array length : 10000000
use time by system method : 25
use time by narmal method : 29array length : 100000000
use time by system method : 279
use time by narmal method : 293

所以在数组长度不大时,两者效率可以忽略, 但是数组长度变大时,System.arrayCopy()还是有效率优势的, 但是感觉效率提升没有想象的大

4.2 copy2

package com.java.data.array;/*** @author: chuanchuan.lcc* @date: 2021-01-11 20:51* @modifiedBy: chuanchuan.lcc* @version: 1.0* @description:*/
public class ArrayCopyDemo1 {public static final int size = 1000000;public static void copyByArrayCopy(String[] strArray){Long startTime = System.currentTimeMillis();String[] destArray = new String[size];System.arraycopy(strArray,0,destArray,0,strArray.length);//printArr(destArray);Long endTime = System.currentTimeMillis();System.out.println("copyByArrayCopy cost time is "+(endTime-startTime));}public static void copyByLoop(String[] strArray){Long startTime = System.currentTimeMillis();String[] destArray = new String[size];for(int i = 0;i<strArray.length;i++){destArray[i] = strArray[i];}//printArr(destArray);Long endTime = System.currentTimeMillis();System.out.println("copyByLoop cost time is "+(endTime-startTime));}public static void copyByClone(String[] strArray){Long startTime = System.currentTimeMillis();String[] destArray = strArray.clone();Long endTime = System.currentTimeMillis();System.out.println("copyByClone cost time is "+(endTime-startTime));}public static void main(String args[]){String arr1[] = new String[size];for(int i=0;i<size;i++){arr1[i] = "this is a test"+i;//arr1[i] = "shishangzhiyoumamahaoyouamdehaizixiangkuaibaotoujinmamadehuaibaoxingfuxiangbuliaoshishangzhiyoumamahaomeimadehaizixianggencaolikaimamadehuaibaoxingfunalizhaoshishangzhiyoumamahaomeimadehaizibuzhidaoyaoshitazhidaomengliyehuixiao"+i;}String arr2[] = new String[size];for(int i=0;i<size;i++){arr2[i] = "this is a test"+i;//arr2[i] = "shishangzhiyoumamahaoyouamdehaizixiangkuaibaotoujinmamadehuaibaoxingfuxiangbuliaoshishangzhiyoumamahaomeimadehaizixianggencaolikaimamadehuaibaoxingfunalizhaoshishangzhiyoumamahaomeimadehaizibuzhidaoyaoshitazhidaomengliyehuixiao"+i;}String arr3[] = new String[size];for(int i=0;i<size;i++){arr3[i] = "this is a test"+i;//arr3[i] = "shishangzhiyoumamahaoyouamdehaizixiangkuaibaotoujinmamadehuaibaoxingfuxiangbuliaoshishangzhiyoumamahaomeimadehaizixianggencaolikaimamadehuaibaoxingfunalizhaoshishangzhiyoumamahaomeimadehaizibuzhidaoyaoshitazhidaomengliyehuixiao"+i;}copyByClone(arr1);copyByLoop(arr2);copyByArrayCopy(arr3);}public static void printArr(String[] strArray){for(String str:strArray){System.out.println(str);}}
}

测试结果如下

数据量:1000000
copyByClone cost time is 2
copyByLoop cost time is 9
copyByArrayCopy cost time is 1数据量:5000000
copyByClone cost time is 19
copyByLoop cost time is 27
copyByArrayCopy cost time is 12

5.总结

System.arraycopy为 JVM 内部固有方法,它通过手工编写汇编或其他优化方法来进行 Java 数组拷贝,这种方式比起直接在 Java 上进行 for 循环或 clone 是更加高效的。数组越大体现地越明显。

【java】System.arraycopy为什么快相关推荐

  1. Java System.arraycopy()方法示例

    Java System.arraycopy() is a native static method to copy elements from the source array to the dest ...

  2. Java System.arraycopy

    Java System.arraycopy 从指定源数组中进行拷贝操作,可以指定开始位置,拷贝指定长度的元素到指定目标数组中.该方法是一个本地静态方法,声明如下: public static nati ...

  3. System.arraycopy为什么快

    前言 在 Java 编程中经常会遇到数组拷贝操作,一般会有如下四种方式对数组进行拷贝. * for遍历,遍历源数组并将每个元素赋给目标数组. * clone方法,原数组调用clone方法克隆新对象赋给 ...

  4. [Java] System.arraycopy 数组复制

    函数原型: public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length) ; s ...

  5. java arraycopy 参数_java System.arraycopy()复制数组

    在数组的复制中,总共有四种方法可以实现.不过在追求效率上,本篇所要提到的System.arraycopy()方法,要比我们熟知的for循环快太多.考虑到这种方法使用不频繁,所以在接下来的学习中,我们先 ...

  6. java的arraycopy_java System.arrayCopy使用说明

    java System.arrayCopy使用说明 java.lang.System.arraycopy() 方法复制指定的源数组的数组,在指定的位置开始,到目标数组的指定位置. 下面是 System ...

  7. arraycopy java_Java System arraycopy()方法

    Java System arraycopy()方法 java.lang.System.arraycopy() 方法复制从指定源数组的数组,开始在指定的位置,到目标数组的指定位置. 阵列组件的子序列是从 ...

  8. java arraycopy 参数_java 数组复制:System.arrayCopy 深入解析

    先看ArrayList源码中数组复制的代码: 其实ArrayList 就是一个数组的形式存放数据的.没有高深的地方. 他的性能在于他的索引能力,正因为他是数组形式,所以索引元素的时候他表现得非常的快速 ...

  9. java arraycopy()方法_Java学习之System.arraycopy()方法

    java.lang.System的静态方法arraycopy()可以实现数组的复制,讲课的老师说这个方法效率比较高,如果数组有成千上万个元素,那么用这个方法,比用for语句循环快不少.System提供 ...

最新文章

  1. linux命令2--cd和pwd
  2. Android PackageManager packages.xml文件格式
  3. 亿万人抢10亿红包的数据监控,如何实现业务零资损?
  4. 移植opencv2.4.9到itop4412开发板
  5. 解决IIS中HTTP/1.1 500 Server Error错误的方法
  6. 从微信服务器获取用户信息,微信小程序Ⅴ [获取登陆用户信息,重点openID(详解)]...
  7. SonarQube6.2源码解析(二)
  8. 开发者必看:iOS应用审核的通关秘籍
  9. 如何实现一个Servlet中的多个功能
  10. Leetcode44. Wildcard Matching
  11. nankai 2082: 靶形数独 数独(9*9)求所有解 DLX+精确覆盖
  12. 合数阶群与素数阶群的双线性映射
  13. 十六进制转二进制原理
  14. 黑马程序员—因为感恩,所以我也来深圳黑马当班主任
  15. python获取标准的北京时间的源码
  16. paypal开发者账户申请步骤(最新最实用)
  17. 初学古琴怎么学?古琴入门基础知识
  18. 懒惰型性格分析,如何改变懒惰型性格?
  19. 峰会/论坛现场签约怎么签?君子签提供区块链电子签约技术支持
  20. Unity简单使用Job System

热门文章

  1. 苏宁易购:公司改选董事 同意聘任张近东为公司名誉董事长
  2. 微信8.0来了!黄脸表情会动了,还能扔炸弹...一大波新功能上线!
  3. 某自媒体发布“抢小孩”视频,最高近25万次点赞,结果竟是自导自演!
  4. 最大规模线上新基建项目拉开大幕!第127届广交会今天正式开展
  5. 特斯拉入驻天猫卖车了 将连做8天直播
  6. 订单扣款却又被系统删除?携程回应:酒店系统故障导致
  7. 华强北万事俱备,只待 iPhone 11到货
  8. 70英寸巨屏!小米首款Redmi红米电视来了 售价狠!
  9. 华为给力!算力最强AI处理器在中国!
  10. 中国联通回应裁员传闻:严重不实 勿传勿信