Java多维数组:内存地址和遍历速度

时间:2015-04-10 15:34:05

标签: java arrays memory

据我所知,在Java中处理二维数组时,在循环中访问数组元素的顺序会影响遍历数组所需的时间:

int size = 500;
int[][] array = new int[size][size];

// Slower
for (int i = 0; i < size; i++) {
    for (int j = 0; j < size; j++) {
        array[j][i] = 1;
    }
}

// Faster
for (int i = 0; i < size; i++) {
    for (int j = 0; j < size; j++) {
        array[i][j] = 1;
    }
}

这对我来说很有意义,因为它需要较少的内存跳跃,而只能步入后续地址。

当使用三维数组进行相同操作时,我对结果更加困惑:

Time:  12356332 nanoseconds. ([i][j][k])
Time:  18278948 nanoseconds. ([i][k][j])
Time:  13985288 nanoseconds. ([j][i][k])
Time: 126192723 nanoseconds. ([j][k][i])
Time:  39441820 nanoseconds. ([k][i][j])
Time: 156352618 nanoseconds. ([k][j][i])

[i][j][k][j][i][k]的结果在大多数代码运行中都是可以互换的。这是为什么?

另外你能解释一下多维数组是如何存储在Java中的吗?

给定数组int[][][] array = new int[2][2][2],内存地址是这样的(我的理解是每个块之间可能有其他变量的附加数据,但我已经省略了这些情况,因为它们不相关): Multi-Dimensional Array Memory Addresses (很抱歉,如果图片令人困惑,我只能使用油漆,并尽力表达布局。所以array[0][0][0] //[i][j][k]会在地址06

1 个答案:

答案 0 :(得分:3)

首先要考虑的是,java在其单一实体的意义上具有密切关注的无多维数组。相反,java仅处理单维数组,但元素类型本身可以是数组类型,并且语言/编译器支持与例如相同的语法。 C可用于多个维度以快速寻址元素。

例如,int[][] twoDim = new int[50][100];实际上在内存中创建了51个对象; 一个类型为int[][]数组,其中包含50个int[]类型元素的空间,并使用int[100]类型的数组填充这50个空格(剩下的50个)对象)。这51个对象中的每一个都是相同的,它们可以位于堆中的任何位置。实际上,他们甚至不需要在同一个声明中创建。

下面两个方法给出了与结果相同的数组,但第二个方法应该明确真正

 public int[][] createArrayA(int n, int m) {
     return new int[n][m];
 }

 public int[][] createArrayB(int n, int m) {
     int[][] array = new int[n][];
     for (int i=0; i<n; ++i)
         array[i] = new int[m];
     return array;
 }

请注意,在createArrayB()中,您可以选择向后初始化n维(循环倒计数而不是向上),从而导致相同的数组:

 public int[][] createArrayC(int n, int m) {
     int[][] array = new int[n][];
     for (int i=n-1; i>=0; --i)
         array[i] = new int[m];
     return array;
 }

变体B和C的内存布局会有所不同,因为它们的分配顺序不同。但是不要以为它们的内存布局是一个常量,垃圾收集器可能会在堆中将它们放在堆中。

如果你担心访问速度,迭代数组的最快方法是最左边的维度进入最外层循环最右边的维度转到最里面的循环(这严格围绕各个维度线性位于内存中的事实)。线性内存访问的CPU比随机访问更快(我不会进入为什么)。

在处理数组时,可以考虑可以进行两次微优化。

首先是尺寸的顺序,当您可以随意订购尺寸时,将最小最左侧和最大最右侧放置:

int[][] slowArray = new int[10000][2];
int[][] fastArray = new int[2][10000];

第二个还节省了大量内存,因为慢变量由10000 x int [2] = 10001个对象组成,而快速变体由2 x int [10000] = 3个对象组成。

第二个是使用数组维度的切片(它是代码不变运动的一种形式):

long sum = 0;
int[][] fastArray = new int[2][10000];
for (int i=0; i<fastArray.length; ++i) {
    int[] subArray = fastArray[i];
    for (int j=0; j<subArray.length; ++j) {
        sum += subArray[j];
    }
}

定义局部变量subArray完全从内部循环中消除外部维度(毕竟,我从不在内部循环中更改,所以为什么每次要解析j时都要查找数组索引?)。这种优化可以由即时编译器自动执行,但据我所知,它不会自动执行总是。偶尔的循环并不重要,但是如果数组行走是你处理时间的重要部分,那么就需要考虑优化。