删除 StringBuilder 的最后一个字符?

Remove last character of a StringBuilder?

提问人:Matthew 提问时间:8/3/2010 最后编辑:Jared BurrowsMatthew 更新时间:11/18/2023 访问量:456504

问:

当您必须遍历一个集合并用分隔符分隔每个数据的字符串时,您总是会在末尾使用一个额外的分隔符,例如

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

给出如下内容:serverId_1、serverId_2、serverId_3、

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

Java 字符串生成器

评论

11赞 dyslexicanaboko 7/23/2012
如果通过连接字符串,您的意思是“字符串连接”,则取决于字符串的数量及其长度。如果您要敲击大量字符串,无论它们的大小如何,使用字符串生成器会更有效,因为字符串是不可变的。每次将字符串连接在一起时,您都会创建一个新的结果字符串(实际上是一个 char 数组)。字符串生成器本质上是一个字符列表,在调用 toString() 方法之前,它不会成为不可变字符串。
7赞 ArtOfWarfare 7/11/2015
如果您使用的是 Java 8,只需使用 : stackoverflow.com/a/29169233/901641StringJoiner

答:

222赞 bragboy 8/3/2010 #1
if(sb.length() > 0){
    sb.deleteCharAt(sb.length() - 1);
}

评论

45赞 alianos- 9/19/2012
这被点赞太多了,但它效率不高,它做了一个 system.arraycopy。雷迪·科拉波卢(Reddy Korrapolu)@Rohit说了什么。
0赞 rogerdpack 3/19/2015
这在代理对角色的情况下安全吗?
1赞 Stephen C 10/31/2017
假设最后一个字符是逗号分隔符(如示例所示),则代理项没有区别。如果你需要概括,那就减去而不是 1。separator.length()
547赞 Stephen C 8/3/2010 #2

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

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

更复杂的解决方案:

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

// 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));

评论

33赞 Alain O'Dea 9/6/2012
非常好的解决方案。对性能的影响最小,所需的代码最少:)
1赞 Manu Jose 10/20/2020
@Stephen C @ Alain O'Dea 无法理解这里的性能提升,它在内部做数组复制 selLenghth() -> ensureCapacityInternal() -> Arrays.copyOf -> System.arraycopy()
2赞 Stephen C 10/20/2020
@ManuJose - 仔细阅读代码。如果正在缩短长度,则不会调用。(我正在查看 Java 11 代码,但我认为这适用于所有版本。setLengthensureCapacityInternalArrays.copy
6赞 Antoine 8/3/2010 #3
StringBuilder sb = new StringBuilder();
sb.append("abcdef");
sb.deleteCharAt(sb.length() - 1);
assertEquals("abcde",sb.toString());
// true
741赞 Jon Skeet 8/3/2010 #4

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

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

或者,使用 Guava :) 中的 Joiner

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

评论

9赞 Jon Skeet 8/28/2010
@Coronatus:不,因为“”是没有任何字符,而不是一个字符。
42赞 Harish 10/24/2011
不会执行 prefix=“,”;每个循环周期都会影响性能?
25赞 Jon Skeet 10/24/2011
@Harish:可能,很小,很小 - 虽然不太可能很重要。
7赞 Stephen C 8/27/2012
@Harish - 如果优化器展开第一次循环迭代,可能根本不会。
7赞 GoRoS 9/29/2013
Apache Commons 在他们的 .commons.apache.org/proper/commons-lang/javadocs/api-2.6/org/..., java.lang.String)JoinerStringUtils
9赞 Zaki 8/3/2010 #5

或者

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

评论

0赞 anthonyms 10/26/2011
可用,因为您可以在末尾添加“.”。
5赞 Jason Day 8/3/2010 #6

另一种选择:

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();
}
10赞 Rafiq 12/26/2011 #7

另一种选择

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

评论

3赞 slott 7/17/2013
应该比删除最后一个字符更好,因为这需要大小计算。除非删除第一个字符会导致数据四处移动......
40赞 Rohit Reddy Korrapolu 4/13/2012 #8

在本例中,

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

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

评论

