连接字符串对象列表的最佳方法? [复制]

Posted

技术标签:

【中文标题】连接字符串对象列表的最佳方法? [复制]【英文标题】:Best way to concatenate List of String objects? [duplicate] 【发布时间】:2010-10-06 03:35:50 【问题描述】:

连接 String 对象列表的最佳方法是什么?我正在考虑这样做:

List<String> sList = new ArrayList<String>();

// add elements

if (sList != null)

    String listString = sList.toString();
    listString = listString.subString(1, listString.length() - 1);

不知何故,我发现这比使用 StringBuilder/StringBuffer 方法更简洁。

有什么想法/cmets?

【问题讨论】:

【参考方案1】:

使用 Apache Commons Lang 中的 StringUtils.join 方法之一。

import org.apache.commons.lang3.StringUtils;

String result = StringUtils.join(list, ", ");

如果您有幸使用 Java 8,那就更简单了...只需使用 String.join

String result = String.join(", ", list);

【讨论】:

Guava 用户的备选方案:Joiner.on(', ').join(Collection) 是的,每次:Bloch,“Effective Java”,第 47 项:“了解和使用库”。 Apache Commons 库应该是放入构建文件的第一件事(希望是 Gradle)。正如第 47 条总结的那样:“总而言之,不要重新发明***”。 使用 Guava API:Joiner.on(', ').skipNulls().join(Iterable&lt;?&gt;)。要表示空值,您可以使用 Joiner.on(',').useForNull("#").join(Iterable&lt;?&gt;) @Gojir4 也许你应该在使用类似语言之前检查 java 版本 >8,请参阅副本 从 Java 8 开始,最好使用 String#join(不需要库)。【参考方案2】:

使用 Java 8+

String str = list.stream().collect(Collectors.joining())

甚至

String str = String.join("", list);

【讨论】:

【参考方案3】:

您的方法依赖于 Java 的 ArrayList#toString() 实现。

虽然在 Java API 中记录了实现并且不太可能改变,但它有可能改变。自己实现这一点要可靠得多(循环、StringBuilders、递归任何你喜欢的东西)。

当然,这种方法可能看起来“更整洁”或更“太甜”或“钱”,但在我看来,这是一种更糟糕的方法。

【讨论】:

我同意我不喜欢 OP 的做法,但出于不同的原因。 (1) 这样做会掩盖代码的意图(而像StringUtils.join 这样的东西是完全可读的)。 (2) 它不灵活,因为您可能想要更改分隔符。 (只是在最后一个字符串中替换", " 不是一个好方法,因为原始字符串中可能嵌入了", "。)所以即使,正如你所说,ArrayList.toString 可能不会改变,我仍然不会走这条路。 您可以 99.9% 确定 ArrayList.toString 不会改变,除非它以某种方式成为攻击向量;向后兼容性将确保这一点。但我同意使用 toString() 仍然是个坏主意。【参考方案4】:

codefin 答案的变体

public static String concatStringsWSep(Iterable<String> strings, String separator) 
    StringBuilder sb = new StringBuilder();
    String sep = "";
    for(String s: strings) 
        sb.append(sep).append(s);
        sep = separator;
    
    return sb.toString();                           

【讨论】:

@codefine 见***.com/questions/58431/… 我真的很喜欢这个答案 b/c 你可以使用 foreach ,它非常简单,但效率也更低。你怎样才能使它更紧密的循环? 字符串不需要使用List。收藏会更灵活 @lilalinux 改为 Iterable。 在第一轮迭代后替换 sep 的非常巧妙的技巧。【参考方案5】:

如果你是为android开发,SDK提供了TextUtils.join

【讨论】:

【参考方案6】:

这是迄今为止我发现的最优雅、最干净的方式:

list.stream().collect(Collectors.joining(delimiter));

【讨论】:

