以前面试的时候总会被人问起一些Java里面的很多的东西,比如说ArrayList和Vector内部是如何实现当时我心里就一万个的草泥马,平时我们都不是只管用吗,只要会去使用就行了,管它其他的什么乱七八糟的。后来被人问多了就会慢慢的带着好奇心去看了。下面我就以android-19的api来进行分析,因为Android大部分的源代码都已经开源了而且都比较完善的,可以直接还可以看到jni层的代码实现,但是我们就看不到java的jni层的实现的。
    在了解这个之前我们首先需要看看System.arraycopy()函数的使用以及底层的实现。因为ArrayList里面有一个最关键地方就是调用了该方法去实现的。

1. 数组拷贝的使用
    一般我们在进行数组拷贝和移动的时候都会想到以下几种的方法

  • 使用for循环依次的遍历移动
  • 使用System.arraycopy()方法
  • 使用Arrays.copyOf()方法
  • 使用Object.clone()方法
    第一种方法比较简单就是我们传统的使用for循环依次的遍历去移动赋值操作,下面我们通过使用简单的代码来实现2,3方法。
private static void SystemArrayCopy() {int array1[] = { 10, 9, 6, 12, -9, 1 };int array2[] = { 3, 7, -2, -6, 1, 2, 10 };/*** 注意: 这里的srcPos, destPos 都必须大于或等于0 * srcPos+length<= srcArray.length* destPos + length <= destArray.length*/System.arraycopy(array1, 2, array2, 4, 3);for (int num : array1) {System.out.print(num + ",");}System.out.println();
}private static void arrayCopy() {int array1[] = { 10, 9, 6, 12, -9, 1 };int array2[] = { 3, 7, -2, -6, 1, 2, 10 };int array3[] = Arrays.copyOf(array1, 10);for (int num : array3) {System.out.print(num + ",");}System.out.println();
}System arraycopy 结果: 10,9,6,12,-9,1,
Arrays copyOf 结果: 10,9,6,12,-9,1,0,0,0,0,

从上面的代码中我们可以看的出来Arrays.copyOf()方法对于结束位置是没有要求的,但是它对于开始位置是有要求的,其实Arrays.copyOf()还是使用了System.arrayCopy()方法去实现的


/*** 对指定的int数组,结束位置为newLength进行数组拷贝,startPos为0*/
public static int[] copyOf(int[] original, int newLength) {if (newLength < 0) {throw new NegativeArraySizeException(Integer.toString(newLength));}return copyOfRange(original, 0, newLength);
}/*** @param original 拷贝的源数组* @param start 从数组中拷贝的开始位置* @param end 结束位置* @return 将最后数组拷贝之后的结果返回出去*/
public static <T> T[] copyOfRange(T[] original, int start, int end) {int originalLength = original.length;//开始位置不能大于结束位置if (start > end) {throw new IllegalArgumentException();}//开始位置不能是负数并且开始位置不能大于数组的长度if (start < 0 || start > originalLength) {throw new ArrayIndexOutOfBoundsException();}int resultLength = end - start;int copyLength = Math.min(resultLength, originalLength - start);//这个地方是重新创建一个长度为resultLength的数组。T[] result = (T[]) Array.newInstance(original.getClass().getComponentType(), resultLength);//最后还是调用了native arraycopy方法将original数组的指定内容拷贝到result数组中System.arraycopy(original, start, result, 0, copyLength);return result;
}

其实不管数组的如何拷贝最后还是调用了System的arrayCopy的本地方法去进行操作,因为Java最后还是通过jni调用底层的C语言来实现的,由于Android是开源的所以我就直接找到了 /dalvik/vm/native/java_lang_System.cpp来看看其内部实现,如果你也想研究Java的该类底层是如何实现的话,你可以直接下载OpenJDK,因为Oracle的JDK好像是看不到底层的实现的。

