为什么Java没有真正的多维数组?问题背景为什么Java会这样做这有什么问题对此可以做些什么问题更新
对于那些不想要背景的人来说,TL;DR版本是以下具体问题:
问题
为什么Java没有真正的多维数组的实现?是否有坚实的技术原因?我在这里错过了什么?
背景
Java在语法级别具有多维数组,因为可以声明
int[][] arr = new int[10][10];
但似乎这真的不是人们所期望的。它不是让JVM分配一个足够大的连续RAM块来存储100 s,而是作为一个数组的数组出来:所以每一层都是一个连续的RAM块,但整个事情不是。因此,访问相当慢:JVM必须int
int
arr[i][j]
- 查找存储在
int[]
arr[i]
; - 为此编制索引以查找存储在 的 。
int
arr[i][j]
这涉及查询对象以从一个层转到下一个层,这相当昂贵。
为什么Java会这样做
在一个层面上,不难看出为什么不能将其优化为简单的缩放和添加查找,即使它全部分配在一个固定的块中。问题是,这是一个完全独立的引用,它可以被更改。因此,尽管数组的大小是固定的,但我们可以很容易地编写arr[3]
arr[3] = new int[11];
现在,缩放和添加被搞砸了,因为这一层已经增长。您需要在运行时知道所有内容是否仍与以前相同。此外,当然,这将被分配到RAM中的其他地方(它必须是,因为它比它所替换的更大),所以它甚至不在正确的位置进行缩放和添加。
这有什么问题
在我看来,这并不理想,原因有二。
首先,它很慢。对于多维情况,我使用这些方法对单维或多维数组的内容进行求和的测试花费的时间几乎是其两倍(714秒对371秒)(分别填充随机值的an和a,使用热缓存运行1000000次)。int[1000000]
int[100][100][100]
int
public static long sumSingle(int[] arr) {
long total = 0;
for (int i=0; i<arr.length; i++)
total+=arr[i];
return total;
}
public static long sumMulti(int[][][] arr) {
long total = 0;
for (int i=0; i<arr.length; i++)
for (int j=0; j<arr[0].length; j++)
for (int k=0; k<arr[0][0].length; k++)
total+=arr[i][j][k];
return total;
}
其次,因为它很慢,因此鼓励晦涩难懂的编码。如果您遇到一些性能关键型问题,而多维数组自然会完成,那么您有动力将其编写为平面数组,即使这使得该数组不自然且难以阅读。你剩下一个令人不快的选择:晦涩难懂的代码或缓慢的代码。
对此可以做些什么
在我看来,基本问题可以很容易地得到解决。正如我们之前所看到的,它无法优化的唯一原因是结构可能会发生变化。但是Java已经有了一种使引用不可更改的机制:将它们声明为。final
现在,只需声明它
final int[][] arr = new int[10][10];
还不够好,因为它只是在这里:仍然不是,并且可以改变,所以结构可能仍然会改变。但是,如果我们有一种方法来声明事物,使其贯穿始终,除了在存储值的底层,那么我们将拥有一个完整的不可变结构,并且可以将其全部分配为一个块,并使用scale-and-add进行索引。arr
final
arr[3]
final
int
它在语法上看起来如何,我不确定(我不是语言设计师)。或
final int[final][] arr = new int[10][10];
虽然不可否认,这看起来有点奇怪。这意味着:在顶层; 在下一层;不在底层(否则值本身将是不可变的)。final
final
final
int
最终性将使JIT编译器能够对此进行优化,以提供单维数组的性能,从而消除以这种方式编码的诱惑,只是为了解决多维数组的缓慢性。
(我听到一个谣言说C#做了这样的事情,尽管我也听到另一个谣言说CLR实现太糟糕了,不值得拥有......也许他们只是谣言...)
问题
那么,为什么Java没有真正的多维数组的实现呢?是否有坚实的技术原因?我在这里错过了什么?
更新
一个奇怪的旁注:如果您使用运行总计而不是 .为什么与 a 有这么小的差异,而与 a 会有这么大的差异?int
long
int
long
基准测试代码
我用于基准测试的代码,以防有人想尝试重现这些结果:
public class Multidimensional {
public static long sumSingle(final int[] arr) {
long total = 0;
for (int i=0; i<arr.length; i++)
total+=arr[i];
return total;
}
public static long sumMulti(final int[][][] arr) {
long total = 0;
for (int i=0; i<arr.length; i++)
for (int j=0; j<arr[0].length; j++)
for (int k=0; k<arr[0][0].length; k++)
total+=arr[i][j][k];
return total;
}
public static void main(String[] args) {
final int iterations = 1000000;
Random r = new Random();
int[] arr = new int[1000000];
for (int i=0; i<arr.length; i++)
arr[i]=r.nextInt();
long total = 0;
System.out.println(sumSingle(arr));
long time = System.nanoTime();
for (int i=0; i<iterations; i++)
total = sumSingle(arr);
time = System.nanoTime()-time;
System.out.printf("Took %d ms for single dimension\n", time/1000000, total);
int[][][] arrMulti = new int[100][100][100];
for (int i=0; i<arrMulti.length; i++)
for (int j=0; j<arrMulti[i].length; j++)
for (int k=0; k<arrMulti[i][j].length; k++)
arrMulti[i][j][k]=r.nextInt();
System.out.println(sumMulti(arrMulti));
time = System.nanoTime();
for (int i=0; i<iterations; i++)
total = sumMulti(arrMulti);
time = System.nanoTime()-time;
System.out.printf("Took %d ms for multi dimension\n", time/1000000, total);
}
}