重构Java数组和原语(double [] [])到Collections和Generics(List <list <double>&gt;)</list <double>

时间:2009-09-11 07:15:29

标签: java arrays generics collections

我一直在重构一次性代码,这是我几年前以类似FORTRAN的方式编写的。大多数代码现在更加有条理和可读。然而,算法的核心(性能关键)使用1维和2维Java数组,其典型代表是:

    for (int j = 1; j < len[1]+1; j++) {
        int jj = (cont == BY_TYPE) ? seq[1][j-1] : j-1;
        for (int i = 1; i < len[0]+1; i++) {
            matrix[i][j] = matrix[i-1][j] + gap;
            double m = matrix[i][j-1] + gap;
            if (m > matrix[i][j]) {
                matrix[i][j] = m;
                pointers[i][j] = UP;
            }
            //...
        }
    }

为了清晰,可维护性以及与其余代码的接口,我想重构它。但是,在阅读Java Generics Syntax for arraysJava Generics and number时,我有以下问题:

  • 性能。该代码计划使用大约10 ^ 8 - 10 ^ 9秒/年,这几乎是可管理的。我的阅读建议将double变为Double有时可以在性能上增加3倍。我想要其他经验。我也希望从foo []移动到List也会受到影响。我没有第一手的知识,经验也很有用。

  • 数组绑定检查。这在double []和List中有不同的处理方式吗?我希望有些问题违反了界限,因为算法非常简单,只适用于少数数据集。

  • 如果我不重构那么代码就有两种方法的丑陋且可能很脆弱的混合。我已经在尝试编写如下内容:

    列表与LT;加倍[] GT;和 列表与LT;双&GT; []

并且理解擦除不会使这个漂亮,并且最多会引起编译器警告。如果没有非常复杂的结构,似乎很难做到这一点。

  • 是否过时。一张海报表明Java数组应该被淘汰。我认为这不会发生RSN,但我想摆脱过时的方法。

摘要目前的共识:

  • 集合对原始数组的性能有很大影响,特别是对于矩阵等构造。这是在自动(非)拳击数字和访问列表项中引起的

  • 对于紧密的数值(科学)算法,数组符号[] []实际上更容易阅读,但变量应尽可能有用地命名

  • 泛型和数组混合不好。将数组包装在类中以将它们输入/输出紧密算法可能很有用。

进行更改没有客观理由

问题 @SeanOwen建议从循环中取出常量值会很有用。假设我没有这么做,这看起来像:

 int len1 = len[1];
 int len0 = len[0];
 int seq1 = seq[1];
 int[] pointersi;
 double[] matrixi;
 for (int i = 1; i < len0+1; i++) {
     matrixi = matrix[i];
     pointersi = pointers[i];
 }
 for (int j = 1; j < len1+1; j++) {
    int jj = (cont == BY_TYPE) ? seq1[j-1] : j-1;
    for (int i = 1; i < len0+1; i++) {
        matrixi[j] = matrixi[j] + gap;
        double m = matrixi[j-1] + gap;
        if (m > matrixi[j]) {
            matrixi[j] = m;
            pointersi[j] = UP;
        }
        //...
    }
}

我认为编译器在做这类事情时应该很聪明。我们还需要这样做吗?

7 个答案:

答案 0 :(得分:7)