首先会传进来一个整数数组的指针,该整数数组存放着四个整数,第0个整数存放的是源数组的一个封装类地址,第1个整数存放的是数组开始拷贝的起始位置,第2个整数表示的目标数组的一个封装类地址,第3个表示移动到目标数组的起始位置,第4个表示的是需要移动的个数。

static void Dalvik_java_lang_System_arraycopy(const u4* args, JValue* pResult)
{//该类指针变量封装了源头数组ArrayObject* srcArray = (ArrayObject*) args[0];int srcPos = args[1];ArrayObject* dstArray = (ArrayObject*) args[2];int dstPos = args[3];int length = args[4];//检查源数组指针变量是否为空if (srcArray == NULL) {dvmThrowNullPointerException("src == null");RETURN_VOID();}//检查拷贝到目的地数组指针是否为空if (dstArray == NULL) {dvmThrowNullPointerException("dst == null");RETURN_VOID();}/* Make sure source and destination are arrays. *///接着判断是否是数组类型if (!dvmIsArray(srcArray)) {dvmThrowArrayStoreExceptionNotArray(((Object*)srcArray)->clazz, "source");RETURN_VOID();}if (!dvmIsArray(dstArray)) {dvmThrowArrayStoreExceptionNotArray(((Object*)dstArray)->clazz, "destination");RETURN_VOID();}/* avoid int overflow *///接着继续判断位置和长度是否数组越界。个人觉得这部分可以放在java层进行处理的。if (srcPos < 0 || dstPos < 0 || length < 0 ||srcPos > (int) srcArray->length - length ||dstPos > (int) dstArray->length - length){dvmThrowExceptionFmt(gDvm.exArrayIndexOutOfBoundsException,"src.length=%d srcPos=%d dst.length=%d dstPos=%d length=%d",srcArray->length, srcPos, dstArray->length, dstPos, length);RETURN_VOID();}.......
}

上面只是一些条件判断:判断传进来的数据是否是数组以及位置和长度是否会导致数组越界,接下来则会判断数组的数据类型是否一样,并且会对基本数据类型和引用数据类型分别进行操作。

static void Dalvik_java_lang_System_arraycopy(const u4* args, JValue* pResult)
{........ClassObject* srcClass = srcArray->clazz;ClassObject* dstClass = dstArray->clazz;char srcType = srcClass->descriptor[1];char dstType = dstClass->descriptor[1];//如果其中一个数组是基本数据类型数组,那么另外一个数组也必须是数组类型数组。bool srcPrim = (srcType != '[' && srcType != 'L');bool dstPrim = (dstType != '[' && dstType != 'L');if (srcPrim || dstPrim) {//这里需要判断两个数组的数据类型是否一样,并且是否都是基本数据类型if (srcPrim != dstPrim || srcType != dstType) {dvmThrowArrayStoreExceptionIncompatibleArrays(srcClass, dstClass);RETURN_VOID();}//如果两个数组只要有一个是基本数据类型,并且两者的数据类型都要一样才进入数组拷贝中switch (srcType) {case 'B':case 'Z':/* byte数组和boolean数组的移动,该类型的数组的每个元素都是一个字节大小 */memmove((u1*) dstArray->contents + dstPos,(const u1*) srcArray->contents + srcPos,length);break;case 'C':case 'S':/* 2 char数组和 short数组的移动,该数组的每个元素都是两个字节大小,也就是16个比特 */move16((u1*) dstArray->contents + dstPos * 2,(const u1*) srcArray->contents + srcPos * 2,length * 2);break;case 'F':case 'I':/* int数组和float数组的移动,该数组的每个元素都是四个字节大小 */move32((u1*) dstArray->contents + dstPos * 4,(const u1*) srcArray->contents + srcPos * 4,length * 4);break;case 'D':case 'J':/* double数组和long数组的移动,该数组的每个元素都是8个字节大小,直接调用move32方法进行*//** 8 bytes per element.  We don't need to guarantee atomicity* of the entire 64-bit word, so we can use the 32-bit copier.*/move32((u1*) dstArray->contents + dstPos * 8,(const u1*) srcArray->contents + srcPos * 8,length * 8);break;default://非法的数组类型ALOGE("Weird array type '%s'", srcClass->descriptor);dvmAbort();}}......RETURN_VOID();
}

