【问题标题】:Is there a faster method then StringBuilder for a max 9-10 step string concatenation?对于最大 9-10 步的字符串连接,是否有比 StringBuilder 更快的方法?
【发布时间】:2011-02-23 21:32:54
【问题描述】:

我有这段代码来连接一些数组元素:

StringBuilder sb = new StringBuilder();
private RatedMessage joinMessage(int step, boolean isresult) {
        sb.delete(0, sb.length());
        RatedMessage rm;
        for (int i = 0; i <= step; i++) {
            if (mStack[i] == null)
                continue;
            rm = mStack[i].getCurrentMsg();// msg is built upfront, this just returns, it's a getter method call
            if (rm == null || rm.msg.length() == 0)
                continue;
            if (sb.length() != 0) {
                sb.append(", ");
            }
            sb.append(rm.msg);
        }
        rm.msg=sb.toString();
        return rm;
    }

重要的是,该数组最多可容纳 10 个项目,所以数量并不多。

我的跟踪输出告诉我这个方法被调用了 18864 次,16% 的运行时间都花在了这个方法上。我可以进行更多优化吗?

【问题讨论】:

  • 您的方法说它有一个 RatedMessage 的返回类型,但它似乎返回一个字符串。这是怎么回事?另外,这些 rm.msg 对象有多大,它们是字符串还是调用了隐式 toString?
  • 抱歉,我已将代码缩减为基本部分。每个味精都有一个速率、浮点数。 rm 代表额定消息。
  • @Pentium10,我觉得你剪得太多了。你有一个热点,你基本上假设某些事情不是问题,然后询问其余的事情。如果您的工作代码显示仍然很慢但更简单(如果在给出错误答案的意义上被破坏),那很好,但不要剪掉太多并期望在这里得到有意义的答案。
  • 给你,你有所有的东西。对不起。
  • 代码可能在中间做其他事情。 StringBuilder、数组访问和for循环已经有了很好的性能。你还在那里做什么?

标签: java concatenation stringbuilder


【解决方案1】:

使用来自 Apache Commons Lang 的 StringBuilder + StringUtils。 StringUtils 的意义在于使用分隔符循环遍历字符串并进行咀嚼!

private RatedMessage joinMessage(int step, boolean isresult) {
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i <= step; i++) {
            WhateverTypeIsFromMStackVariable stackVariable = mStack[i];
            String message = getMessage(stackVariable);
            if(StringUtils.isNotEmpty(message)) {
                builder.append(message).append(", ");
            }
        }
        RatedMessage rm = new RatedMessage();
        rm.msg = StringUtils.chomp(builder.toString(), ", ");
        return rm;
    }

private static String getMessage(WhateverTypeIsFromMStackVariable stackVariable) {
    if(stackVariable != null) {
        RatedMessage message = stackVariable.getCurrentMsg();
        if(message != null) {
            return message.msg;
        }
     }
     return null;
 }

Apache Commons Lang 在这里:http://commons.apache.org/lang/

