有没有办法在没有初始化的情况下创建原始数组?
我们知道Java总是在创建时初始化数组。 即new int[1000000]
总是返回一个所有元素都为0的数组。我理解它对于Object数组是必须的,但对于原始数组(除了可能是布尔值),在大多数情况下我们并不关心初始值。
有人知道避免这种初始化的方法吗?
我做了一些调查。 没有合法的方法在Java中创建未初始化的数组。 甚至JNI NewXxxArray也会创建初始化数组。 因此无法确切知道arrays归零的成本。 不过我做了一些测量:
1)创建具有不同数组大小的1000字节数组
long t0 = System.currentTimeMillis(); for(int i = 0; i < 1000; i++) { // byte[] a1 = new byte[1]; byte[] a1 = new byte[1000000]; } System.out.println(System.currentTimeMillis() - t0);
在我的PC上,它为字节[1]提供<1ms,为字节[1000000]提供约500ms。 听起来令我印象深刻。
2)我们在JDK中没有用于填充数组的快速(本机)方法,Arrays.fill太慢了,所以让我们看看至少1000个大小数组的1000次复制需要使用本机System.arraycopy
byte[] a1 = new byte[1000000]; byte[] a2 = new byte[1000000]; for(int i = 0; i < 1000; i++) { System.arraycopy(a1, 0, a2, 0, 1000000); }
这是700毫秒。
它让我有理由相信a)创建长数组是昂贵的b)由于无用的初始化它似乎很昂贵。
3)让我们带sun.misc.Unsafe http://www.javasourcecode.org/html/open-source/jdk/jdk-6u23/sun/misc/Unsafe.html 。 它不受外部使用的影响,但不会太多
Field f = Unsafe.class.getDeclaredField("theUnsafe"); f.setAccessible(true); Unsafe unsafe = (Unsafe)f.get(null);
这是内存分配测试的成本
for(int i = 0; i < 1000; i++) { long m = u.allocateMemory(1000000); }
如果你还记得,它需要<1 ms,新字节[1000000]需要500ms。
4)Unsafe没有直接的方法来处理数组。 它需要知道类字段,但reflection显示数组中没有字段。 有关arrays内部的信息不多,我想这是特定于JVM /平台的。 然而,它与任何其他Java对象一样,是头+字段。 在我的PC / JVM上看起来像
header - 8 bytes int length - 4 bytes long bufferAddress - 8 bytes
现在,使用Unsafe,我将创建byte [10],分配一个10字节的内存缓冲区并将其用作我的数组的元素:
byte[] a = new byte[10]; System.out.println(Arrays.toString(a)); long mem = unsafe.allocateMemory(10); unsafe.putLong(a, 12, mem); System.out.println(Arrays.toString(a));
它打印
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0] [8, 15, -114, 24, 0, 0, 0, 0, 0, 0]
您可以看到数组的数据未初始化。
现在我将改变我们的数组长度(尽管它仍指向10字节内存)
unsafe.putInt(a, 8, 1000000); System.out.println(a.length);
它显示了1000000.这只是为了certificate这个想法是有效的。
现在进行性能测试 我将创建一个空字节数组a1,分配一个1000000字节的缓冲区,将此缓冲区分配给a1一组a1.length = 10000000
long t0 = System.currentTimeMillis(); for(int i = 0; i < 1000; i++) { byte[] a1 = new byte[0]; long mem1 = unsafe.allocateMemory(1000000); unsafe.putLong(a1, 12, mem); unsafe.putInt(a1, 8, 1000000); } System.out.println(System.currentTimeMillis() - t0);
需要10毫秒。
5)在C ++中有malloc和alloc,malloc只分配内存块,calloc也用零初始化它。
CPP
... JNIEXPORT void JNICALL Java_Test_malloc(JNIEnv *env, jobject obj, jint n) { malloc(n); }
java的
private native static void malloc(int n); for (int i = 0; i < 500; i++) { malloc(1000000); }
结果malloc - 78毫秒; calloc - 468毫秒
结论
- 由于无用的元素归零,似乎Java数组创建很慢。
-
我们无法改变它,但Oracle可以。 无需在JLS中更改任何内容,只需将本机方法添加到java.lang.reflect.Array中即可
public static native xxx [] newUninitialziedXxxArray(int size);
适用于所有原始数字类型(byte - double)和char类型。 它可以在整个JDK中使用,就像在java.util.Arrays中一样
public static int[] copyOf(int[] original, int newLength) { int[] copy = Array.newUninitializedIntArray(newLength); System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); ...
或java.lang.String
public String concat(String str) { ... char[] buf = Array.newUninitializedCharArray(count + otherLen); getChars(0, count, buf, 0); ...
我打算将其转移到答案,因为它应该是。
java中的“数组”不是你想象的那样。 它不仅仅是指向堆栈或堆上的一大块连续内存的指针。
Java中的Array就像其他所有(除了原语)一样,并且在堆上。 当你调用new int[100000]
你就像每个其他对象一样创建一个新对象,并且它被初始化,等等。
JLS提供了有关此内容的所有具体信息:
http://docs.oracle.com/javase/specs/jls/se5.0/html/arrays.html
所以不行。 你不能避免“初始化”一个数组。 这不是Java的工作方式。 根本就没有未初始化的堆内存; 许多人称之为“function”,因为它阻止您访问未初始化的内存。