上面就是基本数据类型数组的移动和拷贝,从上面我们可以看得出来如果要将数组从源数组拷贝到目标数组的话,两个数组的数据类型必须是一样的。对于每个元素大小都是一个字节大小的数组,调用的则是系统的memmove()进行数组移动的,其他的move32()和move16()其实最后还是调用的 static void memmove_words(void* dest, const void* src, size_t n)方法的,这里使用了一个宏定义。

#define move16 memmove_words
#define move32 memmove_words

通过这宏定义我们可以知道不管是move16还是move32的话,其实我们最后的是一个方法那就是 memmove_words 方法,只是命名上更加的容易的理解里面的意思的。

除了上面的基本数据类型的数组的拷贝外,其实我们还有的数组是对象的,下面接着看看对象数组的拷贝,还是以前的老方法。

static void Dalvik_java_lang_System_arraycopy(const u4* args, JValue* pResult) {.......//首先计算当前一个对象占用多少空间大小const int width = sizeof(Object*);//1、首先我们需要判断两个数组的维数是否一样,比如说都是一位数组,或者是两个都是二位数组//2、然后判断两个数组的类型是否一样的,如果类型不一样的话,不能进行数组拷贝if (srcClass->arrayDim == dstClass->arrayDim && dvmInstanceof(srcClass, dstClass)) {//1、需要目的地数组位置,通过起始位置+需要的空间大小//2、接着获取原数组的结束,也是通过起始位置+需要空间的大小//3、获取需要拷贝数据的大小,通过length * width(表示单个对象的大小*个数)move32((u1*)dstArray->contents + dstPos * width,(const u1*)srcArray->contents + srcPos * width,length * width);//4、这里就调用Android虚拟机内部代码来进行实现了dvmWriteBarrierArray(dstArray, dstPos, dstPos+length);} else {//下面就是不同对象数组或者是不同维度数组的拷贝了,由于本人能力有限就不进行分析了Object** srcObj;int copyCount;ClassObject*   clazz = NULL;srcObj = ((Object**)(void*)srcArray->contents) + srcPos;if (length > 0 && srcObj[0] != NULL) {clazz = srcObj[0]->clazz;if (!dvmCanPutArrayElement(clazz, dstClass))clazz = NULL;}for (copyCount = 0; copyCount < length; copyCount++) {if (srcObj[copyCount] != NULL && srcObj[copyCount]->clazz != clazz &&!dvmCanPutArrayElement(srcObj[copyCount]->clazz, dstClass)) {break;}}move32((u1*)dstArray->contents + dstPos * width,(const u1*)srcArray->contents + srcPos * width, copyCount * width);dvmWriteBarrierArray(dstArray, 0, copyCount);if (copyCount != length) {dvmThrowArrayStoreExceptionIncompatibleArrayElement(srcPos + copyCount,srcObj[copyCount]->clazz, dstClass);RETURN_VOID();}}
}

通过上面的代码我们可以一层一层的知道上面的Api执行的原理了,也大概慢慢的明白了高级语言大概是如何执行的,对于一些喜欢研究高级语言是如何实现一个函数提供了帮助。下面总结一下Java中的System.arraycopy原理和步骤:

  1. 首先上层调用Java类System中的arraycopy方法,接着调用Dalvilk中的 java_lang_System.cpp类
  2. 然后再Jni中首先会判断传入的两个对象是否是数组类型,接着判断传入的长度和位置是否会导致数组越界问题
  3. 接着判断是否是基本数据类型(int,long,boolean,char,double,float)数组,并且数组维度需要相等(所谓的是数组维度是指一维数组还是二维数组)。如果是char或者是boolean类型的话直接调用系统函数 memmove 进行数据移动,否则调用其内部方法 memmove_words 进行操作的。
  4. 如果不是基本数据类型数组,而是对象数组的话。接着判断数组类型是否是相同类,以及维度是否相同,调用dvmWriteBarrierArray进行数据写入
  5. 如果不是对象数组,或者是维度也不同的话,那么进行另外的数据拷贝问题

