将ArrayList转换为字符串的最佳方法


353

我有一个ArrayList我想完全输出为String的。本质上,我想使用toString由制表符分隔的每个元素按顺序输出。有什么快速的方法可以做到这一点吗?您可以遍历它(或删除每个元素)并将其连接为字符串,但是我认为这会非常慢。


4
或删除每个元素注意,从ArrayList列表中删除元素是一个很大的问题,因为由于元素的移位(假设您从第一个元素到最后一个元素进行循环),“循环”将花费二次时间。
Dimitry K

Answers:


329

基本上,使用循环来迭代ArrayList是唯一的选择:

不要使用此代码,请继续阅读此答案的底部,以了解为什么不希望使用此代码,以及应该使用哪个代码代替:

ArrayList<String> list = new ArrayList<String>();
list.add("one");
list.add("two");
list.add("three");

String listString = "";

for (String s : list)
{
    listString += s + "\t";
}

System.out.println(listString);

实际上,字符串串联就可以了,因为javac编译器无论如何都会通过一系列append操作来优化字符串串联StringBuilder。这是for上面程序从循环中反汇编字节码的一部分:

   61:  new #13; //class java/lang/StringBuilder
   64:  dup
   65:  invokespecial   #14; //Method java/lang/StringBuilder."<init>":()V
   68:  aload_2
   69:  invokevirtual   #15; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   72:  aload   4
   74:  invokevirtual   #15; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   77:  ldc #16; //String \t
   79:  invokevirtual   #15; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   82:  invokevirtual   #17; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;

可以看出,编译器通过使用来优化该循环StringBuilder,因此性能不是大问题。

(好的,乍一看,StringBuilder在每次循环迭代时都会实例化,因此它可能不是最有效的字节码。实例化和使用显式StringBuilder可能会产生更好的性能。)

实际上,我认为拥有任何类型的输出(无论是输出到磁盘还是输出到屏幕)至少要比担心字符串连接的性能慢一个数量级。

编辑:正如评论中指出的那样,上面的编译器优化确实是StringBuilder在每次迭代中创建的新实例。(我之前已经提到过。)

使用最优化的技术是Paul Tomblin的响应,因为它仅实例化循环StringBuilder外的单个对象for

将上面的代码重写为:

ArrayList<String> list = new ArrayList<String>();
list.add("one");
list.add("two");
list.add("three");

StringBuilder sb = new StringBuilder();
for (String s : list)
{
    sb.append(s);
    sb.append("\t");
}

System.out.println(sb.toString());

仅实例化StringBuilder循环外的一次,并且仅对append循环内的方法进行两次调用,如以下字节码所示(显示和的实例化StringBuilder):

   // Instantiation of the StringBuilder outside loop:
   33:  new #8; //class java/lang/StringBuilder
   36:  dup
   37:  invokespecial   #9; //Method java/lang/StringBuilder."<init>":()V
   40:  astore_2

   // [snip a few lines for initializing the loop]
   // Loading the StringBuilder inside the loop, then append:
   66:  aload_2
   67:  aload   4
   69:  invokevirtual   #14; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   72:  pop
   73:  aload_2
   74:  ldc #15; //String \t
   76:  invokevirtual   #14; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   79:  pop

因此,实际上,手优化应该表现得更好,因为for循环内部更短,并且不需要StringBuilder在每次迭代时实例化a 。


8
出于性能方面的考虑,考虑使用StringBuilder类而不是仅附加字符串。
杰里米(Jeremy)

5
编译器将优化字符串连接,但是每次执行循环时,您将创建一个新的StringBuilder对象。
Pedro Henriques,2009年

4
-1用于+ =,在这种情况下,它是性能杀手。始终使用StringBuilder重复附加到字符串。
starblue

10
此解决方案在字符串的末尾添加了一个多余的“ \ t”,这通常是不希望的,尤其是当您要创建用逗号分隔的列表或类似列表时。我认为这里发布的其他解决方案(使用Apache Commons或Guava)是更好的。
彼得·格茨

3
这不是最佳答案。在下面查看Vitalii Java或Geewax的相关信息Android
Gibolt

895

