为什么Java没有真正的多维数组?

39

简短版(TL;DR)的问题是:

问题

为什么Java没有实现真正的多维数组?这是否有充分的技术原因?我在这里错过了什么?

背景

在语法层面上,Java具有多维数组,因为可以声明

int[][] arr = new int[10][10];

然而,事实似乎并非如此。JVM并没有分配足够存储100个int的连续块的内存,而是将其作为int数组的数组:因此每一层都是一块连续的内存,但整体不是连续的。因此,访问arr [i] [j] 相当慢:JVM必须

  1. 找到存储在arr [i]中的int [];
  2. 索引此以查找存储在arr [i] [j] 中的int。

这涉及查询对象以从一层到下一层跳转,这相当昂贵。

Java 之所以这样做

从某个层面上来看,即使所有内容都在一个固定的块中分配,也不难看出为什么无法将其优化为简单的比例和添加查找。问题在于 arr[3] 是它自己的引用,并且它可以更改。因此,尽管数组大小固定,但我们很容易编写

arr[3] = new int[11];

现在,由于这一层已经增长,因此scale-and-add功能出了问题。你需要在运行时知道所有内容是否仍然与以前一样大。此外,当然,它将在RAM的其他位置分配(因为它比要替换的大),因此它甚至不在scale-and-add所需的正确位置。

这个方法存在两个问题。

首先,它很慢。我进行了一个测试,对单维或多维数组中的内容求和,多维情况(int[100][100][100])几乎需要花费原来的两倍时间(714秒对371秒)(分别用随机int值填充int[1000000]int[100][100][100],并在缓存热后运行1000000次)。

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];

这样做还不够好,因为只有 arr 在这里是 final 的:arr [3] 仍然不是,可能会改变,因此结构仍然可能发生变化。但如果我们有一种声明方式,使其在底层存储 int 值的地方之外始终是 final,那么我们就有了一个完整的不可变结构体,并且它可以作为一个块分配,并使用缩放和添加进行索引。

语法上会是什么样子我不确定(我不是设计语言的人)。也许是这样:

final int[final][] arr = new int[10][10];

尽管看起来有点奇怪。这意味着:在顶层使用 final;在下一层使用final;在最底层不使用final(否则,int值本身将是不可变的)。

如果各级都是final状态,则JIT编译器可以将其优化为与单维数组相同性能的代码,这样就消除了编写这种代码的诱惑,以应对多维数组的缓慢性。

(我听说C#做了类似的事情,虽然我也听到另一个谣言称CLR实现很糟糕,不值得拥有...也许它们只是谣言...)

问题

那么为什么Java没有真正的多维数组实现?是否存在确切的技术原因?我错过了什么吗?

更新

一个奇怪的副笔记:如果你使用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);
    }

}

8
如果我遇到了看起来像是一个已经存在将近20年的成熟语言的缺陷,我的第一个假设是我错过了什么...而这里有一些非常聪明的人可能能够告诉我我错过了什么... - chiastic-security
22
此问题不适合,因为它是伪装成问题的抱怨博客帖子 - user177800
3
Java没有这个功能,因为它的设计并不包含此项功能。为什么C没有对象?为什么Pascal没有矩阵求逆运算符? - Hot Licks
11
@JarrodRoberson 你认为我在哪里失去了耐心并开始发牢骚?整个语气都是冷静的。这篇文章最后提出了一个明确的技术问题,询问是否有理由未包含此功能。 - chiastic-security
9
有些人只是询问事物如何运作或者为什么会以某种方式运作。如果这是一个怒斥的话,那么它被“完美地伪装”了,因为我在这里看不到任何这样的东西。 - maaartinus
显示剩余20条评论
6个回答