下面我将通过一个流程图更加形象的说明

总结

通过在Android4.4 上面的代码我们可以很清晰的理解上层Java代码在进行数组拷贝时的原理和内部实现了,换位思考一下如果我们是Google的开发者,你们会怎么去实现数组的拷贝呢?其实我们在大学学习C语言的时时候候老师也会经常让我们自己去实现一个数组的拷贝的,那个时候的我们并没有这种意思,对我们写的代码进行一个封装成SDK的思想,然后以后如果我们需要使用的时候就直接引用过来了。其实细心的我们发现那些基本数据类型的数组是否也可以放到Java层去实现的呢?其实在 Android6.0中 System类中确实提供了对基本数据类型的数组的拷贝。同时由于 C语言的执行效率比Java的效率要更加的高效,当我们遇到数组拷贝和移动的个数比较多时候,内部一样还提供了 native方法来对外进行调用。

Android 6.0 System类中的数组拷贝方法:

public final class System {private static final int ARRAYCOPY_SHORT_CHAR_ARRAY_THRESHOLD = 32;由于有7个方法涉及到拷贝,所以这里我也不一一进行列举了,感兴趣的时候可以去看看......public static void arraycopy(int[] src, int srcPos, int[] dst, int dstPos, int length) {if (src == null) {throw new NullPointerException("src == null");}if (dst == null) {throw new NullPointerException("dst == null");}if (srcPos < 0 || dstPos < 0 || length < 0 ||srcPos > src.length - length || dstPos > dst.length - length) {throw new ArrayIndexOutOfBoundsException("src.length=" + src.length + " srcPos=" + srcPos +" dst.length=" + dst.length + " dstPos=" + dstPos + " length=" + length);}if (length <= ARRAYCOPY_SHORT_INT_ARRAY_THRESHOLD) {// Copy int by int for shorter arrays.if (src == dst && srcPos < dstPos && dstPos < srcPos + length) {// Copy backward (to avoid overwriting elements before// they are copied in case of an overlap on the same// array.)for (int i = length - 1; i >= 0; --i) {dst[dstPos + i] = src[srcPos + i];}} else {// Copy forward.for (int i = 0; i < length; ++i) {dst[dstPos + i] = src[srcPos + i];}}} else {// 这里有一个非常重要的就是当涉及到拷贝的数组个数大于 32个的时候,则会调用native的数组拷贝了arraycopyIntUnchecked(src, srcPos, dst, dstPos, length);}}......
}

通过对Android6.0 中的System类的分析我们可以得出如果涉及到数组拷贝的长度小于等于32个的时候,则调用Java的代码来进行操作,否则就调用 Native 方法进行操作

后记

由于个人能力水平有限只能对这些进行一个浅显的分析了,其目的是为了让大家养成一种刨根问底的习惯而不光只是一个会调用Api的人,在使用别人 Api的同时我们也更应该想想对方为什么会这么实现的原因和想法,同时也思考自己能不能有更好的思路来实现,其实这些东西在我们大学的时候就已经有了,只是我们那个时候我们也根本没有这种意识和想法就是对自己写的代码进行一个更好的封装,然后变成文档最后给别人或者是自己使用,这样子也可以减少平时开发中的一些重复工作量。

深入理解System.arraycopy内部原理相关推荐

  1. System.arraycopy和Arrays.copyOf的原理解剖

    数组的复制其实在很多业务情况下会用到,不同的业务情况下选择适合的方式来实施复制是每个开发人员要考虑的事情. 那么System.arraycopy.Arrays.copyOf到底是如何复制的呢?接下来就 ...

  2. System.arraycopy()和 Arrays.copyOf()的区别联系(源码深度解析copyOf扩容原理)

