如何避免初始化大型数组



>我将大量双精度数组分配为

double[] x = new double[ n ];

其中n很大,我想避免初始化以节省时间。可能吗?

简短回答:否。数组在创建时始终归零。

如果您的分析表明这是一个主要瓶颈,则可以考虑保留一个阵列实例池,每个集合的长度都大于n。问题是您可能需要一个包装器对象来包含数据数组和使用的实际长度,因为您不能再使用 data.length .

您可以使用ArrayList或其他东西,并在需要向其添加元素时构建数组吗?如果这是您的问题,这将节省初始化时间。

ArrayList<double> x = new ArrayList<double>();

如果您不想初始化太长的数组,您可以确定数组大小的限制,这不会让您等待太多。我建议将长数组拆分为较小的数组。定义一个保存数组的列表。如果您的数组已填充,请将其添加到列表中。并继续填写一个新的。

import java.util.ArrayList;
import java.util.List;
public class Tester {
    private static final int LIMIT = 30;
    private static int index = 0;
    private static int[][] lookup;
    private static List<int[][]> list = new ArrayList<int[][]>();
    public static void main(String[] args) {
        lookup = new int[LIMIT][1];
        for (int i = 0; i <= 93; i++) {
            addToArr(i);
        }
        list.add(lookup);
        for (int[][] intArr : list) {
            for (int i = 0; i < intArr.length; i++) {
                System.out.print(intArr[i][0] + ",");
            }
        }
    }
    public static void addToArr(int value) {
        if (index == LIMIT) {
            list.add(lookup);
            lookup = new int[LIMIT][1];
            index = 0;
        }
        lookup [index++][0] = value;
    }
}

指纹:

0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,

84,85,86,87,88,89,90,91,92,93,

0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,00,00,00,00,00,000000000000000000000000000000000000000000000000000000000000000

** 警告 ** 不安全的替代方案 **

这不是一个确切的解决方案,但它可能是一个可行的替代方案。这种方法有一些风险。但如果真的绝对必要,你可以走这条路。此方法使用未记录的 sun.misc.Unsafe 类来分配堆外内存来存储双精度值。堆外意味着它不是垃圾回收的,因此您需要注意释放关联的内存。

以下代码基于这篇关于 sun.misc.Unsafe 的博客文章。