【讨论】:

    【解决方案2】:

    有时没有什么可以优化的。我认为这是其中一种情况。您可以尝试减少一两条指令,但原则上不会更快。

    我认为唯一需要优化的是考虑为什么您调用它 18864 次,以及是否可以完全避免其中一些调用。也许有些不需要,或者在某些情况下您可以缓存结果。

    【讨论】:

    • 它是状态机生成过程的一部分,当状态达到停止状态时,它会生成结果。它已经从 2M 值减少了一些,只调用了 18k 次。
    【解决方案3】:

    拥有带有字符串表示的mStack 数组的单独副本,默认情况下使用空字符串初始化,因此您的循环将是:

    String [] mStackCopy = new String[]{"","","","","","","","","","",};
    // or mstackCopy = new String[mStack.length]; 
    // for( int i = 0 ; i < mStackCopy.lenght ; i++ ) { mStack[i] = "" }
    

    另外,创建具有足够容量的 StringBuilder:

    StringBuilder sb = new StringBuilder( 10000 );// 10k chars or whatever makes sense.
    

    因此,当您需要创建消息时,您只需:

    for (int i = 0; i <= step; i++) {
       sb.append( mStackCopy[i] );
    }
    

    空的部分不会造成问题,因为它们已经是空白的:

    您甚至可以对其进行硬编码:

     sb.append( mStackCopy[0]);
     sb.append( mStackCopy[1]);
     sb.append( mStackCopy[2]);
     sb.append( mStackCopy[3]);
     sb.append( mStackCopy[4]);
     sb.append( mStackCopy[5]);
     sb.append( mStackCopy[6]);
     sb.append( mStackCopy[7]);
     sb.append( mStackCopy[8]);
     sb.append( mStackCopy[9]);
    

    但这肯定会在未来带来更多的痛苦而不是缓解。

    当您向 mStack 添加内容时:

    MStack item = new MStack();
    item.setCurrentMessage("Some message");
    
     .... 
    

    只需复制邮件并附加“,”即可。

      addToMStack(int position,  MStackItem item ) {
        mStack[position] = item;
        mStackCopy[position] = item.getCurrentMessage() + ", ";
    }
    

    并且根据空值的出现(如果它很低),您可以捕获它们

      addToMStack(int position,  MStackItem item ) {
        if( item == null ) { return; }
        mStack[position] = item;
        try {
            mStackCopy[position] = item.getCurrentMessage() + ", ";
        } catch( NullPointerException npe ){}
     }
    

    这是可怕的

    或验证它:

      addToMStack(int position,  MStackItem item ) {
        if( item == null ) { return; }
        mStack[position] = item;
        mStackCopy[position] = item.getCurrentMessage() + ", ";
     }
    

    我很确定你的方法正在做一些你没有向我们展示的事情。大概原因就在那里。

    另外,如果 100% 是 1 秒,那么 16% 也不是那么糟糕。

    【讨论】:

    • 因为我在移动设备上运行它要慢得多。以上告诉 18864 步耗时 8-10 秒。
    【解决方案4】:

    16% 的运行时间在这个方法包括不包括调用的方法? getCurrentMsg() 调用可能是一个隐藏的问题,如果它创建了很多对象。

    除此之外,我建议从堆栈中取出所有需要的字符串,然后调用

    StringUtils.join(myStrings, ", ")

    使用Apache commons library。尝试依赖经过测试的代码来处理这些低级的事情,而不是每隔一天自己优化一次。最后,这将为您提供更好的优化结果,因为您将能够专注于全局(即软件的整体设计)。

    【讨论】:

    • 因为没有注意到那个电话之外的评论而打自己一巴掌。尽管如此,StringUtils 的建议仍然存在。 :)
    • 它是包容的,独占是 12% 的东西
    【解决方案5】:

    如果你的 mStack 是一个集合而不是一个数组,你可以只做mStack.toString(),它会打印一个数组的可读字符串。这可能比自己编写更容易。

    【讨论】:

      【解决方案6】:

      一些小优化...将逗号测试放在循环之外。

      private RatedMessage joinMessage(int step, boolean isresult) {
          sb.delete(0, sb.length());
          for (int i = 0; i <= step; i++) {
              if (mStack[i] == null)
                  continue;
              rm = mStack[i].getCurrentMsg();
              if (rm == null || rm.msg.length() == 0)
                  continue;
              sb.append(rm.msg).append(", ");
          }
          if (sb.length() > 2) {
              sb.delete(sb.length() - 2, 2);
          }
          return sb.toString();
      }
      

      其他建议是:

      • 确保在构造 StringBuilder 时将其初始长度设置为合适的值
      • 我不确定其余代码的上下文,但也许您可以预先确保 mStack[i] 不为空,并且 mStack[i].getCurrentMessage() 不为空或空- 这将允许您在循环之外使用更多 if 语句。

      【讨论】:

        【解决方案7】:

        一些想法:

        1) 您是否使用估计的最大容量初始化 StringBuilder?这样可以节省内部数组重新分配和复制所花费的时间。

        2) 也许您可以在循环中附加 尾随 逗号,并避免循环内的字符串长度条件。相反,在方法末尾添加一个条件,并在需要时删除尾随逗号。

        【讨论】:

          【解决方案8】:

          首先遍历堆栈中的每个元素,计算所有字符串长度的总和。

          然后就可以使用了

          sb.ensureCapacity(totalEndLength);
          

          字符串生成器的工作方式类似于数组列表,因此您可能会使用大部分追加来重建该数组。

          【讨论】:

            【解决方案9】:

            您可以进行以下更改(仅显示差异):

                String separator = "";
                for (int i = 0; i <= step; i++) {
                // ...
                    sb.append(separator).append(rm.msg);
                    separator = ", ";
                }
            

            如果额外的 if 9 次以添加一次空字符串为代价,它会被删除。在决定保留此更改之前,您应该衡量它是否对您正在使用的数据有帮助:-)

            【讨论】:

              【解决方案10】:

              如果你的函数应该连接数组元素,你为什么要传入所有这些疯狂的值和未使用的参数?

              private string joinMessage( string[] myArray)
              {
                StringBuilder sbr = new StringBuilder();
                for(int i = 0; i < myArray.Length; i++)
                {
                   if(!string.IsNullOrEmpty(myArray[i])
                   {
                     sbr.Append(myArray[i]);
                     sbr.Append(",")
                   }
                }
                return sbr.ToString();
              }
              

              【讨论】:

              • 我还有其他一些小问题,请参阅我对主要问题的评论。
              • 我并不是说您不需要返回 RatedMessage 或做类似的事情,我是说如果方法的重点是“获取字符串数组并返回所有元素连接的逗号分隔字符串”,除此之外的任何内容都毫无价值(为了讨论)
              【解决方案11】:

              首先我不会重用 StringBuilder 并且总是创建新实例。那肯定会更快,因为它允许 GC 使用年轻代堆区域。

              另一个允许消除至少一个 if 语句的小技巧是像这样重写你的代码:

                  String separator = "";
                  for (int i = 0; i <= step; i++) {
                      ...
                      sb.append(separator);
                      sb.append(rm.msg);
                      separator = ", ";
                  }
              

              【讨论】:

              • 额外赋值会比 if 语句在性能方面更好吗?还是被编译器神奇地优化掉了?
              • 在不添加赋值的情况下删除 if 块的另一种方法是将退出条件移出 for 循环标头并进入循环本身:if ( i &lt;= step ) break; sb.append(separator);
              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2015-05-17
              • 2012-02-04
              • 2018-03-14
              • 2014-03-19
              相关资源
              最近更新 更多