【问题标题】:How to find all naive ("+" based) string concatenations in large Java codebase?如何在大型 Java 代码库中查找所有幼稚(基于“+”)的字符串连接?
【发布时间】:2009-06-23 13:36:12
【问题描述】:

我们有一个庞大的代码库,我们怀疑代码中有很多基于“+”的字符串连接可能会从使用 StringBuilder/StringBuffer 中受益。是否有有效的方法或现有的工具来搜索这些,尤其是在 Eclipse 中?

通过“+”搜索不是一个好主意,因为代码中有很多数学运算,因此需要实际分析代码和类型以找出哪些添加涉及字符串。

【问题讨论】:

    标签: java optimization string


    【解决方案1】:

    我很确定 FindBugs 可以检测到这些。如果没有,它仍然非常有用。

    编辑:它确实可以找到concatenations in a loop,这是唯一一次真正有所作为。

    【讨论】:

    • +1 用于循环中的连接。这就是让我害怕的事情。
    • 我主要担心循环,所以如果 findbugs 这样做,我一定会看看。我以前在其他项目上使用过 findbugs,但从不关心性能,只关心 bug。谢谢!
    • 是的,FindBugs 中有几个类别仅用于性能。例如,“方法分配一个装箱的原语只是为了调用 toString”、“显式垃圾收集;除了在基准代码中之外非常可疑”和“使用 keySet 迭代器而不是 entrySet 迭代器效率低下”。
    【解决方案2】:

    请确保您真正了解使用StringBuilder 的地方实际上更好。我不是说你知道,但肯定有很多人会接受这样的代码:

    String foo = "Your age is: " + getAge();
    

    并把它变成:

    StringBuilder builder = new StringBuilder("Your age is: ");
    builder.append(getAge());
    String foo = builder.toString();
    

    这只是同一事物的可读性较差的版本。通常,天真的解决方案是最佳解决方案。同样有些人担心:

    String x = "long line" + 
        "another long line";
    

    实际上,连接是在编译时执行的。

    正如 nsander 说的很对,先看看你有没有问题......

    【讨论】:

    • 正是如此。这就是 FindBugs 只检查循环的原因。
    • 不仅如此,编译器(至少是 javac)还会为您将大部分连接转换为 StringBuffer/StringBuilder.append()...
    • 当然...但它对后代来说是一个很好的说明。我主要担心存在无法在编译时优化的串联程序链(例如循环)的情况。
    【解决方案3】:

    为什么不使用分析器来查找真正重要的“朴素”字符串连接?仅在您确实需要时才切换到更详细的 StringBuffer。

    【讨论】:

    • 在大多数情况下,只需要切换到 StringBuilder。
    【解决方案4】:

    你很有可能会成为你的performance worse and your code less readable。编译器已经进行了这种优化,除非您处于循环中,否则它通常会做得更好。此外,在 JDK 8 中,它们可能会出现 StringUberBuilder,并且所有使用 StringBuilder 的代码都会运行得更慢,而“+”连接的字符串将受益于新类。

    “我们应该忘记小的效率,比如大约 97% 的时间:过早优化是万恶之源。然而,我们不应该放弃那关键的 3% 的机会。” - 唐纳德·高德纳

    【讨论】:

      【解决方案5】:

      IntelliJ 可以使用“结构搜索”找到这些。您搜索“$a + $b”并将 $a 和 $b 的特征设置为 java.lang.String 类型。

      但是,如果您有 IntelliJ,它可能具有内置检查功能,无论如何都能更好地找到您想要的东西。

      【讨论】:

        【解决方案6】:

        我建议使用分析器。这确实是一个性能问题,如果您不能让代码显示出合理的测试数据,那么更改它就没有任何价值。

        【讨论】:

          【解决方案7】:

          Jon Skeet(一如既往)和其他人已经说了所有需要的东西,但我真的想强调一下,也许您正在寻找一种不存在的性能改进......

          看看这段代码:

          public class StringBuilding {
            public static void main(String args[]) {
              String a = "The first part";
              String b = "The second part";
              String res = a+b;
          
              System.gc(); // Inserted to make it easier to see "before" and "after" below
          
              res = new StringBuilder().append(a).append(b).toString();
            }
          }
          

          如果你用javap编译和反汇编它,这就是你得到的。

          public static void main(java.lang.String[]);
            Code:
             0:   ldc     #2; //String The first part
             2:   astore_1
             3:   ldc     #3; //String The second part
             5:   astore_2
             6:   new     #4; //class java/lang/StringBuilder
             9:   dup
             10:  invokespecial   #5; //Method java/lang/StringBuilder."<init>":()V
             13:  aload_1
             14:  invokevirtual   #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
             17:  aload_2
             18:  invokevirtual   #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
             21:  invokevirtual   #7; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
             24:  astore_3
             25:  invokestatic    #8; //Method java/lang/System.gc:()V
             28:  new     #4; //class java/lang/StringBuilder
             31:  dup
             32:  invokespecial   #5; //Method java/lang/StringBuilder."<init>":()V
             35:  aload_1
             36:  invokevirtual   #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
             39:  aload_2
             40:  invokevirtual   #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
             43:  invokevirtual   #7; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
             46:  astore_3
             47:  return
          

          如您所见,6-21 与 28-43 几乎相同。没有太多优化,对吧?

          编辑:循环问题是有效的...

          【讨论】:

            【解决方案8】:

            而不是只搜索 + 搜索 "++" 那些可能会找到绝大多数。连接多个变量的情况会更加困难。

            【讨论】:

            • 而且大多数时候你会在字符串变量之间添加空格,所以这仍然可以捕捉到大多数东西。
            • 或使用正则表达式来解释可选空格。 "\"\s?+" "+\s?\""
            【解决方案9】:

            如果您拥有庞大的代码库,您可能有很多热点,这些热点可能涉及也可能不涉及“+”连接。只需运行您常用的分析器,然后修复大的,不管它们是什么类型的构造。

            仅修复一类(潜在)瓶颈而不是修复实际瓶颈将是一种奇怪的方法。

            【讨论】:

              【解决方案10】:

              使用 PMD,您可以使用 XPath 或 Java 语法编写规则。可能值得研究它是否可以匹配字符串连接运算符——它当然似乎在静态分析的范围内。这是一个模糊的想法,我要制作这个“社区维基”;如果其他人想详细说明(或按照这些思路创建自己的答案),请这样做!

              【讨论】:

                【解决方案11】:

                忘记它 - 你的 JVM 很可能已经这样做了 - 请参阅 the JLS, 15.18.1.2 Optimization of String Concatenation:

                实现可以选择在一个步骤中执行转换和连接,以避免创建然后丢弃中间 String 对象。为了提高重复字符串连接的性能,Java 编译器可以使用 StringBuffer 类或类似技术来减少通过计算表达式创建的中间 String 对象的数量。

                【讨论】:

                • 它不能循环执行此操作。即,如果您通过循环某些内容并使用 += 来构建字符串,则每次迭代都会创建一个新的 StringBuffer/Builder。
                • 好点 - JLS 中的所有内容都可能,所以我没有证据。有时间我得看看字节码。
                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2011-04-25
                • 2013-09-01
                相关资源
                最近更新 更多