我读了肯特贝克关于编写最佳实践的优秀书籍(http://www.amazon.com/Implementation-Patterns/dp/B000XPRRVM)。还有一些有趣的表现数字。 具体来说,数组和各种集合之间存在比较,并且数组实际上要快得多(与ArrayList相比可能是x3)。

另外,如果你使用Double而不是double,你需要坚持使用它,并且不使用double,因为自动(联合)拳击将会扼杀你的表现。

考虑到您的性能需求,我会坚持原始类型数组


更重要的是,我只会计算循环中条件的上限。 这通常在循环之前完成。

但是,如果你不喜欢只在循环中使用的上限变量可以在循环外部访问,你可以利用for循环的初始化阶段,如下所示:

    for (int i=0, max=list.size(); i<max; i++) {
      // do something
    }

我不相信java中数组的过时。对于性能关键的循环,我看不到任何语言设计者拿走最快的选项(特别是如果差异是x3)。


我理解您对可维护性的关注,以及与应用程序其余部分的一致性。但我相信关键循环有权采用某些特殊做法。

我会尝试在不改变代码的情况下使代码尽可能清晰:

  • 仔细询问每个变量名称,理想情况下与我的同事进行10分钟的头脑风暴会议
  • 编写编码注释(我反对他们的使用一般,因为不清楚的代码应该清楚,不要注释;但是一个关键的循环证明它是合理的。)
  • 根据需要使用私有方法(正如Andreas_D在他的回答中指出的那样)。如果设为private final,那么在运行时它们会被内联的可能性很大(因为它们很短),因此在运行时不会对性能产生影响。

答案 1 :(得分:3)

一般原则是在Java中使用通用集合而不是数组,但它只是一个指导原则。我的第一个想法是不要改变这个工作代码。如果你真的想做这个改变,那么就对这两种方法进行基准测试。

正如您所说,性能至关重要,在这种情况下,满足所需性能的代码优于不具备所需性能的代码。

在装箱/打开双打时,您可能还会遇到自动装箱问题 - 这可能是一个更微妙的问题。

Java语言人员一直非常严格地保持JVM在不同版本之间兼容,所以我看不到数组在任何地方 - 我不会把它们称为过时的,只是比其他选项更原始。

答案 2 :(得分:3)

我完全同意KLE的回答。因为代码对性能至关重要,所以我也会保留基于数组的数据结构。我相信,只是引入集合,原始类型和泛型的包装器不会提高可维护性和清晰度。

此外,如果此算法是应用程序的核心,并且已经使用了好几年,那么机会相当低,需要维护,如修复错误或改进。

  

为了清晰,可维护性和   与其余代码连接   我想重构一下。

我没有改变数据结构,而是专注于重命名,并可能将部分代码移动到私有方法。通过查看代码,我不知道发生了什么,而且正如我所看到的那样,问题或多或少是技术变量和字段名称。

只是一个例子:一个二维数组只是命名为'矩阵'。但很明显,这是一个矩阵,所以将它命名为“矩阵”是非常多余的。重命名它以使其变得清晰,这个矩阵真正用于什么,内部有什么类型的数据会更有帮助。

另一位候选人是你的第二行。通过两次重构,我将'jj'重命名为更有意义的东西,并将表达式移动到一个带有'speak'名称的私有方法。

答案 3 :(得分:2)

我认为数组是在算法中存储过程数据的最佳方式。由于Java不支持运算符重载(我认为数组不会很快过时的原因之一)切换到集合会使代码难以阅读:

double[][] matrix = new double[10][10];
double t = matrix[0][0];

List<List<Double>> matrix = new ArrayList<List<Double>>(10);
Collections.fill(matrix, new ArrayList<Double>(10));
double t = matrix.get(0).get(0); // autoboxing => performance

据我所知,Java预存了一些数字实例的包装器Object(例如前100个整数),这样你就可以更快地访问它们,但我认为这对那么多数据没什么帮助。

答案 4 :(得分:1)

  

我认为编译器应该是   聪明地做这种事情。做   我们还需要这样做吗?

JIT可能正确处理它,但如果这部分性能如此重要,那么尝试和基准测试不会受到影响。

答案 5 :(得分:0)

当您知道列表的确切尺寸时,您应该坚持使用数组。数组本身并不坏,它们不会去任何地方。如果要执行大量(非顺序)读写操作,则应使用数组而不是列表,因为列表的访问方法会带来很大的开销。

答案 6 :(得分:0)

除了坚持使用数组之外,我认为你可以用一些有意义的方式收紧这些代码。例如:

  • 确实,不要每次计算循环边界,将它们保存起来
  • 您反复参考矩阵[i]。只需保存对此子阵列的引用,而不是每次都取消引用2D数组
  • 如果你可以在外循环中循环i而不是内循环
  • ,这个技巧会变得更有用
  • 它变得极端,但在本地保存j-1的价值甚至可能证明是值得的而不是重新计算
  • 最后,如果您真的非常关心性能,请在生成的字节代码上运行ProGuard优化器,让它执行一些编译器优化,如展开或窥孔优化