在Java 8或更高版本中:

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

如果list不是String类型,则可以使用联接收集器:

String listString = list.stream().map(Object::toString)
                        .collect(Collectors.joining(", "));

144
我不敢相信,直到Java 8才添加了它。
保罗

45
这个答案应该更多!没有黑客,没有库,也没有循环。
Songo 2014年

4
这对OP所说的不起作用。String.join作为第二个参数Iterable <?扩展CharSequence>。因此,如果您有要显示的List <String>,它会起作用,但是如果您有List <MyObject>,它将不会按OP的要求在其中调用toString()
Hilikus 2014年

3
为什么这不是第一个(并没有被接受的答案)..我总是必须向下滚动直到知道它在那里。.优雅而简洁
杰克

2
需要Android API 26+
Arsen Sench

391

如果您恰巧在Android上执行此操作,则有一个名为TextUtils的不错的实用程序,它具有一个.join(String delimiter, Iterable)方法。

List<String> list = new ArrayList<String>();
list.add("Item 1");
list.add("Item 2");
String joined = TextUtils.join(", ", list);

显然,在Android之外没有太多使用,但是我想将其添加到此线程中...


1
由于TextUtils.join采用Object[],我假设它正在调用toString()每个令牌。是否PatientDetails实现toString()?如果那不能解决问题,则您可能无法在Separator该类上做一些事情。
JJ Geewax 2012年

5
org.apache.lang3.StringUtils几乎一样,所以您的答案对Android以外的人来说绝对有用:)
Patrick

1
这个答案的流行(目前投票率最高)显示了许多Java问题源于Android开发
Amr H. Abd Elmajeed

1
我敢打赌,您被送到地球解决了我的问题:)
kofoworola

1
你救了我的命。<3
Pratik Butani '18

234

下载Apache Commons Lang并使用该方法

 StringUtils.join(list)

 StringUtils.join(list, ", ") // 2nd param is the separator.

当然,您可以自己实现它,但是它们的代码已经过全面测试,可能是最好的实现。

我是Apache Commons库的忠实拥护者,我也认为它是Java Standard Library的重要补充。


63
不幸的是,SO的居民更喜欢重新发明轮子。
skaffman's

33
同样在Apache Commons Lang中。用法看起来像是StringUtils.join(list.toArray(),"\t")
Muhd

33
这个特殊的轮子几乎不值得额外依赖。
塞瓦·阿列克谢耶夫

25
@SevaAlekseyev我认为这值得商bat。如果立即添加此依赖项,则将更多地使用它的类。而不是使用“ if string!= null && string.trim()!=”“,而是使用StringUtils.isNotEmpty ...您将使用ObjectUtils.equals(),这样您就不必在所有地方检查null。如果你等待一个依赖,使用这些库证明,你可能从来没有真正使用它们。
拉维瓦劳

6
不会在末尾添加其他选项卡也很好!
keuleJ

139

这是一个很老的问题,但是我想我也可以添加一个更现代的答案-使用Guava中Joiner类:

String joined = Joiner.on("\t").join(list);

2
这是我最喜欢的解决方案。:->番石榴+1。
csgeek

Ctrl + F也适用于番石榴。谢谢!
Priidu Neemre

List <String> list = new ArrayList <String>(); list.add(“ Hello”); list.add(“ Hai”); list.add(“ Hi”); System.out.println(list.toString()。substring(1,list.toString()。length()-1).replaceAll(“,”,“ \ t”)));
洛瓦·奇图穆里

86

列表更改为可读且有意义的字符串确实是每个人都可能遇到的常见问题。

案例1。如果您的类路径中有apache的StringUtils(例如来自rogerdpack和Ravi Wallau):

import org.apache.commons.lang3.StringUtils;
String str = StringUtils.join(myList);

情况2。如果只想使用JDK(7)中的方法:

import java.util.Arrays;
String str = Arrays.toString(myList.toArray()); 

只是不要自己动手制作轮子,不要在此单线任务中使用循环。


1
在发布我自己的案例之前,我没有注意到您的案例2解决方案。您的实际上更好,并节省了额外的步骤。我肯定会在似乎需要额外工具的一些答案中推荐它。
Elliander