    1.System.arraycopy()方法 public static void arraycopy(Object src, int srcPos, Object dest, int destPos ...

  3. 深入理解git内部原理

    文章转自:腾讯技术工程–Git内部原理揭秘 这才是真正的Git--Git内部原理揭秘! 前言 近几年技术发展十分迅猛,让部分同学养成了一种学习知识停留在表面,只会调用一些指令的习惯.我们时常有一种&q ...

  4. git gui fetch不到文件_Git内部原理剖析,有比这还详细的吗?

    1.1. 为什么写这篇文章 写这篇文章的本意有二: 工作安排原因,常有同事询问我一些关于 Git 的问题,总觉得自己解释的不够透彻,因此觉得有必要深入了解一下. 目前中文的 Git 教程往往本末倒置, ...

  5. JVM 内部原理(七)— Java 字节码基础之二

    JVM 内部原理(七)- Java 字节码基础之二 介绍 版本:Java SE 7 为什么需要了解 Java 字节码? 无论你是一名 Java 开发者.架构师.CxO 还是智能手机的普通用户,Java ...

  6. java泛型(二)、泛型的内部原理:类型擦除以及类型擦除带来的问题

    原 java泛型(二).泛型的内部原理:类型擦除以及类型擦除带来的问题 2012年08月29日 23:44:10 Kilnn 阅读数:56717 版权声明:本文为博主原创文章,未经博主允许不得转载. ...

  7. 【分布式ID】理解Snowflake算法的实现原理

    1.概述 转载:冷饭新炒:理解Snowflake算法的实现原理 我上次也看了一个视频讲解:[分布式ID]键高并发 分布式 全局唯一 ID 雪花算法 snowflake 2.前提# Snowflake( ...

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

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

  9. Git详解之九 Git内部原理

    以下内容转载自:http://www.open-open.com/lib/view/open1328070620202.html Git 内部原理 不管你是从前面的章节直接跳到了本章,还是读完了其余各 ...

最新文章

  1. 阿里疯传,手把手教你如何从0开始开展UI自动化测试?
  2. 织梦根目录下面404页面,主页能正常运行404页面,切换至栏目页404页面内的图片不能正常显示,解决...
  3. POJ3026 bfs+prim
  4. JavaOne 2012:掌握Java部署
  5. 通用crt更新不适用计算机,Office2016安装提示需要通用补丁CRT(KB2999226)
  6. HP惠普服务器做RAID
  7. 绝对不可错过的圣诞节H5游戏
  8. 标题中间有虚线或横线,并且居中,而且标题字数不固定
  9. Access数据库偏移注入
  10. fckeditor 上传图片 php_FCKeditor上传文件重命名for php
  11. 电脑android模拟器下载地址,原神电脑版怎么下载 安卓模拟器电脑版下载地址
  12. 快速查找论文的源代码网站资料
  13. onpageshow 用户浏览网页时触发
  14. java中正则表达式Pattern与Matcher类使用详解(find、group)
  15. 。iod_IOD '09,第二天:Maria Winans谈信息主导的即时通讯转型
  16. Unicode编码表到GB2312编码表映射表
  17. Google组织架构不学传统大公司
  18. 深入理解 DiscoveryClient
  19. 计算机8个应用软件,8个职场人必装的电脑软件,用过以后就离不开了,超级高效好用!...
  20. 最简单的判断自己体质方法

热门文章

  1. 终于在pycharm下(Python3.6.1版本)安装完成机器学习相关库文件(sklearn scikit-learn gensim xgboost tensorflow nltk )
  2. 【你你你你在开玩笑吧】什么叫凭借纯兴趣搞ACM?涨姿势了
  3. 数据透视表如何移动位置?
  4. 芯片驱动之充电芯片2
  5. 【stable diffusion 小白最全详细使用教程+大模型资源】
  6. GIS如何实现自动编号
  7. 期货买量和卖量是什么意思(期货交易量是什么意思)
  8. 新华三进击数智化“大基建”
  9. 关于VMware ESX4.1上架设 海蜘蛛+Panabit流控 的虚拟整合
  10. android百度地图开发