【问题标题】:StringBuilder vs. String considering replaceStringBuilder 与 String 考虑替换
【发布时间】:2011-06-25 07:55:24
【问题描述】:

在连接大量字符串时,建议我使用StringBuilder 这样做:

StringBuilder someString = new StringBuilder("abc");
someString.append("def");
someString.append("123");
someString.append("moreStuff");

相对于

String someString = "abc";
someString = someString + "def";
someString = someString + "123";
someString = someString + "moreStuff";

这将导致创建相当多的字符串,而不是一个。

现在,我需要做类似的事情,但我不使用串联,而是使用 String 的 replace 方法:

String someString = SOME_LARGE_STRING_CONSTANT;
someString = someString.replace("$VARIABLE1", "abc");
someString = someString.replace("$VARIABLE2", "def");
someString = someString.replace("$VARIABLE3", "123");
someString = someString.replace("$VARIABLE4", "moreStuff");

要使用 StringBuilder 完成同样的事情,我必须这样做,只是为了一次替换:

someString.replace(someString.indexOf("$VARIABLE1"), someString.indexOf("$VARIABLE1")+10, "abc");

所以我的问题是:“是使用 String.replace 并创建许多额外的字符串更好,还是继续使用 StringBuilder 并有很多像上面那样的冗长的行?”

【问题讨论】:

  • 如果产生性能问题,请更改它。如果没有更重要的更改要执行,请更改它。如果输入真的很大并且经常使用,请更改它。顺便说一句,第二种方法不起作用,因为它只会替换它一次,你必须把它放在一个 while 循环中。看看我的回答。

标签: java string replace stringbuilder


【解决方案1】:

可能是String类内部使用

索引

找到旧字符串的索引并用新字符串替换它的方法。

而且 StringBuilder 也不是线程安全的,所以它执行得更快。

