有没有办法在没有初始化的情况下创建原始数组?

时间:2012-12-08 18:14:37

标签: java arrays

我们知道Java总是在创建时初始化数组。即new int[1000000]总是返回一个所有元素都为0的数组。我理解它对于Object数组是必须的,但对于原始数组(除了可能是布尔值),在大多数情况下我们并不关心初始值。

有人知道避免这种初始化的方法吗?

4 个答案:

答案 0 :(得分:18)

我做了一些调查。没有合法的方法在Java中创建未初始化的数组。甚至JNI NewXxxArray也会创建初始化数组。所以不可能确切地知道阵列归零的成本。不过我做了一些测量:

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);

在我的电脑上它给出&lt;字节[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);
    }

需要&lt; 1 ms,如果你还记得,新字节[1000000]需要500ms。

4)Unsafe没有直接的方法来处理数组。它需要知道类字段,但反射显示数组中没有字段。有关阵列内部的信息不多,我想这是特定于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.这只是为了证明这个想法有效。

现在进行性能测试。我将创建一个空字节数组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 ms; calloc - 468 ms

<强>结论

  1. 由于无用的元素归零,似乎Java数组创建速度很慢。
  2. 我们无法改变它,但Oracle可以。无需在JLS中更改任何内容,只需将本机方法添加到java.lang.reflect.Array,如

    public static native xxx [] newUninitialziedXxxArray(int size);

  3. 表示所有原始数字类型(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);
            ...
    

答案 1 :(得分:3)

我打算将其转移到答案中,因为它应该是。

java中的“数组”不是你想象的那样。它不仅仅是指向堆栈或堆上的一大块连续内存的指针。

Java中的数组就像其他所有(原语除外)一样,并且在堆上。当你调用new int[100000]时,你就像创建一个新对象一样,并且它会被初始化,等等。

JLS提供了有关此内容的所有具体信息:

http://docs.oracle.com/javase/specs/jls/se5.0/html/arrays.html

所以,不。你无法避免“初始化”一个数组。这不是Java的工作方式。根本就没有未初始化的堆内存;许多人称之为“功能”,因为它阻止您访问未初始化的内存。

答案 2 :(得分:0)

Java 9实际上开始通过jdk.internal.misc.Unsafe.allocateUninitializedArray方法公开此内容。实际上它将需要JDK。不受支持的模块declaration

答案 3 :(得分:0)

我可以想象,新int [n]的O(n)成本可能会成为某些数据结构或算法的负担。

在Java中为大小为n的原始数组分摊O(1)的内存分配成本的一种方法是使用对象池或其他策略对分配的数组进行回收。对于下一次分配,可以将回收的数组视为“未初始化的”。