使用循环创建StringBuilder的最佳实践

3

我想知道使用StringBuilder的代码是否最高效,还是它仍然通过将“”与当前项连接来创建大量的临时字符串?

如果是这样,您能否提供更好的代码建议?

public String toString() {
    StringBuilder out = new StringBuilder();
    for (long item : someListOfNumbers) {
        out.append( " " + item);
    }
    return out.toString();
}

13
out.append(' ').append(item); 这样吗? - Usagi Miyamoto
2
这将导致一个前导空格,我不知道是否需要。 - deHaar
4个回答

4

对于这种特定的用例,您可能希望使用StringJoiner, 除非您的目标是在结果中有一个前导空格。

public String toString() {
    StringJoiner joiner = new StringJoiner(" ");
    someListOfNumbers.forEach(l -> joiner.add(Long.toString(l)));
    return joiner.toString();
}

或者使用Collectors.joining()

public String toString() {
    returns someListOfNumbers.stream()
        .map(String::valueOf)
        .collect(Collectors.joining(" "));
}

StringBuilder在构建文本时更加高效,但有人会认为上面的方法更可读。当使用StringBuilder时,应避免使用+运算符,而是使用像append()这样的方法。

如果您查看此处此处或其他答案,您将看到String连接还被javac编译器进行了优化,例如,如果常量与+操作符连接,则编译器可以将结果常量插入字节码中。


2
如果someListOfNumbers实际上是数字列表,则joiner::add无法工作。 - Andy Turner
1
StringJoiner需要你先把所有的数字转换成中间字符串,而StringBuilder可以直接将数字输出到其内部缓冲区,所以我会使用StringBuilder(反正这也不会使代码更短)。 - Thilo

3
out.append( " " + item);

在上面的语句中,这里似乎存在一个问题" " + item,因为它将字符串与长整数组合在一起。由于字符串是不可变的,所以对于字符串的+运算符会在内存中创建一个新的字符串副本。最优解是使用:
out.append(' ').append(item);

甚至可以

out.append(" ").append(item);

在以上两种情况下,由于StringBuilder是可变的,因此不需要额外的内存空间来保存新组合的字符串。因此它更为优化。

2
或许您也可以预估 StringBuilder 的容量,提前设定一些大小(例如 someListOfNumbers.size() * 4),以避免重复调整其大小。 - Thilo
没错,但只有在我们确切地知道结果字符串中字符的长度时才可能实现,否则可能会引发异常。 - Asad Ali Choudhry
2
@AsadChoudhary 不会引起异常,它只会调整大小。 - Andy Turner
如果字符长度超过了给定的大小,那么我是指数组越界异常。难道不是吗? - Asad Ali Choudhry
2
@AsadChoudhary 不会,它只会改变大小。 - Andy Turner

1
一种现代的方法是将字符串构建器的操作留给库方法:
public String toString() {
    return someListOfNumbers.stream()
            .map(Number::toString)
            .collect(Collectors.joining(" "));
}

我假设someListOfNumbers是一个List<Long>。我的方法与你的方法给出的结果不完全相同:你的结果有一个前导空格,我已经省略了它。如果你需要这个空格,你可以自己添加或使用以下方法:

            .collect(Collectors.joining(" ", " ", ""));

使用三个参数的 joining 方法,需要按照顺序提供分隔符、前缀和后缀(在此处我将空字符串作为后缀)。

附注:我原本想使用 .map(Long::toString),但它无法编译,因为存在歧义。在这里,toString 可能会引用无参的 Long.toString 或静态的单参 Long.toString(long)。我通过使用 Number::toString 来引用其超类来解决了这个问题。另一种解决方案是使用 Karol Dowbecki 的答案中的 .map(String::valueOf)


0

为了去掉末尾或开头的零,您必须检查循环中当前处理的项的索引。

以下是三个示例,构建一个没有任何要修剪的String

public String toString() {
    StringBuilder out = new StringBuilder();
    // iterate in a classic for-loop checking for item at index 0
    for (int i = 0; i < someListOfNumbers.size(); i++) {
        long item = someListOfNumbers.get(i);
        if (i == 0) {
            // in this case, avoid a leading zero
            out.append(item);
        } else {
            out.append(" ").append(item);
        }
    }
    return out.toString();
}

public String toString() {
    StringBuilder out = new StringBuilder();
    // iterate in a classic for-loop checking for item at last index
    for (int i = 0; i < someListOfNumbers.size(); i++) {
        long item = someListOfNumbers.get(i);
        if (i == someListOfNumbers.size() - 1) {
            out.append(item).append(" ");
        } else {
            // this case avoids a trailing zero
            out.append(item);
        }
    }
    return out.toString();
}

public String toString() {
    StringBuilder out = new StringBuilder();
    // add the first element and iterate from 1 to end afterwards 
    // (this requires a check for an empty list)
    out.append(someListOfNumbers.get(0));
    for (int i = 1; i < someListOfNumbers.size(); i++) {
        out.append(" ").append(someListOfNumbers.get(i));
    }
    return out.toString();
}

你甚至可以使用自己的方法,但是使用增强型for循环,你必须使用indexOf然后执行检查。我不想这样做,但这是你的决定。


1
我更喜欢三个例子中的第三个。在for循环内部有一个特殊情况是一种反模式,而且在这种情况下很容易避免。 - Ole V.V.
@OleV.V.当然,无论有多“大”,反模式都应该在任何情况下都要避免;-) 但在这种情况下,我认为重点是正确工作的代码,而不是代码质量。 - deHaar
我同意反模式的观点,也许在这里我用了一个强烈的词。但是我不太认同它的定义:对我来说,它始终与代码质量有关。感谢您的评论。 - Ole V.V.
@OleV.V. 我不怀疑对你来说现在总是关于代码质量,这很好。但是难道没有过一段时间你不知道代码质量,只是想学习基础吗?我无法确定OP想要什么,但似乎主要不是代码质量,而是满足某些功能要求的代码。 - deHaar
这可能是一个漫长而有趣的讨论,但在这里没有足够的空间。当我教初学者编程时,我几乎从第一天开始告诉他们,编程是关于编写“下一个程序员”可以阅读、理解和维护的代码。 - Ole V.V.
1
@OleV.V 那绝对是教授编程的正确方式。这对你的学生有好处,我曾经遇到过几位持不同观点的老师... - deHaar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接