【问题标题】: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?

【问题讨论】:

    标签: java string list


    【解决方案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();                           
      }
      

      【讨论】:

      • 我真的很喜欢这个答案 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);
                

                【讨论】:

                【解决方案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));
                        }
                        

                        【讨论】:

                          猜你喜欢
                          • 2013-07-23
                          • 2016-12-11
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          • 2017-12-20
                          相关资源
                          最近更新 更多