import java.lang.reflect.Field;
import sun.misc.Unsafe;
@SuppressWarnings("restriction")
public class SuperDoubleArray {
    private final static Unsafe unsafe = getUnsafe();
    private final static int INDEX_SCALE = 8;
    private long size;
    private long address;
    public SuperDoubleArray(long size) {
        this.size = size;
        address = unsafe.allocateMemory(size * INDEX_SCALE);
    }
    private static Unsafe getUnsafe() {
        try {
            Field singleoneInstanceField = Unsafe.class.getDeclaredField("theUnsafe");
            singleoneInstanceField.setAccessible(true);
            return (Unsafe) singleoneInstanceField.get(null);
        } catch (IllegalArgumentException | SecurityException 
                | NoSuchFieldException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }
    public void set(long i, double value) {
        unsafe.putDouble(address + i * INDEX_SCALE, value);
    }
    public double get(long idx) {
        return unsafe.getDouble(address + idx * INDEX_SCALE);
    }
    public long size() {
        return size;
    }
    public void deallocate() {
        unsafe.freeMemory(address);
    }
}

以下代码将打印一些来自单位化内存的随机双精度值。

SuperDoubleArray sda = new SuperDoubleArray(100);
for (int i=0; i<sda.size(); i++) {
    System.out.println(sda.get(i));
}
sda.deallocate();

没有安全/范围检查,什么都没有,你可以很容易地用它使JVM崩溃,可能无法与非SUN JRE一起使用,甚至可能在未来的SUN JRE版本中停止工作,但在某些情况下,它可能是唯一的解决方案。与 Java 数组不同,它还可以分配> Integer.MAX_VALUE大小的伪数组。

java.nio.ByteBuffer.allocateDirect(...)实际上在后台使用相同的 Unsafe 类来分配字节缓冲区,您可以使用 ByteBuffer.allocateDirect(8*size).asDoubleBuffer() 使其适应DoubleBuffer,但ByteBuffer.allocateDirect(...)仍然用零初始化缓冲区,因此它可能会产生性能开销。

就像其他人已经提到的,简单的答案是:不,您无法避免初始化部分。 除非使用某些native分配或使用作为字节缓冲区视图创建的 IntBuffer 将是直接的,当且仅当字节缓冲区本身是直接的。

如果您没有使用其中任何一个,那么为了尽快分配和初始化数组,您需要最大限度地减少GC调用,并且必须确保 JVM 具有存储和使用该阵列所需的内存。

在Albert Hendriks的案例中:static int[][] lookup = new int[113088217][2],如果没有至少2.3G(12+113088217*(12+2*4)字节)的内存,JVM将无法分配所需的空间。请注意,我没有添加所需的填充空间(内存对齐)。

回答为什么lookup = new int[2*113088217];执行得更快。这是因为处理的内存要少得多,因为我们没有子数组(标头 + 元素 + 每个子数组的对齐方式),只需要 (2*113088217*4+12) 字节=~804M。

一旦你声明了"new double[n]"语句,数组就会初始化。这是没有办法的。

如果您这样做是为了优化,那么我会雇用您来阅读过早优化。如果您的程序没有碰壁,那么就不值得优化。而且它肯定也不是您应该优化的阵列。

您可以使用 ArrayList 来节省初始化时间,如果您绝对需要像这样处理双数组,则可以将其转换为数组:

List<Double> withoutInitializing = new ArrayList<Double>();
Double[] nowYouConvert = (Double[]) withoutInitializing.toArray();

来自 Java 文档:

toArray:返回一个数组,该数组按正确的顺序(从第一个到最后一个元素)包含此列表中的所有元素。

返回的数组将是"安全的",因为没有对它的引用 由此列表维护。(换句话说,此方法必须分配 新数组,即使此列表由数组支持)。因此,调用方是 自由修改返回的数组。

此方法充当基于数组和基于集合之间的桥梁 蜜蜂属。

指定者:集合中的 toArray()

关于如何不初始化数组的一些解决方法。

创建一个保证大于最大可能条目数的数组,并部分填充它。

例如,您可以决定用户永远不会提供超过 100 个输入值。然后分配一个大小为 100 的数组:

final int VALUES_LENGTH = 100;
double[] values = new double[VALUES_LENGTH];

然后保留一个伴随变量,告诉数组中实际使用了多少个元素。

int valuesSize = 0;

现在 values.length 是数组值的容量,valuesSize是数组的当前大小。继续将元素添加到数组中,每次递增valuesSize变量。

values[valuesSize] = x;
valuesSize++;

这样,valuesSize始终包含正确的元素计数。以下代码段演示如何将数字读入部分填充数组。

int valuesSize = 0;
Scanner in = new Scanner(System.in);
while (in.hasNextDouble()) {
  if (valuesSize < values.length) {
    values[valuesSize] = in.nextDouble();
    valuesSize++;
  }
}

在此循环结束时,valuesSize包含数组中元素的实际数量。

例如,以下是将任意长的序列号读入的方法一个数组,不会耗尽空间:

int valuesSize = 0;
while (in.hasNextDouble()) {
   if (valuesSize == values.length) {
      values = Arrays.copyOf(values, 2 * values.length);
   }
   values[valuesSize] = in.nextDouble();
   valuesSize++;
}

据我了解这个问题,真正的问题是分配一个巨大的二维数组,如评论中所述

"静态 int[][] 查找 = 新 int[113088217][2];不起作用,而私有最终静态 int[][] 查找 = 新 int[11308821][2];(少 10 倍)不到一秒"

假设这是正确的,是的,对于巨大的数字来说,它是骨头慢的。 您不是在分配 113088217 * 2 整数的单个块! 您正在分配113088217单独的数组,这些数组是对象,每个数组都必须在堆上分配:这意味着超过 1 亿次 JVM 正在寻找空间,将其标记为已使用,可能在内存紧张时运行 GC,等等...... 每个数组都占用大量(在这些巨大的数字中)额外的内存,加上每个数组包含 2 个整数。

对于这个巨大的案例:

1)切换索引,然后去

static int[][] lookup = new int[2][113088217]

这不是分配 1.13 亿个阵列,而是分配两个阵列。 在数组中执行查找时,切换两个索引。

2)制作一个1D数组并自己进行查找

static int[] lookup = new int[2*113088217];

这需要做简单的数学运算来找到正确的索引。 与其直接访问数组,不如编写一个函数来执行数学运算,调用代码应该使用它。

最新更新