【问题标题】:Java: what is faster split in a loop or doing it before [closed]Java:在循环中更快地拆分或在[关闭]之前执行它
【发布时间】:2012-01-27 13:54:56
【问题描述】:

Goolge 提出了很多比较,但不是我要找的:

如果我想遍历一个拆分的字符串,有什么更好的方法

String[] flagArr = flags.split(";");
for (String f: flagArr) {
    // some stuff
}

for (String f: flags.split(";")) {
    // some stuff
}

对于第二段代码,我想知道编译器是否足够聪明,只进行一次拆分

【问题讨论】:

  • 为什么不自己运行一个简短的基准测试呢?只需使用相同的字符串 n 次(n 足够大)运行两个版本并跟踪您感兴趣的时间或内存使用情况。
  • 我想到了一个衡量时间的基准。但后来我认为问一个问题是更好的选择,因为更多地了解 Java 在内部做什么可以提高我的编程技能。此外,其他人可能有同样的问题。
  • 两个版本都可以调试。那就是找出发生了什么后台

标签: java performance loops split


【解决方案1】:

您可以检查生成的字节码,但我很确定它们都会做同样的事情。为什么第二个会有所不同?

编辑:如您所见,两种方式都只调用一次split()

第一个字节码:

public class javatesting.JavaTesting extends java.lang.Object{
public javatesting.JavaTesting();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public static void main(java.lang.String[]);
  Code:
   0:   ldc     #2; //String 1;2;3
   2:   astore_1
   3:   aload_1
   4:   ldc     #3; //String ;
   6:   invokevirtual   #4; //Method java/lang/String.split:(Ljava/lang/String;)
[Ljava/lang/String;
   9:   astore_2
   10:  aload_2
   11:  astore_3
   12:  aload_3
   13:  arraylength
   14:  istore  4
   16:  iconst_0
   17:  istore  5
   19:  iload   5
   21:  iload   4
   23:  if_icmpge       41
   26:  aload_3
   27:  iload   5
   29:  aaload
   30:  astore  6
   32:  aconst_null
   33:  astore  6
   35:  iinc    5, 1
   38:  goto    19
   41:  return

}

还有第二个的字节码:

public class javatesting.JavaTesting extends java.lang.Object{
public javatesting.JavaTesting();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public static void main(java.lang.String[]);
  Code:
   0:   ldc     #2; //String 1;2;3
   2:   astore_1
   3:   aload_1
   4:   ldc     #3; //String ;
   6:   invokevirtual   #4; //Method java/lang/String.split:(Ljava/lang/String;)
[Ljava/lang/String;
   9:   astore_2
   10:  aload_2
   11:  arraylength
   12:  istore_3
   13:  iconst_0
   14:  istore  4
   16:  iload   4
   18:  iload_3
   19:  if_icmpge       37
   22:  aload_2
   23:  iload   4
   25:  aaload
   26:  astore  5
   28:  aconst_null
   29:  astore  5
   31:  iinc    4, 1
   34:  goto    16
   37:  return

}

【讨论】:

  • 谢谢 - 完全回答了这个问题。这就是我想要的。
【解决方案2】:

这根本不重要。不要浪费时间进行此类优化。

编辑:如果您想关心这样的事情,那么您可能会使用第二个选项,因为带有拆分的数组将仅存在于 for 循环范围内。

【讨论】:

  • 我完全不同意。以另一种方式做某事可能会导致巨大的性能差异。请参阅此链接,例如:roseindia.net/javatutorials/appending_strings.shtml
  • 是的,有时重要,有时不重要。在这种情况下它没有,这就是我的观点。
【解决方案3】:

如果您真的想查看字节码的差异,请使用javap -c。用 Eclipse 编译器编译,第一个版本(在源代码中有一个局部变量)给出了:

public static void test(java.lang.String);
  Code:
   0:   aload_0
   1:   ldc #16; //String ;
   3:   invokevirtual   #18; //Method java/lang/String.split:(Ljava/lang/String;)[Ljava/lang/String;
   6:   astore_1
   7:   aload_1
   8:   dup
   9:   astore  5
   11:  arraylength
   12:  istore  4
   14:  iconst_0
   15:  istore_3
   16:  goto    27
   19:  aload   5
   21:  iload_3
   22:  aaload
   23:  astore_2
   24:  iinc    3, 1
   27:  iload_3
   28:  iload   4
   30:  if_icmplt   19
   33:  return
}

第二个:

public static void test(java.lang.String);
  Code:
   0:   aload_0
   1:   ldc #16; //String ;
   3:   invokevirtual   #18; //Method java/lang/String.split:(Ljava/lang/String;)[Ljava/lang/String;
   6:   dup
   7:   astore  4
   9:   arraylength
   10:  istore_3
   11:  iconst_0
   12:  istore_2
   13:  goto    24
   16:  aload   4
   18:  iload_2
   19:  aaload
   20:  astore_1
   21:  iinc    2, 1
   24:  iload_2
   25:  iload_3
   26:  if_icmplt   16
   29:  return
}

如你所见(如果你能读懂的话),第一个变体确实使用了一个额外的局部变量槽(astore_1/aload_1 开头)。但是(a)即使字节码被解释,它的开销也可以忽略不计(它只是复制引用)并且(b)任何 JIT 编译器都能够优化它,无论您是否事先进行了一些静态字节码优化: 局部变量 (1) 以后永远不会使用。

源代码中的局部变量主要用于清晰,以及能够重用中间结果。从根本上说,第二个变体(没有显式的局部变量)不会让您在方法中进一步使用split 的结果(甚至直接在循环中),而在代码中命名局部变量时可以.

有了第二段代码,我想知道编译器是否足够聪明 只做一次拆分

这两种方法的唯一区别是在源代码中显式声明了一个局部变量。

在这两种情况下,for (String f: flags.split(";")) { } 是语法糖(除了两个局部变量都不可访问)等价于:

int _hidden_i = 0;
for (String[] _hidden_arr=flags.split(";"); _hidden_i<_hidden_arr.length; _hidden_i++){
    String f = _hidden_arr[_hidden_i];
    // some stuff
}

需要注意的是:

  • 使用这种表示法,_hidden_i 永远不会暴露,因此您永远无法将某些内容分配给 _hidden_arr[_hidden_i]
  • 即使是一个天真的源代码到字节码编译器也知道_hidden_arr_hidden_i 将永远不会再次使用,因此它不会复制它们以供进一步使用。

根本不同的是:

for (var i = 0; i < flags.split(";").length; i++) {
    String f = flags.split(";")[i];
}

for(;;) 表示法中,第一部分总是执行一次。第二个表达式总是在进入循环块之前执行(以测试是否运行它)。最后一个总是在运行循环块之后执行(除非使用break)。

您通常希望避免在第二部分中可能很繁重的方法调用,尤其是当它总是会得到相同的结果时。

【讨论】:

  • 谢谢 - 非常好的背景知识。
【解决方案4】:

认为编译器会对此进行优化。这可能取决于您选择的 Java 提供程序(sun、ibm 等),但这很简单,如果每个编译器都没有,我会感到惊讶。

也许创建一个非常大的flags 字符串并进行一些简单的性能测试?

但是,既然这很容易,为什么不做你的第一个选择....

【讨论】:

    【解决方案5】:

    我很确定拆分只会进行一次。在内部,这段代码将被转换为带有迭代器的循环,因此编译器将执行一次拆分,创建结果集合,然后在该集合上创建迭代器。

    如果要重复拆分,迭代器将不会指向正确的位置。

    【讨论】:

      【解决方案6】:

      试试这个:

      public double calculateRunTime()
      {
          long startTime = System.currentTimeMillis();
      
          split();
      
          long endTime = System.currentTimeMillis();
      
          return endTime - startTime;
      }
      

      【讨论】:

        猜你喜欢
        • 2012-03-10
        • 2012-06-23
        • 1970-01-01
        • 2019-08-04
        • 1970-01-01
        • 1970-01-01
        • 2022-07-26
        • 1970-01-01
        • 2019-09-03
        相关资源
        最近更新 更多