IntelliJ 给了我一个提示,我可以通过将此解决方案从其他答案更改为 String.join 示例来改进事情,理由如下“报告流 API 调用链可以简化。它允许避免遍历集合时创建冗余临时对象。”【参考方案7】:

Guava 是来自 Google 的一个非常简洁的库:

Joiner joiner = Joiner.on(", ");
joiner.join(sList);

【讨论】:

【参考方案8】:

我更喜欢 Java 8 中的 String.join(list)

【讨论】:

【参考方案9】:

你看过这个 Coding Horror 博客文章吗?

The Sad Tragedy of Micro-Optimization Theater

我不确定它是否“更整洁”,但从性能的角度来看,它可能并不重要。

【讨论】:

【参考方案10】:

在我看来,StringBuilder 会快速高效。

基本形式如下所示:

public static String concatStrings(List<String> strings)

    StringBuilder sb = new StringBuilder();
    for(String s: strings)
    
        sb.append(s);
    
    return sb.toString();       

如果这太简单了(很可能是这样),您可以使用类似的方法并添加如下分隔符:

    public static String concatStringsWSep(List<String> strings, String separator)

    StringBuilder sb = new StringBuilder();
    for(int i = 0; i < strings.size(); i++)
    
        sb.append(strings.get(i));
        if(i < strings.size() - 1)
            sb.append(separator);
    
    return sb.toString();               

我同意其他回答这个问题的人的观点,他们说你不应该依赖 Java ArrayList 的 toString() 方法。

【讨论】:

【参考方案11】:

ArrayListAbstractCollection继承其toString()-方法,即:

public String toString() 
    Iterator<E> i = iterator();
    if (! i.hasNext())
        return "[]";

    StringBuilder sb = new StringBuilder();
    sb.append('[');
    for (;;) 
        E e = i.next();
        sb.append(e == this ? "(this Collection)" : e);
        if (! i.hasNext())
            return sb.append(']').toString();
        sb.append(", ");
    

自己构建字符串会更有效率。


如果您真的想预先将字符串聚合到某种列表中,您应该提供自己的方法来有效地加入它们,例如像这样:

static String join(Collection<?> items, String sep) 
    if(items.size() == 0)
        return "";

    String[] strings = new String[items.size()];
    int length = sep.length() * (items.size() - 1);

    int idx = 0;
    for(Object item : items) 
        String str = item.toString();
        strings[idx++] = str;
        length += str.length();
    

    char[] chars = new char[length];
    int pos = 0;

    for(String str : strings) 
        str.getChars(0, str.length(), chars, pos);
        pos += str.length();

        if(pos < length) 
            sep.getChars(0, sep.length(), chars, pos);
            pos += sep.length();
        
    

    return new String(chars);

【讨论】:

我希望你做了基准测试来确认你的实现更好,因为它并不比 AbstractCollection 的 toString 实现更干净。读者必须真正专注于您的实施才能理解某些内容,而其他内容则非常容易阅读。【参考方案12】:

不知何故,我发现这比 使用 StringBuilder/StringBuffer 接近。

我想这取决于您采取的方法。

AbstractCollection#toString() 方法简单地遍历所有元素并将它们附加到 StringBuilder。因此,您的方法可能会节省几行代码,但代价是额外的字符串操作。这种权衡是否合适取决于您。

【讨论】:

您所说的额外字符串操作是在末尾创建子字符串,对吧? @Jagmal,是的。如果这段代码运行很多,那么你应该分析它是否对你有问题。否则你应该没问题。要记住的一件事是内部 toString() 的格式可能会在未来的 JDK 版本中发生变化。 @Kevin:是的,我想,在未来的 JDK 版本中更改 toString 的实现听起来足够好,我认为不这样做的理由。 :) @Jagmal:subString 真的在伤害你。如果您的 listString 占用 n 个字节,则 subString 临时将另外 n-4 个字节添加到堆中。通过使用 StringBuilder,您将从“第一次尝试”中收到您的 n-4 @LicenseQ:我不知道在您编写此声明时是否正确,但对于最近发布的任何 Java 版本都不正确。 String.substring 的实现比这更聪明:它不复制原始字符串的字符缓冲区,它只是创建一个指向同一缓冲区的新 String 对象,并使用非零“偏移”字段来指示字符串不t 从缓冲区的开头开始。 Substring 分配一个 String 对象,其中包含三个 int(count、hash 和 offset)和对预先存在的缓冲区的引用,仅此而已。【参考方案13】:

如果您使用的是 java 8,您可以编写一个单行代码,而不是依赖于 ArrayList.toString() 实现:

String result = sList.stream()
                     .reduce("", String::concat);

如果您更喜欢使用StringBuffer 而不是String,因为String::concat 的运行时间为O(n^2),您可以先将每个String 转换为StringBuffer

StringBuffer result = sList.stream()
                           .map(StringBuffer::new)
                           .reduce(new StringBuffer(""), StringBuffer::append);

【讨论】:

仅供参考,这个实现是 O(n^2),因为 String::concat 将两个字符串复制到一个新的缓冲区:grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/…【参考方案14】:

Peter Lawrey 的答案的下一个变体,每个循环都没有初始化一个新字符串

String concatList(List<String> sList, String separator)

    Iterator<String> iter = sList.iterator();
    StringBuilder sb = new StringBuilder();

    while (iter.hasNext())
    
        sb.append(iter.next()).append( iter.hasNext() ? separator : "");
    
    return sb.toString();

【讨论】:

【参考方案15】:

假设仅移动指针/将字节设置为 null(或者 Java 实现 StringBuilder#setLength)更快,而不是每次通过循环检查条件以查看何时附加分隔符,您可以使用此方法:

public static String Intersperse (Collection> 集合,字符串分隔符)

    StringBuilder sb = new StringBuilder ();
    对于(对象项目:集合)
    
        if (item == null) 继续;
        sb.append (item).append (分隔符);
    
    sb.setLength(sb.length() - delimiter.length());
    return sb.toString();

【讨论】:

【参考方案16】:

根据性能需求和要添加的元素数量,这可能是一个不错的解决方案。如果元素数量较多,Arraylists 重新分配内存可能会比StringBuilder 慢一些。

【讨论】:

您能否解释一下“Arraylists 重新分配内存可能比 StringBuilder 慢一点。” Arraylist 建立在标准数组之上,这些要求您设置数组的大小(如int[] a = new int[10];)。这就是 ArrayList 正在为您做的事情,当您放入新元素时,ArrayList 必须创建一个更大的数组并将所有元素复制到新的数组中。【参考方案17】:

使用Functional Java 库,导入这些:

import static fj.pre.Monoid.stringMonoid;
import static fj.data.List.list;
import fj.data.List;

...那么你可以这样做:

List<String> ss = list("foo", "bar", "baz");
String s = stringMonoid.join(ss, ", ");

或者,如果您没有字符串列表,则通用方式:

public static <A> String showList(List<A> l, Show<A> s) 
  return stringMonoid.join(l.map(s.showS_()), ", ");

【讨论】:

【参考方案18】:

如果您的依赖项中有 json。您可以使用 new JSONArray(list).toString()

【讨论】:

【参考方案19】:

在 java 8 中,您还可以使用 reducer,例如:

public static String join(List<String> strings, String joinStr) 
    return strings.stream().reduce("", (prev, cur) -> prev += (cur + joinStr));

【讨论】:

以上是关于连接字符串对象列表的最佳方法? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

连接/聚合字符串的最佳方式

python builder方法是做啥用的

从连接字符串中获取用户名和密码的正确方法? [复制]

使用 C# 和 ASP.NET 将数据从 Excel 文件导出到 SQL Server 表的最佳方法? [复制]

内置对象相关方法

在 id 和值列表之间在 Pandas 中进行复杂连接的最佳方法