5
Arrays.toString(myList.toArray())-最简单,最直接的解决方案
Ondrej Bozek

我认为这应该被认为是最好,最有效的解决方案
Morey

Arrays.toString(list.toArray())易于编写,但效率极低。
Matthieu

60

如果您正在寻找一种快速的单行代码,那么从Java 5开始,您可以执行以下操作:

myList.toString().replaceAll("\\[|\\]", "").replaceAll(", ","\t")

另外,如果您的目的只是打印内容,而不关心“ \ t”,则可以执行以下操作:

myList.toString()

它返回一个类似的字符串

[str1,str2,str3]

如果您有一个Array(而不是ArrayList),则可以完成以下操作:

 Arrays.toString(myList).replaceAll("\\[|\\]", "").replaceAll(", ","\t")

3
我认为这实际上比上面发布的方法更有效。真可惜,它显示在底部。它可能并不优雅,但我坚信您的解决方案运行O(n),而其他解决方案理论上运行在O(n ^ 2)(由于Java字符串是不可变的,因此您基本上在每次合并时都创建一个新的String,随着结果字符串变大)
user3790827 '16

如果您的文本很大,则最终会占用计算机资源。
user3790827 '16

此方法比使用StringBuilder慢7-8倍。
Zoka

@ user3790827您所看到的只是一个肤浅的层面。如果我告诉您在各州城市的m家庭中寻找人,您会说是或,但是我可以改写“在这个国家寻找这个人”,然后您会立即告诉我。最重要的是,这里所有算法通常都是,循环没有循环。nxO(mnx)O(n^3)O(n)O(n)
Jai

39

遍历它并调用toString。没有一种神奇的方法,如果有的话,除了循环浏览之外,您认为它会在幕后做什么?关于唯一的微优化,是使用StringBuilder而不是String,即使那也不是一个大赢家-将字符串串联起来就变成了StringBuilder的内幕,但至少如果您以这种方式编写,您可以看到发生了什么。

StringBuilder out = new StringBuilder();
for (Object o : list)
{
  out.append(o.toString());
  out.append("\t");
}
return out.toString();

谢谢!这就是我最终要做的事情:)
Juan Besa,2009年

4
对于大型阵列,这绝对不是微优化。对于每个字符串连接,将自动完成使用StringBuilder的自动转换,这在循环的情况下无济于事。如果在一个表达式中连接大量元素,则可以。
starblue

1
如果要串联5万个字符串,使〜1 MB结果字符串显式使用,则显式使用StringBuilder是一件大事,这与1 s执行时间相比可能相差1分钟。
纳皮克先生2013年

36

大多数Java项目通常都提供apache-commons lang。StringUtils.join()方法非常好,并且具有多种功能可以满足几乎所有需求。

public static java.lang.String join(java.util.Collection collection,
                                    char separator)


public static String join(Iterator iterator, String separator) {
    // handle null, zero and one elements before building a buffer 
    Object first = iterator.next();
    if (!iterator.hasNext()) {
        return ObjectUtils.toString(first);
    }
    // two or more elements 
    StringBuffer buf = 
        new StringBuffer(256); // Java default is 16, probably too small 
    if (first != null) {
        buf.append(first);
    }
    while (iterator.hasNext()) {
        if (separator != null) {
            buf.append(separator);
        }
        Object obj = iterator.next();
        if (obj != null) {
            buf.append(obj);
        }
    }
    return buf.toString();
}

参数:

collection-要连接在一起的值的Collection,可以为null

分隔符 -要使用的分隔符

返回:联接的String,如果null迭代器输入,则返回 null

从:2.3


2
这很好,因为该join(...)方法具有数组和集合的变体。
vikingsteve

我需要创建包含很少填充的元素和随机数的字符串集合。最后,我需要一个字符串,而不是数组而不是数组。这是我的代码:Collections.shuffle(L); StringBuilder sb = new StringBuilder(); L.forEach(e-> sb.append(e)); 返回sb.toString();
dobrivoje


14

对于这个简单的用例,您可以简单地用逗号将字符串连接在一起。如果您使用Java 8:

String csv = String.join("\t", yourArray);

否则commons-lang具有join()方法:

String csv = org.apache.commons.lang3.StringUtils.join(yourArray, "\t");

13

处理尾随分隔符的一种优雅方法是使用Class Separator

StringBuilder buf = new StringBuilder();
Separator sep = new Separator("\t");
for (String each: list) buf.append(sep).append(each);
String s = buf.toString();

类分隔符的toString方法返回分隔符,除了第一个调用。因此,我们在打印列表时不使用尾部(或在此情况下)前导分隔符。


8

在Java 8中很简单。请参阅示例以获取整数列表:

String result = Arrays.asList(1,2,3).stream().map(Object::toString).reduce((t, u) -> t + "\t" + u).orElse("");

或多行版本(更易于阅读):

String result = Arrays.asList(1,2,3).stream()
    .map(Object::toString)
    .reduce((t, u) -> t + "\t" + u)
    .orElse("");

更新-较短的版本

String result = Arrays.asList(1,2,3).stream()
                .map(Object::toString)
                .collect(Collectors.joining("\t"));

2
这可能只是一行代码,但是对我来说,这看起来并不简单。对我来说,遍历List并将其元素解析为String比这更简单。
巴特斯拉

1
这也是我的第一印象。但是当我习惯了它之后,我发现它很棒。例如,您可以添加类似的操作filter。最后,对我来说,阅读代码要容易得多。
阿姆拉

3
在Java 8中:String.join(“ \ t”,array)
Tomasz

仅当内容也为a时String,最后一条注释才有效。如果它的列表Integer有问题
LeO

所有这3个都需要Android上的API级别24。
Asad35Waheed

6

O(n)无论哪种方式,它都是一种算法(除非您执行了一些多线程解决方案,否则您将列表分成了多个子列表,但是我认为这不是您要的)。

只需使用StringBuilder如下所示:

StringBuilder sb = new StringBuilder();

for (Object obj : list) {
  sb.append(obj.toString());
  sb.append("\t");
}

String finalString = sb.toString();

StringBuilder将比字符串串联快很多,因为您不会String在每个串联中重新实例化对象。


5

如果您碰巧是在Android上并且尚未使用Jack(例如,因为它仍不支持Instant Run),并且您想对生成的字符串的格式进行更多控制(例如,您希望将换行符用作,并且恰好使用/想要使用StreamSupport库(用于在Java 7或更早版本的编译器上使用流),您可以使用类似的方法(我将此方法放在ListUtils类中):

public static <T> String asString(List<T> list) {
    return StreamSupport.stream(list)
            .map(Object::toString)
            .collect(Collectors.joining("\n"));
}

当然,请确保在列表对象的类上实现toString()。


1
reduce对于String串联,使用效率极低。您应该return StreamSupport.stream(list).map(Object::toString).collect(joining("\n"))使用内部使用的Java 8方法StringJoiner。没有理由您也无法使用streamsupport做到这一点
Stefan Zobel

另一件事是,如果代码中Optional#get传递了一个空列表,则该代码将严重失败(由于)。
Stefan Zobel

@StefanZobel感谢您指出效率和边缘案例问题。修改了代码。
Javad Sadeqzadeh '16

3

如果您不希望最后一个\ t出现在最后一个元素之后,则必须使用索引进行检查,但是请记住,当列表实现RandomAccess时,这只能“起作用”(即O(n))。

List<String> list = new ArrayList<String>();
list.add("one");
list.add("two");
list.add("three");

StringBuilder sb = new StringBuilder(list.size() * apprAvg); // every apprAvg > 1 is better than none
for (int i = 0; i < list.size(); i++) {
    sb.append(list.get(i));
    if (i < list.size() - 1) {
        sb.append("\t");
    }
}
System.out.println(sb.toString());

3

以下代码可以为您提供帮助,

List list = new ArrayList();
list.add("1");
list.add("2");
list.add("3");
String str = list.toString();
System.out.println("Step-1 : " + str);
str = str.replaceAll("[\\[\\]]", "");
System.out.println("Step-2 : " + str);

输出:

Step-1 : [1, 2, 3]
Step-2 : 1, 2, 3

3

可能不是最好的方法,而是优雅的方法。

Arrays.deepToString(Arrays.asList(“ Test”,“ Test2”)

import java.util.Arrays;

    public class Test {
        public static void main(String[] args) {
            System.out.println(Arrays.deepToString(Arrays.asList("Test", "Test2").toArray()));
        }
    }

输出量

[测试,测试2]


1
我认为这是“最佳”表示代码可读性的最佳方法。我没有看过代码,但是它可能和StringBuilder方法一样高效(它可能在后台使用StringBuilder),尽管在输出中不是可定制的。
Mike Miller

3

以下内容有什么好处:

List<String> streamValues = new ArrayList<>();
Arrays.deepToString(streamValues.toArray()));   

3

如果您使用的是Eclipse Collections,则可以使用该makeString()方法。

ArrayList<String> list = new ArrayList<String>();
list.add("one");
list.add("two");
list.add("three");

Assert.assertEquals(
    "one\ttwo\tthree",
    ArrayListAdapter.adapt(list).makeString("\t"));

如果可以将转换ArrayListFastList,则可以摆脱适配器。

Assert.assertEquals(
    "one\ttwo\tthree",
    FastList.newListWith("one", "two", "three").makeString("\t"));

注意:我是Eclipse Collections的提交者。


3
List<String> stringList = getMyListOfStrings();
StringJoiner sj = new StringJoiner(" ");
stringList.stream().forEach(e -> sj.add(e));
String spaceSeparated = sj.toString()

您将传递到new StringJoiner要用作分隔符的char序列。如果要执行CSV:new StringJoiner(", ");


3

一行:从[12,0,1,78,12]到12 0 1 78 12

String srt= list.toString().replaceAll("\\[|\\]|,","");

2
1)不再有理由使用这样的代码(截至4年前!)。2)此答案不会对问答添加任何内容。
Radiodef '18