21
但事实表明这并不是人们所期望的。为什么呢?考虑到形式T[]表示“类型为T的数组”,那么就像我们期望int[]表示“类型为int的数组”一样,我们也期望int[][]表示“类型为数组的类型为int”的数组,因为没有比将int[]作为T更少的理由。因此,考虑到可以有任何类型的数组,从声明和初始化数组时使用[和](以及{、}和,)的方式,如果没有禁止数组的特殊规则,我们就可以免费获得这种使用方式。现在再考虑一下,我们可以用交错数组做一些无法通过其他方式实现的事情:
1. 我们可以有“交错”数组,其中不同的内部数组具有不同的大小。 2. 我们可以在外部数组中有空数组,以进行数据映射或懒惰构建。 3. 我们可以在数组中故意别名,例如lookup[1]与lookup[5]相同的数组。(这可以为某些数据集节省大量空间,例如许多Unicode属性可以映射到包含具有匹配模式的范围的属性的叶数组中)。 4. 一些堆实现可以更好地处理许多较小对象而不是一个大对象。
当然有些情况下这种多维数组很有用。现在,任何功能的默认状态都是未指定和未实现的。必须有人决定指定和实现一个功能,否则它就不会存在。由于如上所示,除非有人决定引入特殊禁止数组的功能,否则将存在数组的数组类型的多维数组。由于数组的数组对于以上原因很有用,这将是一个奇怪的决定。相反,具有定义的秩可以大于1,并且因此可以与一组索引一起使用而不是单个索引的数组类型的多维数组并不自然地遵循已经定义的内容。有人需要:
1. 决定声明、初始化和使用的规范将如何工作。 2. 将其记录下来。 3. 编写实际代码来完成此操作。 4. 测试执行此操作的代码。 5. 处理错误、边缘情况、报告不是错误的错误以及由于修复错误而导致的向后兼容性问题。 此外,用户还必须学习这个新功能。因此,它必须值得。使其值得的一些事情可能包括:
  1. 如果没有做同样事情的方法。
  2. 如果做同样事情的方法很奇怪或不为人所知。
  3. 人们会期望从类似的上下文中得到它。
  4. 用户无法自己提供类似的功能。

然而,在这种情况下:

  1. 但是有办法。
  2. 在C和C++程序员中已经知道使用数组内部步长的技巧,Java基于其语法构建,因此相同的技术可以直接应用。
  3. Java的语法基于C++,而C++只支持作为数组的数组来直接支持多维数组。(除非静态分配,但这不是Java中具有类比的东西,因为数组是对象)。
  4. 一个人可以很容易地编写一个包装数组和步幅大小详细信息的类,并通过一组索引访问它。

实际上,问题不在于“为什么Java没有真正的多维数组”?而是“为什么要这样做?”

当然,你提出的支持多维数组的观点是有效的,一些语言确实因此而拥有它们,但负担仍然是为一个特性进行争论,而不是反对它。

