这个问题呢是我在想看 "ArrayList插入删除涉及数组移动时,是怎么做的" 时发现的。
Arraylist 虽然底部是数组实现的,但是你可以将它看成数组实现的线性表,他的元素都是相连的,你在中间插入删除的话,是需要移动数组内的元素的。
可以看下面的add方法,
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
可以看到是使用的System.arraycopy 方法来移动数组的。
那么就有一个疑问了,为什么不是使用for循环来移动,而是采用这个System.arraycopy 方法呢?
然后点开源码发现这是一个native方法
我们都知道native 方法不是用Java语言写的,一般都是出于性能考虑,或者用c++,来操作Java不能直接操作的系统资源。那么这个方法具体是怎么回事呢?
最后我在知乎上找到了同样的问题
Java 的 System.arraycopy() 方法拷贝小数组时高效吗?
StackOverflow 镜像问题: Is Java's System.arraycopy() efficient for small arrays?
考虑到 System.arraycopy() 是个 native 方法, 而 JVM 调用 native 方法有一定开销的; 在拷贝小数组的时候, 使用 System.arraycopy() 和使用 for 循环的方式哪种更高效?
下面是一位大佬的回答 感谢!
作者:RednaxelaFX 链接:https://www.zhihu.com/question/53749473/answer/136701254
在主流高性能的JVM上(HotSpot VM系、IBM J9 VM系、JRockit系等等),可以认为System.arraycopy()在拷贝数组时是可靠高效的——如果发现不够高效的情况,请报告performance bug,肯定很快就会得到改进。
java.lang.System.arraycopy()方法在Java代码里声明为一个native方法。所以最naïve的实现方式就是通过JNI调用JVM里的native代码来实现。题主大概也是基于这种假设来提问的。
但实际上在高性能的JVM里,System.arraycopy() (以及相关的 java.util.Arrays.copyOf() )都会被实现为intrinsic method,JVM内会对它做特殊的优化处理,在优化后并不会通过JNI那种慢速方式来实现。
以HotSpot VM为例,它的Server Compiler(C2)会在编译System.arraycopy()的调用点的时候,编译时判断传入参数的类型(以及可能有常量值的情况)来判断要选用哪种具体实现方式,大部分情况下都会调用高度优化的手写汇编实现的stub,尽可能使用当前CPU所提供的SIMD指令来提高速度:
//-----------------------------generate_arraycopy---------------------- // Generate an optimized call to arraycopy. // Caller must guard against non-arrays. // Caller must determine a common array basic-type for both arrays. // Caller must validate offsets against array bounds. // The slow_region has already collected guard failure paths // (such as out of bounds length or non-conformable array types). // The generated code has this shape, in general: // // if (length == 0) return // via zero_path // slowval = -1 // if (types unknown) { // slowval = call generic copy loop // if (slowval == 0) return // via checked_path // } else if (indexes in bounds) { // if ((is object array) && !(array type check)) { // slowval = call checked copy loop // if (slowval == 0) return // via checked_path // } else { // call bulk copy loop // return // via fast_path // } // } // // adjust params for remaining work: // if (slowval != -1) { // n = -1^slowval; src_offset += n; dest_offset += n; length -= n // } // slow_region: // call slow arraycopy(src, src_offset, dest, dest_offset, length) // return // via slow_call_path // // This routine is used from several intrinsics: System.arraycopy, // Object.clone (the array subcase), and Arrays.copyOf[Range].
JRockit也会做类似的优化,可以把System.arraycopy()优化为对优化的stub的调用或者对内联的拷贝循环的调用。而且更有趣的是,JRockit的逃逸分析(escape analysis)更先进,可以把小数组的System.arraycopy()拷贝“爆炸”开。类似这样的代码:public static int doSum(int x, int y, int z) { int[] src = { x, y, z }; int[] dst = new int[3]; System.arraycopy(src, 0, dst, 0, 3); return dst[0] + dst[1] + dst[2]; }
可以通过逃逸分析+标量替换被优化到:
public static int doSum(int x, int y, int z) { // int[] src = { x, y, z }; int src_0 = x, src_1 = y, src_2 = z; // int[] dst = new int[3]; int dst_0 = 0, dst_1 = 0, dst_2 = 0; // System.arraycopy(src, 0, dst, 0, 3); dst_0 = src_0; dst_1 = src_1; dst_2 = src_2; return dst_0 + dst_1 + dst_2; }
然后应用一般的对局部变量的优化可以得到:
public static int doSum(int x, int y, int z) { return x + y + z; }
而JRockit对第三方库自己手写的数组拷贝循环则未必能可靠地做到同等级别的优化。
所以总体来说,用 System.arraycopy() 更有可能得到细致的优化,应该尽可能去使用它。