1赞 Stephen C 9/19/2012
调用不会为最后一个值分配任何内容。Java 字符串缓冲区不是以空/零结尾的。实际上,只是更新一个字段。setLengthsetLengthlength
0赞 maaartinus 11/3/2013
@Rohit Reddy Korrapolu:但是复制了 0 个元素,所以我想它可以被优化掉。arraycopy
2赞 fglez 1/10/2014
如果 newLength 参数大于或等于当前长度,则追加足够的 null 字符 ('\u0000'),以便长度成为 newLength 参数。事实并非如此。
68赞 ArtOfWarfare 3/20/2015 #9

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

String.join(",", serverIds);

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

评论

0赞 Eugene 5/18/2018
如果您不介意,NVM会为您编辑,也删除了我的评论
0赞 ArtOfWarfare 5/18/2018
@Eugene - 我完全重写了答案,以专注于而不是.String.joinStringJoiner
1赞 Vikasdeep Singh 4/6/2015 #10

我正在做如下事情:

    StringBuilder stringBuilder = new StringBuilder();
    for (int i = 0; i < value.length; i++) {
        stringBuilder.append(values[i]);
        if (value.length-1) {
            stringBuilder.append(", ");
        }
    }
49赞 Reuel Ribeiro 10/13/2015 #11

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

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

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

编辑

由于我一直在回答我的答案(谢谢大家😊),因此值得一提的是:

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

取自此处的示例: 示例

使用简单分隔符的示例:

    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);

输出:

洛根-磁-流氓-风暴

带有后缀和前缀的示例:

    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);

输出

(尼根,瑞克,玛姬,达里尔)

评论

1赞 Reuel Ribeiro 10/31/2017
您将确定最后一个字符是 a,因为它是 的最后一个语句。这更多的是为了可读性,如果你不想记住它是否是 0 索引,那就不费吹灰之力了。此外,您无需干预字符串生成器的长度。只是为了方便起见。,for looplastInfexOf
12赞 akash 12/27/2015 #12

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

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
3赞 NickUnuchek 2/26/2016 #13

为了避免使用 TextUtils.isEmpty 的 reinit(影响性能):prefix

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

评论

0赞 Markus 6/17/2016
TestUtils 属于哪种类型的软件包?
0赞 NickUnuchek 6/17/2016
@Markus android.text.TextUtils
1赞 oguzhan 7/1/2016 #14

您可以尝试使用“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"
0赞 Stephan 12/22/2016 #15

这是另一种解决方案:

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

String resultingString = "";
if ( sb.length() > 1 ) {
    resultingString = sb.substring(1);
}

评论

0赞 Stephen C 10/31/2017
但无论如何,这只是 Zaki 2010 年解决方案的一个小变体。
0赞 Mohamed Farook Mohamed Fazrin 2/10/2020 #16

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

评论

0赞 Stephen C 4/9/2021
没有方法。并且 (或 ) 不是字段。这是一种方法。removeLengthlength
0赞 TheKodeToad 7/16/2022
我认为这可能是C#
1赞 egerardus 4/5/2021 #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('}');
}

我得到了以下结果:

平台 追加后 追加前 追加也许
Windows Server 2016、Java 11 - 热点 26毫秒 40毫秒 26毫秒
Windows Server 2016、Java 8 - 热点 27毫秒 36毫秒 21毫秒
Windows Server 2016,Java 11 - OpenJ9 63毫秒 81毫秒 59毫秒
Windows Server 2016,Java 8 - OpenJ9 66毫秒 64毫秒 55毫秒

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

我把基准测试代码留在这里,以防有人想在他们的平台上尝试。如果您这样做,请在上面提供您的结果!

评论

0赞 canbax 8/13/2021
您是否尝试删除最后一个字符。对我来说,它看起来性能更高,因为删除最后一个字符可以在循环之外完成for
0赞 Bernard Agbemadon 11/15/2023 #18

如果您不想使用 StringJoiner,这里有对 @superpupervlad 技术的一个小改进(对于大型列表有很好的增益),它减少了赋值的数量。

char prefix = '\0'; // same as ""
for (String serverId : serverIds) {
  sb.append(prefix);
  if (prefix == '\0') prefix = ',';
  sb.append(serverId);
}
0赞 NickD 11/18/2023 #19

一个更简单、更紧凑的解决方案只需要对代码进行少量修改。

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