(我听说C#做了类似的事情,尽管我也听到另一个传言,即CLR实现非常糟糕,不值得拥有......也许它们只是谣言......)

像许多谣言一样,这里有一定的真相,但并不是全部真相。

.NET数组确实可以具有多个秩。这不是它比Java更灵活的唯一方式。每个秩还可以具有低于零的下界。因此,例如,您可以拥有从-3到42或二维数组的数组,其中一个秩从-2到5,另一个秩从57到100,或其他任何内容。

C#没有从其内置语法中完全访问所有这些(您需要调用Array.CreateInstance()以获得低于零的下限),但它确实允许您使用int [,]的语法。对于int的二维数组,int [,,]表示三维数组等等。

现在,处理低于零的下界所涉及的额外工作会增加性能负担,但这些情况相对不常见。因此,具有下界为0的单秩数组被视为具有更高性能的特殊情况。实际上,它们在内部是一种不同类型的结构。

在.NET中,具有下界为零的多维数组被视为其下界恰好为零的多维数组(即作为较慢情况的示例),而不是更快的情况能够处理大于1的秩。

当然,.NET可以为基于零的多维数组提供快速路径,但这样做会遇到Java没有它们的所有原因,而且已经有一个特殊情况了,而特殊情况很糟糕,那么就会有两个特殊情况,它们会更糟糕。 (事实上,尝试将一种类型的值分配给另一种类型的变量可能会出现一些问题)。
上面没有一件事清楚地表明Java不可能拥有你所说的多维数组;这将是一个明智的决定,但是作出的决定也是合理的。

1
因为我们可以进行抽象化,所以我们不需要标准化的论点是站不住脚的;第一种方法使其成为更高阻力的路径,并且可能有许多不兼容的实现,其中许多存在缺陷,第二种方法使其在概念验证之外使用也是可以的。 - Dmytro
@Dmitry 不,它是有道理的,因为人们显然并不需要它。如果他们需要,那么Java没有它意味着人们不使用Java,而事实上他们确实在使用。可以说它仍然足够有用,以至于他们应该拥有它,我在上面也说过这也是一个明智的设计决策,但它并不是必要的,留下它的决定是无可辩驳的。 - Jon Hanna

16

我想这应该是一个向James Gosling提问的问题。 Java最初的设计是关于面向对象编程和简单性,而不是速度。

如果您有更好的多维数组工作方式的想法,有几种方法可以实现:

  1. 提交JDK增强提案
  2. 通过Java社区流程开发新的JSR。
  3. 提出新的项目

更新。当然,您不是第一个质疑Java数组设计问题的人。
例如,项目SumatraPanama也将从真正的多维数组中受益。

“Arrays 2.0” 是John Rose在2012年JVM语言峰会上关于此主题的演讲。


1
@MarkoTopolnik - 数组的数组更简单,因为多维数组可以被忽略,除了编译器中的一些细节。有点像C语言基本上忽略普通数组。 - Hot Licks
1
@HotLicks,您简洁地表达了我在答案中需要用很多词来表达的内容。 - maaartinus
1
@apangin - 实际上,支持“真正的”(无论这意味着什么)多维数组并不是非常困难。也许最大的问题在于符号表示,能够声明指向任一类型(以及两者的组合)的指针。但这是Java发明时不需要的额外复杂性,而且它仍然具有可疑的实用性——在大多数情况下,效率差异都是微不足道的。 - Hot Licks
2
正如John Rose所指出的那样,人们并不是真正想要2D数组,而是想要它们的好处,比如缓存友好性。他还解释了如何在当前框架内获得这些好处。 - Marko Topolnik
2
并支持“缓存友好性”只需要添加一个新的字节码来创建数组。它仍然是一组数组,因此将按照“通常”的(对于Java而言)方式进行寻址,但是单个数组将在一起分配。 GC需要进行适度的更改,字节码解释器或JITC不需要进行任何更改(除了添加新指令)。当然,javac必须更改以生成该指令。(并且JITC可以通过识别模式来完成此优化,而无需新指令。) - Hot Licks
显示剩余5条评论

10

在我看来,你似乎已经自己回答了这个问题:

......这鼓励我们将其写成一个平坦的数组,即使这样会使其不自然且难以阅读。

所以请将它写成易于阅读的平坦数组。可以使用一个简单的辅助工具,例如

double get(int row, int col) {
    return data[rowLength * row + col];
}

如果你有类似的setter和可能的+=-等效项,可以假装正在使用2D数组。这真的没什么大不了的。您不能使用数组符号,一切都变得冗长而丑陋。但这似乎是Java的方式。与BigIntegerBigDecimal完全相同。您无法使用大括号访问Map,这是一个非常相似的情况。

现在的问题是所有这些功能有多重要?更多的人会高兴吗,如果他们可以编写x += BigDecimal.valueOf("123456.654321") + 10;,或spouse["Paul"] = "Mary";,或者在没有样板文件的情况下使用2D数组,或其他什么?这些都很好,您甚至可以进一步去做,例如数组切片。但实际上并没有真正的问题。您必须在许多其他情况下选择冗长和低效率之间。依我看,花费在这个功能上的精力可以在其他地方更好地利用。您的2D数组是一个新的最佳选择.....

Java实际上没有2D基本数组,...

它主要是一种语法糖,底层的东西是对象数组。

double[][] a = new double[1][1];
Object[] b = a;

由于数组是具体化的,当前实现几乎不需要任何支持。您的实现会引起一系列问题:

  • 当前有8种原始类型,这意味着有9种数组类型,二维数组算第十种吗?三维数组呢?
  • 有一个单独的特殊对象头类型用于数组。二维数组可能需要另一个。
  • 那么java.lang.reflect.Array呢?克隆它以用于二维数组吗?
  • 许多其他功能也必须进行适应,例如序列化。

接下来的内容是什么?

??? x = {new int[1], new int[2]};

你需要一个老式的 2D int[][] 数组吗?那交互操作呢?

我猜可以实现,但是 Java 缺少更简单和更重要的功能。有些人一直需要使用 2D 数组,但很多人几乎不记得什么时候使用过数组。


9

我无法复现你所声称的性能提升。具体来说,测试程序:

public abstract class Benchmark {

    final String name;

    public Benchmark(String name) {
        this.name = name;
    }

    abstract int run(int iterations) throws Throwable;

    private BigDecimal time() {
        try {
            int nextI = 1;
            int i;
            long duration;
            do {
                i = nextI;
                long start = System.nanoTime();
                run(i);
                duration = System.nanoTime() - start;
                nextI = (i << 1) | 1;
            } while (duration < 1000000000 && nextI > 0);
            return new BigDecimal((duration) * 1000 / i).movePointLeft(3);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public String toString() {
        return name + "\t" + time() + " ns";
    }

    public static void main(String[] args) throws Exception {
        final int[] flat = new int[100*100*100];
        final int[][][] multi = new int[100][100][100];

        Random chaos = new Random();
        for (int i = 0; i < flat.length; i++) {
            flat[i] = chaos.nextInt();
        }
        for (int i=0; i<multi.length; i++)
            for (int j=0; j<multi[0].length; j++)
                for (int k=0; k<multi[0][0].length; k++)
                    multi[i][j][k] = chaos.nextInt();

        Benchmark[] marks = {
            new Benchmark("flat") {
                @Override
                int run(int iterations) throws Throwable {
                    long total = 0;
                    for (int j = 0; j < iterations; j++)
                        for (int i = 0; i < flat.length; i++)
                            total += flat[i];
                    return (int) total;
                }
            },
            new Benchmark("multi") {
                @Override
                int run(int iterations) throws Throwable {
                    long total = 0;
                    for (int iter = 0; iter < iterations; iter++)
                        for (int i=0; i<multi.length; i++)
                            for (int j=0; j<multi[0].length; j++)
                                for (int k=0; k<multi[0][0].length; k++)
                                    total+=multi[i][j][k];
                    return (int) total;
                }
            },
            new Benchmark("multi (idiomatic)") {
                @Override
                int run(int iterations) throws Throwable {
                    long total = 0;
                    for (int iter = 0; iter < iterations; iter++)
                        for (int[][] a : multi)
                            for (int[] b : a)
                                for (int c : b)
                                    total += c;
                    return (int) total;
                }
            }

        };

        for (Benchmark mark : marks) {
            System.out.println(mark);
        }
    }
}

在我的工作站上运行

java version "1.8.0_05"
Java(TM) SE Runtime Environment (build 1.8.0_05-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.5-b02, mixed mode)
打印
flat              264360.217 ns
multi             270303.246 ns
multi (idiomatic) 266607.334 ns
换句话说,我们观察到您提供的一维和多维代码之间仅有3%的差异。如果使用惯用的Java语法(特别是增强的for循环)进行遍历,则此差异将降至1%(可能是因为边界检查在循环引用的同一数组对象上执行,使及时编译器能够更完全地省略边界检查)。
因此,性能似乎不足以证明增加语言复杂性的正当性。具体而言,为了支持真正的多维数组,Java编程语言必须区分数组和多维数组。同样,程序员必须区分它们,并了解它们之间的区别。API设计人员必须考虑是否使用数组或多维数组。编译器、类文件格式、类文件验证器、解释器和即时编译器都必须进行扩展。这将特别困难,因为不同维度计数的多维数组具有不兼容的内存布局(因为必须存储其维度大小以启用边界检查),因此不能彼此成为子类型。因此,java.util.Arrays类的方法可能必须针对每个维度计数进行复制,所有与数组一起工作的多态算法也必须如此。
总之,为了大多数程序而言,将Java扩展以支持多维数组会带来微不足道的性能提升,但需要对其类型系统、编译器和运行时环境进行非平凡的扩展。因此,引入它们将与Java编程语言的设计目标相矛盾,特别是使其简单

啊,你把“total”(计数器)的类型从“long”改成了“int”。奇怪的是,这似乎导致时间差异大大缩小!为什么会这样?!我已经将我的基准测试代码添加到问题中。 - chiastic-security
事实上,这似乎值得写一个新问题,明天我会写出来。(顺便说一下,我不知道为什么您的答案被踩了,但不是我。实际上,看起来像是有人刚刚把所有答案都踩了。) - chiastic-security
1
已修复,我现在也使用了“long”。这不会对测量结果产生显著影响。 - meriton
2
如果我使用你的测试工具,并将迭代次数减少到20000,那么我的输出结果就与我的测试工具相似。如果我将迭代次数设置为100000,则会得到与你所见相似的输出结果。如果我在主方法中更改基准的顺序,则再次获得与我的测试工具相似的输出结果。因此,你看到的巨大差异很可能是及时编译器做出的错误决策的产物,在第二个循环之前触发了主方法的即时编译器,因此解释器无法收集有关该循环的任何统计信息。 - meriton
1
@chiastic-security: 当你测试一个简单的循环时,Java中经常出现"longint慢"的情况。请参阅https://dev59.com/IGIj5IYBdhLWcg3w04Up --- JIT更积极地展开具有int归纳变量的循环比具有long归纳变量的循环。 - tmyklebu
显示剩余2条评论

3
由于这个问题很大程度上与性能有关,让我贡献一个基于 JMH 的适当基准测试。 我还更改了一些内容,使您的示例更简单,性能优势更明显。
在我的情况下,我比较了一个一维数组和一个二维数组,并使用了非常短的内部维度。 这是缓存的最坏情况。
我尝试了使用长整型和整型累加器,并未看到它们之间的区别。 我提交了使用整型的版本。
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@BenchmarkMode(Mode.AverageTime)
@OperationsPerInvocation(X*Y)
@Warmup(iterations = 30, time = 100, timeUnit=MILLISECONDS)
@Measurement(iterations = 5, time = 1000, timeUnit=MILLISECONDS)
@State(Scope.Thread)
@Threads(1)
@Fork(1)
public class Measure
{
  static final int X = 100_000, Y = 10;
  private final int[] single = new int[X*Y];
  private final int[][] multi = new int[X][Y];

  @Setup public void setup() {
    final ThreadLocalRandom rnd = ThreadLocalRandom.current();
    for (int i=0; i<single.length; i++) single[i] = rnd.nextInt();
    for (int i=0; i<multi.length; i++)
      for (int j=0; j<multi[0].length; j++)
          multi[i][j] = rnd.nextInt();
  }

  @Benchmark public long sumSingle() { return sumSingle(single); }

  @Benchmark public long sumMulti() { return sumMulti(multi); }

  public static long sumSingle(int[] arr) {
    int total = 0;
    for (int i=0; i<arr.length; i++)
      total+=arr[i];
    return total;
  }

  public static long sumMulti(int[][] arr) {
    int total = 0;
    for (int i=0; i<arr.length; i++)
      for (int j=0; j<arr[0].length; j++)
          total+=arr[i][j];
    return total;
  }
}

性能差异比您测得的更大

Benchmark                Mode  Samples  Score  Score error  Units
o.s.Measure.sumMulti     avgt        5  1,356        0,121  ns/op
o.s.Measure.sumSingle    avgt        5  0,421        0,018  ns/op

这是一个超过三倍的因素。(注意到计时是针对每个数组元素报告的。)

我还注意到没有热身运动:前100毫秒与后面的速度一样快。显然这是一个非常简单的任务,解释器已经做了所有使其最优化所需的工作。

更新

sumMulti的内部循环更改为

      for (int j=0; j<arr[i].length; j++)
          total+=arr[i][j];

(注意arr[i].length)的使用显著提高了速度,正如maaartinus所预测的那样。使用arr[0].length将不可能消除索引范围检查。现在的结果如下:

Benchmark                Mode  Samples  Score   Error  Units
o.s.Measure.sumMulti     avgt        5  0,992 ± 0,066  ns/op
o.s.Measure.sumSingle    avgt        5  0,424 ± 0,046  ns/op

2
通过使用 arr[0].length 而不是 arr[i].length,您可以强制进行一些数组边界检查...至少需要进行一个额外的检查。 - maaartinus
我本来想保持OP的方法不变,但你说得对,主要是要比较数组性能。更改后,速度显著提高:现在因子只有2倍。 - Marko Topolnik
有趣...尺寸非常不对称,你尝试过交换它们吗?我想这应该会帮助很多。当然,这会导致一个无休止的问题,我们想要测量什么... - maaartinus
这是故意的(正如我在介绍中所解释的):我的目标是测量最坏情况,从而提供缓存敌对惩罚的上限。 - Marko Topolnik
也许你想要一个更大的数组来将其从L2中清除。拥有许多短数组的最大惩罚是增加了解引用的数量,缺乏预取和可能重新排列内存布局。无论如何,将问题旋转90度并交换X和Y是有意义的 - 但正如你所说,基准测试必须展示最坏情况。 - bestsss

1
如果你想要一个真正的多维数组的快速实现,你可以编写像这样的自定义实现。但是你是对的...它不像数组符号那么简洁。尽管如此,一个整洁的实现可能会更加友好。
public class MyArray{
    private int rows = 0;
    private int cols = 0;
    String[] backingArray = null;
    public MyArray(int rows, int cols){
        this.rows = rows;
        this.cols = cols;
        backingArray  = new String[rows*cols];
    }
    public String get(int row, int col){
        return backingArray[row*cols + col];
    }
    ... setters and other stuff
}

为什么它不是默认实现?

Java的设计者可能必须决定通常的C数组语法的默认表示方式。他们有一个单一的数组符号,可以实现数组嵌套或真正的多维数组。

我认为早期的Java设计者非常关心Java的安全性。许多决策似乎都是为了让普通程序员(或者是在糟糕的一天里表现不佳的优秀程序员)不会搞砸某些东西。对于真正的多维数组,用户更容易浪费大量内存,通过分配无用块来实现。

此外,从Java的嵌入式系统根源来看,他们可能发现更容易找到可分配的内存片段,而不是真正多维对象所需的大块内存。

当然,另一方面是真正需要多维数组的地方受到影响。您将被迫使用库和混乱的代码来完成工作。

为什么它仍未包含在语言中?

即使在今天,真正的多维数组也存在可能浪费/滥用内存的风险。


2
@MarkoTopolnik 在近半个世纪的普遍实践中,这已经不再被视为“晦涩难懂”了。 - Jon Hanna
1
如果封装得当,代码就不会“不自然且难以阅读”。 - Kevin Krumwiede
1
@chiastic-security 当代码在普通JVM上运行时,所有的getter、setter和许多其他琐碎的东西都会被内联。我不确定Android是否也是这样,但有些工具会在编译时进行内联。 - maaartinus
1
@MarkoTopolnik我说“近乎”半个世纪,因为我考虑的是1972年的C语言,尽管在此之前其他编程语言也使用了相同的技术,所以实际上应该超过半个世纪,但我不确定何时会为你处理对象大小(如C中,在Java中你只需要做row*cols + col,而不是(row*cols + col) * size_el),所以我不会把这个说法推到“接近半个世纪”以上。 - Jon Hanna
1
@MaksimDmitriev 你说得对。即使在当前实现中,也有可能进行不必要的分配。 - Teddy
显示剩余9条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接