Scruffy
Scruffy

Reputation: 580

Efficient conversion of ArrayList<String[]> to multi-line String in JAVA

ArrayList<String[]> writtenClasses = new ArrayList<String[]>();
// usually there is functional code here that populates
// ArrayList<String[]> writtenClasses with variably 3000
// String[] objects always of exactly 8 lines each

ArrayList<String> processedClasses = new ArrayList<String>();
for(String[] classLines: writtenClasses)
{
    for(String classLine: classLines)
    {
        processedClasses.add(classLine);
    }
}

String result = "";
for(String fileLine: processedClasses)
{
    result += fileLine + "\n";
}

My code is above. It works fine and produces exactly the result I want, just slowly. It takes about 10ms per item of ArrayList writtenClasses which is okay until I give it bigger jobs. I suspect that there is something there to do with ArrayLists that is taking so long, but timing and printing to console job stats after each run revealed little.

This above code is an adaptation of earlier code in hopes to improve efficiency. It does so by about 4%. The below code is the old method I used which takes just a little longer than the above.

for(String[] classLines: writtenClasses)
{
    for(String classLine: classLines)
    {
        result += classLine + "\n";
    }
    writtenClasses.set(writtenClasses.indexOf(classLines), null);
}

I do writtenClasses.set(writtenClasses.indexOf(classLines), null); merely for the purposes of memory efficiency, and my stats show that it uses memory more efficiently with an undetectable amount of CPU effort.

This is my second question here on StackOverflow and i've done my best to follow the rules, but if i'm asking this badly or being inadvertently inconsiderate in some way, please, highlight that to me and i'll address that. :)

Upvotes: 5

Views: 2442

Answers (5)

assylias
assylias

Reputation: 328619

The problem has been pointed out by other answers. With Java 8, an alternative to the two nested loops and a StringBuilder is to use a stream and a joining collector*:

String result = writtenClasses.stream()
        .flatMap(array -> Arrays.stream(array))
        .collect(joining("\n"));

*requires import static java.util.Collectors.joining;

Upvotes: 3

icza
icza

Reputation: 417767

There is absolutely no use creating the intermediate processedClasses list. Also, StringBuilder will speed up significantly the process:

// Consider a large initial size to even avoid reallocation, here I used 64 KB
StringBuilder sb = new StringBuilder(65536);

for (String[] classLines : writtenClasses)
    for (String lines : classLines)
        sb.append(lines).append('\n');

// Note: you might not even need to convert it to String, read reasoning below
String result = sb.toString();

We build the content in a StringBuilder which implements the CharSequence interface. Many classes accept CharSequences and not just Strings. A good example is a FileWriter. In these cases you don't even need to convert the StringBuilder to a String because the StringBuilder can be passed just as easily as its String result which may be another performance advantage if the content is really big.

Upvotes: 3

Ker p pag
Ker p pag

Reputation: 1588

based on this question

ewall:

At what point do you switch to StringBuilder? When it effects memory or performance. Or when it might. If you're really only doing this for a couple strings once, no worries. But if you're going to be doing it over and over again, you should see a measurable difference when using StringBuilder.

StringBuilder myString = new StringBuilder();

     for(String classLine: classLines)
        {
           myString.append(classLine).append("\n");
        }

StringBuilder would somehow improve your performance.

Upvotes: 0

Mureinik
Mureinik

Reputation: 311448

The main pain point here probably isn't the ArrayList, but the use of the + operator with Strings. Since Strings are immutable in java, each invocation forces the creation of a new object and copying of all the data, which, as you stated, may be quite long.

A faster way to do this would be to use a StringBuilder, which does not (necessarily) force the copying of the data on each operation:

StringBuilder result = new StringBuilder();
for(String[] classLines: writtenClasses)
{
    for(String classLine: classLines)
    {
        result.append(classLine).append('\n');
    }
}

Upvotes: 1

Jan Groth
Jan Groth

Reputation: 14636

Not a proper answer, but too awkward to read in a comment:

String result = "";
for(String fileLine: processedClasses)
{
    result += fileLine + "\n";
}

That is creating a million String instances. I guess using a StringBuilder here should have a positive effect on performance.

Upvotes: 1

Related Questions