【问题标题】:Remove last character of a StringBuilder?删除 StringBuilder 的最后一个字符?
【发布时间】:2011-03-24 15:26:06
【问题描述】:

当你必须遍历一个集合并用分隔符分隔每个数据的字符串时,你总是会在最后得到一个额外的分隔符,例如

for (String serverId : serverIds) {
  sb.append(serverId);
   sb.append(",");
}

给出类似的东西:serverId_1, serverId_2, serverId_3,

我想删除 StringBuilder 中的最后一个字符(不转换它,因为在这个循环之后我仍然需要它)。

【问题讨论】:

  • 如果通过连接字符串你的意思是“字符串连接”,它取决于字符串的数量和它们的长度。如果您要敲打大量字符串,而不管它们的大小如何,使用字符串生成器会更有效,因为字符串是不可变的。每次将字符串连接在一起时,您都会创建一个新的结果字符串(实际上是一个 char 数组)。字符串构建器本质上是一个 char 列表,在调用 toString() 方法之前不会成为不可变字符串。
  • 如果您使用的是 Java 8,请使用 StringJoiner: stackoverflow.com/a/29169233/901641

标签: java stringbuilder


【解决方案1】:

其他人指出了deleteCharAt 方法,但这里有另一种替代方法:

String prefix = "";
for (String serverId : serverIds) {
  sb.append(prefix);
  prefix = ",";
  sb.append(serverId);
}

或者,使用Guava 中的Joiner 类:)

从 Java 8 开始,StringJoiner 是标准 JRE 的一部分。

【讨论】:

  • @Coronatus:不,因为“”是任何字符的缺席,而不是单个字符。
  • 不会执行 prefix=",";每个循环周期都会影响性能?
  • @Harish:可能是很小很小的一点——不过不太可能很重要。
  • @Harish - 如果优化器展开第一个循环迭代,可能根本不会。
  • Apache Commons 在他们的 StringUtils 中也确实有另一种 Guava 的 Joiner 替代品。 commons.apache.org/proper/commons-lang/javadocs/api-2.6/org/…, java.lang.String)
【解决方案2】:

另一个简单的解决方案是:

sb.setLength(sb.length() - 1);

更复杂的解决方案:

上述解决方案假设sb.length() > 0 ...即有一个“最后一个字符”要删除。如果您无法做出该假设,并且/或者您无法处理假设不正确时会出现的异常,请先检查 StringBuilder 的长度;例如

// Readable version
if (sb.length() > 0) {
   sb.setLength(sb.length() - 1);
}

// Concise but harder-to-read version of the above.
sb.setLength(Math.max(sb.length() - 1, 0));

【讨论】:

  • 非常好的解决方案。对性能的影响最小,所需的代码最少:)
  • @Stephen C @Alain O'Dea 无法理解这里的性能提升,它在内部进行数组复制 selLenghth() -> ensureCapacityInternal() -> Arrays.copyOf -> System.arraycopy( )
  • @ManuJose - 仔细阅读代码。如果setLength 正在减少长度,ensureCapacityInternal 将不会调用Arrays.copy。 (我正在查看 Java 11 代码,但我认为这适用于所有版本。)
【解决方案3】:
if(sb.length() > 0){
    sb.deleteCharAt(sb.length() - 1);
}

【讨论】:

  • 这得到了太多的支持,但效率不高,它做了一个 system.arraycopy。 @Rohit Reddy Korrapolu 说了什么。
  • sb.length() == 0 也是不安全的
  • 使用代理对字符是否安全?
  • 假设最后一个字符是逗号分隔符(根据示例),那么代理没有区别。如果需要泛化,则减去 separator.length() 而不是 1。
【解决方案4】:

从 Java 8 开始,String 类有一个静态方法 join。第一个参数是你想要的每对字符串之间的字符串,第二个是Iterable<CharSequence>(它们都是接口,所以像List<String> 这样的东西可以工作。所以你可以这样做:

String.join(",", serverIds);

同样在 Java 8 中,您可以使用新的 StringJoiner 类,用于在获得要放入的完整元素列表之前开始构造字符串的场景。

【讨论】:

  • nvm 为你编辑,如果你不介意的话,也删除了我的评论
  • @Eugene - 我完全重写了答案以专注于 String.join 而不是 StringJoiner
【解决方案5】:

只需获取最后一个字符出现的位置。

for(String serverId : serverIds) {
 sb.append(serverId);
 sb.append(",");
}
sb.deleteCharAt(sb.lastIndexOf(","));

由于lastIndexOf 将执行反向搜索,并且您知道它会在第一次尝试时找到,因此性能不会成为问题。

编辑

由于我一直在回答我的问题(谢谢大家?),因此值得考虑:

Java 8 以后,使用 StringJoiner 会更加清晰和明确。 它有一种用于简单分隔符的方法,以及用于前缀和后缀的重载。

示例取自这里:example

使用简单分隔符的示例:

    StringJoiner mystring = new StringJoiner("-");    

    // Joining multiple strings by using add() method  
    mystring.add("Logan");  
    mystring.add("Magneto");  
    mystring.add("Rogue");  
    mystring.add("Storm");  

    System.out.println(mystring);

输出:

Logan-Magneto-Rogue-Storm

带后缀和前缀的示例:

    StringJoiner mystring = new StringJoiner(",", "(", ")");    

    // Joining multiple strings by using add() method  
    mystring.add("Negan");  
    mystring.add("Rick");  
    mystring.add("Maggie");  
    mystring.add("Daryl");  

    System.out.println(mystring);

输出

(尼根、瑞克、玛吉、达里尔)

【讨论】:

  • 您可以确定最后一个字符是,,因为它是for loop 的最后一个语句。 lastInfexOf 更多的是为了可读性,如果您不想记住它是否为 0 索引,则更容易理解它。此外,您不需要干预 stringbuilder 的长度。只是为了方便。
【解决方案6】:

在这种情况下,

sb.setLength(sb.length() - 1);

更可取,因为它只是将最后一个值分配给'\0',而删除最后一个字符则为System.arraycopy

【讨论】:

  • setLength 调用没有为最后一个值分配任何内容。 Java 字符串缓冲区不是以空/零结尾的。事实上,setLength 只是在更新一个length 字段。
  • @Rohit Reddy Korrapolu:但是arraycopy 复制了 0 个元素,所以我想它可以被优化掉。
  • 如果 newLength 参数大于或等于当前长度,则追加足够的空字符('\u0000'),使长度成为 newLength 参数。不是这样的。
【解决方案7】:

Java-8 可以使用String 类的静态方法,

String#join(CharSequence delimiter,Iterable<? extends CharSequence> elements).


public class Test {

    public static void main(String[] args) {

        List<String> names = new ArrayList<>();
        names.add("James");
        names.add("Harry");
        names.add("Roy");
        System.out.println(String.join(",", names));
    }
}

输出

James,Harry,Roy

【讨论】:

    【解决方案8】:

    另一种选择

    for(String serverId : serverIds) {
       sb.append(",");
       sb.append(serverId); 
    }
    sb.deleteCharAt(0);
    

    【讨论】:

    • 应该比删除最后一个字符更好,因为这需要计算大小。除非删除第一个字符会导致数据移动...
    【解决方案9】:

    或者,

    StringBuilder result = new StringBuilder();
    for(String string : collection) {
        result.append(string);
        result.append(',');
    }
    return result.substring(0, result.length() - 1) ;
    

    【讨论】:

    • 可用,因为您可以添加一个“。”最后。
    【解决方案10】:
    StringBuilder sb = new StringBuilder();
    sb.append("abcdef");
    sb.deleteCharAt(sb.length() - 1);
    assertEquals("abcde",sb.toString());
    // true
    

    【讨论】:

      【解决方案11】:

      另一种选择:

      public String join(Collection<String> collection, String seperator) {
          if (collection.isEmpty()) return "";
      
          Iterator<String> iter = collection.iterator();
          StringBuilder sb = new StringBuilder(iter.next());
          while (iter.hasNext()) {
              sb.append(seperator);
              sb.append(iter.next());
          }
      
          return sb.toString();
      }
      

      【讨论】:

        【解决方案12】:

        为避免prefix 重新初始化(影响性能),请使用 TextUtils.isEmpty:

                    String prefix = "";
                    for (String item : list) {
                        sb.append(prefix);
                        if (TextUtils.isEmpty(prefix))
                            prefix = ",";
                        sb.append(item);
                    }
        

        【讨论】:

        • TestUtils属于什么样的包?
        • @Markus android.text.TextUtils
        【解决方案13】:

        您可以尝试使用“Joiner”类,而不是从生成的文本中删除最后一个字符;

                        List<String> textList = new ArrayList<>();
                        textList.add("text1");
                        textList.add("text2");
                        textList.add("text3");
        
                        Joiner joiner = Joiner.on(",").useForNull("null");
                        String output = joiner.join(textList);
        
                       //output : "text1,text2,text3"
        

        【讨论】:

          【解决方案14】:

          我正在做如下的事情:

              StringBuilder stringBuilder = new StringBuilder();
              for (int i = 0; i < value.length; i++) {
                  stringBuilder.append(values[i]);
                  if (value.length-1) {
                      stringBuilder.append(", ");
                  }
              }
          

          【讨论】:

            【解决方案15】:

            这是另一个解决方案:

            for(String serverId : serverIds) {
               sb.append(",");
               sb.append(serverId); 
            }
            
            String resultingString = "";
            if ( sb.length() > 1 ) {
                resultingString = sb.substring(1);
            }
            

            【讨论】:

            • 但无论如何,这只是 Zaki 2010 年解决方案的一个小变种。
            【解决方案16】:

            stringBuilder.Remove(stringBuilder.Length - 1, 1);

            【讨论】:

            • 没有remove 方法。而Length(或length)不是字段。这是一种方法。
            【解决方案17】:

            我发现自己经常这样做,所以我为 3 种主要的附加分隔符技术编写了一个基准:

            (具有适当预热和 100 轮 100,000 次迭代的基准)

            “后追加”

            static void appendAfter()
            {
                sb.append('{');
                for (int i = 0; i < 10; i++)
                {
                    sb.append('"');
                    sb.append(i);
                    sb.append('"');
                    sb.append(':');
                    sb.append(i);
                    sb.append(',');
                }
                sb.setLength(sb.length() - 1);
                sb.append('}');
            }
            

            “附加在前面”

            static void appendBefore()
            {
                sb.append('{');
                String delimiter = "";
                for (int i = 0; i < 10; i++)
                {
                    sb.append(delimiter);
                    sb.append('"');
                    sb.append(i);
                    sb.append('"');
                    sb.append(':');
                    sb.append(i);
                    delimiter = ",";
                }
                sb.append('}');
            }
            

            “追加可能”

            static void appendMaybe()
            {
                sb.append('{');
                for (int i = 0; i < 10; i++)
                {
                    sb.append('"');
                    sb.append(i);
                    sb.append('"');
                    sb.append(':');
                    sb.append(i);
                    if (i < 9)
                    {
                        sb.append(',');
                    }
                }
                sb.append('}');
            }
            

            我得到了以下结果:

            Platform Append After Append Before Append Maybe
            Windows Server 2016, Java 11 - Hotspot 26ms 40ms 26ms
            Windows Server 2016, Java 8 - Hotspot 27ms 36ms 21ms
            Windows Server 2016, Java 11 - OpenJ9 63ms 81ms 59ms
            Windows Server 2016, Java 8 - OpenJ9 66ms 64ms 55ms

            除了最快之外,我认为“Append Maybe”实现最好地显示了代码的意图。这通常比每次迭代获得的纳秒分数更重要。

            我留下了基准代码here,以防有人想在他们的平台上试用它。如果你这样做,请在上面贡献你的结果!

            【讨论】:

            • 您是否尝试删除最后一个字符。对我来说,它看起来更高效,因为删除最后一个字符可以在 for 循环之外完成
            猜你喜欢
            • 1970-01-01
            • 2011-04-26
            • 1970-01-01
            • 2011-12-15
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多