一行5星。非常感谢。
Atef Farouk

这很完美。对于逗号分隔的字符串,请使用:String srt = list.toString()。replaceAll(“ \ [| \]”,“”);
Asad35Waheed

2

用于使用制表符而不是使用制表符 println分隔,可以使用print

ArrayList<String> mylist = new ArrayList<String>();

mylist.add("C Programming");
mylist.add("Java");
mylist.add("C++");
mylist.add("Perl");
mylist.add("Python");

for (String each : mylist)
{       
    System.out.print(each);
    System.out.print("\t");
}

1

我看到了很多依赖于其他资源的示例,但这似乎是最简单的解决方案:(这是我在自己的项目中使用的方法),它基本上只是从ArrayList转换为Array,然后转换为List 。

    List<Account> accounts = new ArrayList<>();

   public String accountList() 
   {
      Account[] listingArray = accounts.toArray(new Account[accounts.size()]);
      String listingString = Arrays.toString(listingArray);
      return listingString;
   }

0

到目前为止,这是一个相当古老的话题,并且apache commons现在正在内部使用StringBuilder:http : //commons.apache.org/lang/api/src-html/org/apache/commons/lang/StringUtils.html#line。 3045

据我们所知,这将提高性能,但是如果性能至关重要,则所使用的方法可能会有些效率低下。尽管该接口是灵活的,并且将允许在不同Collection类型之间保持一致的行为,但对于Lists而言,效率是低下的,而Lists是原始问题中的Collection类型。

我这样做的基础是,我们会产生一些开销,通过简单地循环访问传统的for循环中的元素可以避免这些开销。相反,在后台检查并发修改,方法调用等情况时会发生其他事情。另一方面,由于在Iterable对象(列表)上使用了迭代器,因此增强的for循环将导致相同的开销。



-1

这个功能如何:

public static String toString(final Collection<?> collection) {
    final StringBuilder sb = new StringBuilder("{");
    boolean isFirst = true;
    for (final Object object : collection) {
        if (!isFirst)
            sb.append(',');
        else
            isFirst = false;
        sb.append(object);
    }
    sb.append('}');
    return sb.toString();
}

它适用于任何类型的收藏...

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.