Есть ли способ создать примитивный массив без инициализации?

Как мы знаем, Java всегда инициализирует массивы при создании. То есть new int[1000000] всегда возвращает массив со всеми элементами = 0. Я понимаю, что это необходимо для массивов объектов, но для примитивных массивов (за исключением, может быть, Boolean) в большинстве случаев нас не интересуют начальные значения.

Кто-нибудь знает, как избежать этой иналинизации?

Ответ 1

Я провел некоторое расследование. Не существует законного способа создания неинициализированного массива в 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);

на моем ПК он дает < 1 мс для байта [1] и ~ 500 мс для байта [1000000]. Звучит впечатляюще.

2) У нас нет быстрого (родного) метода в JDK для заполнения массивов, Arrays.fill слишком медленный, поэтому давайте посмотрим, по крайней мере, сколько 1000 копий массива размером 1000 000 с помощью родной 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 мс, если вы помните, для нового байта [1000000] потребовалось 500 мс.

4) У Unsafe нет прямых методов работы с массивами. Он должен знать поля классов, но отражение не отображает полей в массиве. Внутри массивов мало информации, я полагаю, что это JVM/платформа. Тем не менее, это, как и любой другой заголовок + поля Java Object. На моем ПК /JVM это выглядит как

header - 8 bytes
int length - 4 bytes
long bufferAddress - 8 bytes

Теперь, используя Unsafe, я создам байт [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]

Вы можете видеть, что данные массива thay не инициализируются.

Теперь я изменю длину массива (хотя он все еще указывает на 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) В С++ есть malloc и alloc, malloc просто выделяет блок памяти, calloc также инициализирует его нулями.

каст

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

для всех примитивных числовых типов (байт - двойной) и 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);
        ...

Ответ 2

Я собираюсь переместить это в ответ, потому что это, вероятно, должно быть.

"Массив" в java - это не то, что вы думаете. Это не просто указатель на кусок смежной памяти на стек или кучу.

Массив в Java - это объект, как и все остальное (кроме примитивов) и находится в куче. Когда вы вызываете new int[100000], вы создаете новый объект так же, как и любой другой объект, и он инициализируется и т.д.

JLS предоставляет всю конкретную информацию об этом:

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

Итак, нет. Вы не можете избежать "инициализации" массива. Это просто не то, как работает Java. Просто нет такой вещи, как неинициализированная память кучи; многие люди называют это "функцией", так как это мешает вам получить доступ к неинициализированной памяти.

Ответ 3

Java 9 фактически начинает раскрывать это через метод jdk.internal.misc.Unsafe.allocateUninitializedArray. Это фактически потребовало бы объявления модуля JDK.Unsupported.

Ответ 4

Я могу себе представить, что O (n) стоимость нового int [n] может быть бременем в некоторых структурах данных или алгоритмах.

Способ амортизировать O (1) стоимость выделения памяти в Java для примитивного массива размера n состоит в том, чтобы перераспределять выделенные массивы с помощью пула объектов или какой-либо другой стратегии. Переработанный массив можно считать "неинициализированным" для следующего выделения.