【讨论】:

    【解决方案2】:

    StringBuilder 确实比手动连接或修改字符串要好,因为 StringBuilder 是可变的,而 String 是不可变的,每次修改都需要创建一个新的字符串。

    请注意,Java 编译器会自动转换如下示例:

    String result = someString + someOtherString + anotherString;
    

    变成类似:

    String result = new StringBuilder().append(someString).append(someOtherString).append(anotherString).toString();
    

    也就是说,除非您要替换大量字符串,否则选择更具可读性和可维护性的字符串。因此,如果您可以通过一系列“替换”调用来保持它的清洁度,继续并通过 StringBuilder 方法执行此操作。与处理the sad tragedy of micro-optimizations 所节省的压力相比,这种差异可以忽略不计。

    PS

    对于您的代码示例(正如 OscarRyz 指出的那样,如果您在 someString 中有多个 "$VARIABLE1" 将无法工作,在这种情况下您需要使用循环),您可以缓存indexOf 调用的结果:

    someString.replace(someString.indexOf("$VARIABLE1"), someString.indexOf("$VARIABLE1")+10, "abc");
    

    int index = someString.indexOf("$VARIABLE1");    
    someString.replace(index, index+10, "abc");
    

    无需搜索字符串两次 :-)

    【讨论】:

    • 如果您在输入中有两个 $VARIABLE1(它只替换第一个。您必须将其放在 while 循环中。
    • @OscarRyz 哦!真的。甚至没有注意到:-p。我编写该代码示例的主要原因只是为了向提问者指出他们不应该调用 indexOf 两次。
    • 顺便说一句,这是一个好点。我在一个样本中尝试了同样的方法,直到那时我才意识到。很容易忘记。看我的回答
    • @OscarRyz @ZachL : 示例:builder = new StringBuilder(120);builder.append(a()).append(b()).append(c()); 嗨,如果我们知道最终大小,比如输出字符串将是 120,那么上面的代码不会比自动生成的代码更好编译过程。请注意,方法 a、b、c 不返回静态硬编码字符串,因此编译器无法知道使用什么值初始化构建器。
    【解决方案3】:

    您可以编写一个替换部分 StringBuilder 字符串的方法,而不是那样长的行,类似于以下内容:

    public StringBuilder replace(StringBuilder someString, String replaceWhat, String replaceWith) {
       return someString.replace(someString.indexOf(replaceWhat), someString.indexOf(replaceWhat)+replaceWhat.length(), replaceWith);
    }
    

    【讨论】:

    • 我也是这么想的,但这需要一个while循环来替换它们,而不仅仅是第一次出现。
    【解决方案4】:

    如果您的字符串确实很大并且您担心性能,我建议您编写一个类,该类接受您的模板文本和变量列表,然后逐个字符读取源字符串并使用 StringBuilder 构建结果。就 CPU 和内存使用而言,这应该是最有效的。此外,如果您从文件中读取此模板文本,我不会预先将其全部加载到内存中。当你从文件中读取它时,分块处理它。

    如果您只是在寻找一种构建字符串的好方法,它不如 StringBuilder 高效,但比一遍又一遍地追加字符串更有效,您可以使用 String.format()。它的工作方式类似于 C 中的 sprintf()。MessageFormat.format() 也是一个选项,但它使用 StringBuffer。

    这里还有一个相关问题:Inserting a Java string in another string without concatenation?

    【讨论】:

      【解决方案5】:

      你猜怎么着?如果您使用 Java 1.5+ 运行,则连接对字符串文字的作用相同

        String h = "hello" + "world";
      

        String i = new StringBuilder().append("hello").append("world").toString();
      

      都是一样的。

      所以,编译器已经为您完成了工作。

      当然更好:

       String j = "hellworld"; // ;) 
      

      至于第二个,是的,这是首选,但不应该那么难,具有“搜索和替换”的强大功能和一些正则表达式 foo

      例如,您可以定义一个类似本示例中的方法:

        public static void replace( String target, String replacement, 
                                    StringBuilder builder ) { 
          int indexOfTarget = -1;
          while( ( indexOfTarget = builder.indexOf( target ) ) >= 0 ) { 
            builder.replace( indexOfTarget, indexOfTarget + target.length() , replacement );
          }
        }
      

      你的代码目前看起来像这样:

      someString = someString.replace("VARIABLE1", "abc");
      someString = someString.replace("VARIABLE2", "xyz");
      

      你所要做的就是抓住文本编辑器一个触发器,比如 vi 搜索和替换:

      %s/^.*("\(.*\)".\s"\(.*\)");/replace("\1","\2",builder);
      

      上面写着:“将任何看起来像字符串文字的括号放在括号中,并将其放入另一个字符串中”

      您的代码将从这里查看:

      someString = someString.replace("VARIABLE1", "abc");
      someString = someString.replace("VARIABLE2", "xyz");
      

      到这里:

      replace( "VARIABLE1", "abc", builder );
      replace( "VARIABLE2", "xyz", builder );
      

      很快。

      这是一个工作演示:

      class DoReplace { 
        public static void main( String ... args ) {
          StringBuilder builder = new StringBuilder(
             "LONG CONSTANT WITH VARIABLE1 and  VARIABLE2 and VARIABLE1 and VARIABLE2");
          replace( "VARIABLE1", "abc", builder );
          replace( "VARIABLE2", "xyz", builder );
          System.out.println( builder.toString() );
        }
        public static void replace( String target, String replacement, 
                                    StringBuilder builder ) { 
          int indexOfTarget = -1;
          while( ( indexOfTarget = builder.indexOf( target ) ) > 0 ) { 
            builder.replace( indexOfTarget, indexOfTarget + target.length() , 
                             replacement );
          }
        }
      }
      

      【讨论】:

      • p.s 如果目标实例从位置 0 开始会发生什么???您的 while 循环退出条件表明它不会替换它。
      • 实际上,在您的第一个示例中,编译器实际上并没有生成 StringBuilder 解决方案,而是您的第三行代码,因为它是一个编译时间常数。所以这些是等价的。
      【解决方案6】:

      我会说使用 StringBuilder,但只需编写一个包装器,它有助于使代码更具可读性并因此更易于维护,同时仍保持效率。 =D

      import java.lang.StringBuilder;
      public class MyStringBuilder
      {
          StringBuilder sb;
      
          public MyStringBuilder() 
          {
             sb = new StringBuilder();
          }
      
          public void replace(String oldStr, String newStr)
          {
                  int start = -1;
                  while ((start = sb.indexOf(oldStr)) > -1)
                  {
                          int end = start + oldStr.length(); 
                          sb.replace(start, end, newStr);
                  }
          }
      
          public void append(String str)
          {
             sb.append(str);
          }
      
          public String toString()
          {
                return sb.toString();
          }
      
          //.... other exposed methods
      
          public static void main(String[] args)
          {
                MyStringBuilder sb = new MyStringBuilder();
                sb.append("old old olD dudely dowrite == pwn");
                sb.replace("old", "new");
                System.out.println(sb);
          }
      }
      

      输出:

      new new olD dudely dowrite == pwn
      

      现在您可以使用简单的新版本了

      MyStringBuilder mySB = new MyStringBuilder();
      mySB.append("old dudley dowrite == pwn");
      mySB.replace("old", "new"):
      

      【讨论】:

      • 输入"old old dudley"失败
      • @OscarRyz 很有趣。我在一个项目中有它,输出正是预期的。请记住,我没有添加 append(String) 或 String toString() 方法。为了清楚起见,我决定添加它们
      • 可能您永远不会有超过 1 个要替换的字符串。试试"old old dudley",你会得到"new old dudley"
      • @OscarRyz 哈哈,我很抱歉。我忘记了它需要替换旧字符串的所有实例。已更正。
      【解决方案7】:

      所有人的代码都有一个错误。试试yourReplace("x","xy")。它会无限循环

      【讨论】:

      【解决方案8】:

      Jam Hong 是正确的 - 上述解决方案都包含无限循环的可能性。我想这里要吸取的教训是,微优化通常会导致各种可怕的问题,并不能真正为您节省太多。不过,尽管如此 - 这是一个不会无限循环的解决方案。

      private static void replaceAll(StringBuilder builder, String replaceWhat, String replaceWith){
          int occuranceIndex = builder.indexOf(replaceWhat);
          int lastReplace = -1;
          while(occuranceIndex >= 0){
              if(occuranceIndex >= lastReplace){
                  builder.replace(occuranceIndex, occuranceIndex+replaceWhat.length(), replaceWith);
                  lastReplace = occuranceIndex + replaceWith.length();
                  occuranceIndex = builder.indexOf(replaceWhat);
              }else{
                  break;
              }
          }
      }
      

      【讨论】:

      • 这就像 StringBuilder 的 replaceFirst builder = new StringBuilder("Var x and x and x and x"); replaceAll(builder, "x", "xy");
      【解决方案9】:

      虽然微优化确实存在问题,但有时取决于上下文,例如,如果您的替换恰好在具有 10000 次迭代的循环内运行,您将看到与“无用”优化的显着性能差异.

      然而,在大多数情况下,最好在可读性方面犯错

      【讨论】:

        猜你喜欢
        • 2020-06-01
        • 1970-01-01
        • 2021-07-21
        • 1970-01-01
        • 1970-01-01
        • 2013-08-14
        • 2022-06-10
        • 1970-01-01
        • 2021-12-22
        相关资源
        最近更新 更多