在Java中高效实现多维数组?

时间:2011-02-10 15:34:41

标签: java arrays multidimensional-array

据我所知(来自this等答案),java没有原生的多维连续内存数组(unlike C#, for example)。

虽然锯齿状数组语法(数组数组)可能适用于大多数应用程序,但如果您确实需要连续内存数组的原始效率(避免不必要的内存读取),我仍然想知道最佳实践是什么? / p>

我当然可以使用映射到2D的一维数组,但我更喜欢更有条理的东西。

7 个答案:

答案 0 :(得分:5)

手动操作并不困难:

int[] matrix = new int[ROWS * COLS];

int x_i_j = matrix[ i*COLS + j ];

现在,它真的比java的多维数组快吗?

int x_i_j = matrix[i][j];

随机访问,也许。对于连续访问,可能不是 - matrix[i]几乎肯定在L1缓存中,如果不在寄存器缓存中。在最佳情况下,matrix[i][j]需要一次添加和一次内存读取;而matrix[i*COLS + j]可能需要2次加法,一次乘法,一次内存读取。但谁在数数?

答案 1 :(得分:4)

这取决于您的访问模式。使用this simple program,将int[][]与作为矩阵处理的1D int[]数组上的2D映射进行比较,原生Java 2D矩阵为:

    当行在缓存上时,
  1. 快25%,即:按行访问:
  2. 当行不在缓存中时缓慢100%,即:通过colums访问:
  3. 即:

    // Case #1
    for (y = 0; y < h; y++)
        for (x = 0; x < w; x++)
            // Access item[y][x]
    
    // Case #2
    for (x = 0; x < w; x++)
        for (y = 0; y < h; y++)
            // Access item[y][x]
    

    1D矩阵计算如下:

    public int get(int x, int y) {
        return this.m[y * width + x];
    }
    

答案 2 :(得分:3)

如果你真的想要一个连续内存数组的更多结构,请将它包装在一个对象中。

public class My2dArray<T> {

  int sizeX;

  private T[] items;

  public My2dArray(int x, int y) {
    sizeX = x;
    items = new T[x*y];
  }

  public T elementAt(int x, int y) {
    return items[x+y*sizeX];
  }

}

不是一个完美的解决方案,你可能已经知道了。因此,请考虑确认您怀疑是真实的。

Java仅提供用于组织代码的某些构造,因此最终您必须达到类或接口。由于这也需要特定的操作,因此您需要一个类。

性能影响包括为每个阵列访问创建JVM堆栈帧,并且理想的是避免这样的事情;但是,JVM堆栈框架如何 JVM实现它的范围。代码组织需要适当的范围,因此我无法想象这种性能影响(不违反“一切都是对象”的精神)。

答案 3 :(得分:1)

示例实现,没有编译器。这基本上是当您访问多维数组时C / C ++在幕后所做的事情。当指定的实际尺寸小于实际尺寸时,您必须进一步定义存取器行为。等等。开销将是最小的,可以进一步优化,但这是微优化imho。此外,在JIT启动后,你实际上从未真正知道引擎盖下发生了什么。

class MultiDimentionalArray<T> {
//disclaimer: written within SO editor, might contain errors
    private T[] data;
    private int[] dimensions; //holds each dimensions' size

    public MultiDimensionalArray(int... dims) {
        dimensions = Arrays.copyOf(dims, dims.length);
        int size = 1;
        for(int dim : dims)
            size *= dim;
        data = new T[size];
    }

   public T access(int... dims) {
       int idx = 1;
       for(int i = 0; i < dims.length)
            idx += dims[i] * dimensions[i]; //size * offset
       return data[idx];
    }
}

答案 4 :(得分:1)

实现多维数组的最有效方法是将一维数组用作多维数组。有关将2D阵列映射到1D阵列的信息,请参阅this answer

// 2D data structure as 1D array
int[] array = new int[width * height];
// access the array 
array[x + y * width] = /*value*/;
  

我当然可以使用映射到2D的一维数组,但我更喜欢更有条理的东西。

如果您想以更有条理的方式访问array,请为其创建一个类:

public class ArrayInt {

    private final int[] array;
    private final int width, height;

    public ArrayInt(int width, int height) {
        array = new int[width * height];
        this.width = width;
        this.height = height;
    }

    public int getWidth() {
        return width;
    }

    public int getHeight() {
        return height;
    }

    public int get(int x, int y) {
        return array[x + y * width];
    }

    public void set(int x, int y, int value) {
        array[x + y * width] = value;
    }

}

如果你想要对象数组,可以使用泛型并定义类Array<T>,其中T是存储在数组中的对象。

在性能方面,在大多数情况下,这将比Java中的多维数组更快。原因可以找到in the answers to this question

答案 5 :(得分:1)

假设您有一个2D数组int[][] a = new int[height][width],所以按照惯例,您有索引a[y][x]。根据您表示数据的方式以及访问方式,性能的变化范围为20:

comparison of 2D array access

代码:

public class ObjectArrayPerformance {
    public int width;
    public int height;
    public int m[];

    public ObjectArrayPerformance(int w, int h) {
            this.width = w;
            this.height = h;
            this.m = new int[w * h];
    }

    public int get(int x, int y) {
            return this.m[y * width + x];
    }

    public void set(int x, int y, int value) {
            this.m[y * width + x] = value;
    }

    public static void main (String[] args) {
            int w = 1000, h = 2000, passes = 400;

            int matrix[][] = new int[h][];

            for (int i = 0; i < h; ++i) {
                    matrix[i] = new int[w];
            }

            long start;
            long duration;

            System.out.println("duration[ms]\tmethod");

            start = System.currentTimeMillis();
            for (int z = 0; z < passes; z++) {
                    for (int y = 0; y < h; y++) {
                        for (int x = 0; x < w; x++) {
                                    matrix[y][x] = matrix[y][x] + 1;
                            }
                    }
            }
            duration = System.currentTimeMillis() - start;
            System.out.println(duration+"\t2D array, loop on x then y");

            start = System.currentTimeMillis();
            for (int z = 0; z < passes; z++) {
                    for (int x = 0; x < w; x++) {
                            for (int y = 0; y < h; y++) {
                                    matrix[y][x] = matrix[y][x] + 1;
                            }
                    }
            }
            duration = System.currentTimeMillis() - start;
            System.out.println(duration+"\t2D array, loop on y then x");

            //

            ObjectArrayPerformance mt = new ObjectArrayPerformance(w, h);
            start = System.currentTimeMillis();
            for (int z = 0; z < passes; z++) {
                    for (int x = 0; x < w; x++) {
                            for (int y = 0; y < h; y++) {
                                    mt.set(x, y, mt.get(x, y) + 1);
                            }
                    }
            }
            duration = System.currentTimeMillis() - start;
            System.out.println(duration+"\tmapped 1D array, access trough getter/setter");

            //

            ObjectArrayPerformance mt2 = new ObjectArrayPerformance(w, h);
            start = System.currentTimeMillis();
            for (int z = 0; z < passes; z++) {
                    for (int x = 0; x < w; x++) {
                            for (int y = 0; y < h; y++) {
                                    mt2.m[y * w + x] = mt2.m[y * w + x] + 1;
                            }
                    }
            }
            duration = System.currentTimeMillis() - start;
            System.out.println(duration+"\tmapped 1D array, access through computed indexes, loop y then x");

            ObjectArrayPerformance mt3 = new ObjectArrayPerformance(w, h);
            start = System.currentTimeMillis();
            for (int z = 0; z < passes; z++) {
                    for (int y = 0; y < h; y++) {
                        for (int x = 0; x < w; x++) {
                                    mt3.m[y * w + x] = mt3.m[y * w + x] + 1;
                            }
                    }
            }
            duration = System.currentTimeMillis() - start;
            System.out.println(duration+"\tmapped 1D array, access through computed indexes, loop x then y");

            ObjectArrayPerformance mt4 = new ObjectArrayPerformance(w, h);
            start = System.currentTimeMillis();
            for (int z = 0; z < passes; z++) {
                    for (int y = 0; y < h; y++) {
                        int yIndex = y * w;
                        for (int x = 0; x < w; x++) {
                                    mt4.m[yIndex + x] = mt4.m[yIndex + x] + 1;
                            }
                    }
            }
            duration = System.currentTimeMillis() - start;
            System.out.println(duration+"\tmapped 1D array, access through computed indexes, loop x then y, yIndex optimized");
    }
}

我们可以得出结论,线性访问性能更多地取决于您处理数组的方式(行然后列或反向?:性能增益= x10,很多是由于CPU缓存)而不是数组本身的结构(1D vs 2D) :性能增益= x2)。

如果是随机访问,性能差异应该低得多,因为CPU缓存效果较差。

答案 6 :(得分:-1)

如果没有C构造就无法生存,那么总会有JNI。

或者您可以开发自己的Java派生语言(以及VM和优化JIT编译器),该语言具有多维连续内存数组的语法。