简体   繁体   中英

Why String concatenation is faster than StringBuilder in Java?

This program converts an object's state into an HTML string.

public class Test {
    public static void main(String[] args) {
        Address addr = new Address();
        addr.setLine1("A straight line");
        addr.setLine2("A curve");
        addr.setCity("A Round City");
        addr.setState("A Triangular State");
        addr.setCountry("A Rectangle Country");
        addr.setZip("123456");

        @SuppressWarnings("unused")
        String str;
        int count = 1000;
        for (int j = 0; j < 5; j++) {

            double timeRich = System.nanoTime();
            for (int i = 0; i < count; i++) {
                str = AddressFormatter.formatRich(addr);
            }
            timeRich = System.nanoTime() - timeRich;

            double timeFine = System.nanoTime();
            for (int i = 0; i < count; i++) {
                str = AddressFormatter.formatFine(addr);
            }
            timeFine = System.nanoTime() - timeFine;


            double timePoor = System.nanoTime();
            for (int i = 0; i < count; i++) {
                str = AddressFormatter.formatPoor(addr);
            }
            timePoor = System.nanoTime() - timePoor;

            System.out.println("Test cases: " + count);
            System.out.println("Average time to format (SB Poor): " + (int)(timePoor/count) + " ns");
            System.out.println("Average time to format (SB Fine): " + (int)(timeFine/count) + " ns");
            System.out.println("Average time to format (String) : " + (int)(timeRich/count) + " ns");
            System.out.println();
            count *= 10;
        }
        System.out.println("***End of test***");
    }
}

class Address {
    private String line1;
    private String line2;
    private String city;
    private String state;
    private String country;
    private String zip;

    /**
     * Default constructor.
     */
    public Address() {}

    public String getLine1() {
        return line1;
    }
    public void setLine1(String line1) {
        this.line1 = line1;
    }
    public String getLine2() {
        return line2;
    }
    public void setLine2(String line2) {
        this.line2 = line2;
    }
    public String getCity() {
        return city;
    }
    public void setCity(String city) {
        this.city = city;
    }
    public String getState() {
        return state;
    }
    public void setState(String state) {
        this.state = state;
    }
    public String getCountry() {
        return country;
    }
    public void setCountry(String country) {
        this.country = country;
    }
    public String getZip() {
        return zip;
    }
    public void setZip(String zip) {
        this.zip = zip;
    }
}

class AddressFormatter {
    // more readable than formatFine()
    public static String formatPoor(Address obj) {
        StringBuilder str = new StringBuilder();
        str.append("<div class=\"address-wrapper\">\n");
        str.append("\t<div class=\"addr-line\">" + obj.getLine1() + "</div>\n");
        str.append("\t<div class=\"addr-line\">" + obj.getLine2() + "</div>\n");
        str.append("\t<div class=\"addr-city\">" + obj.getCity() + "</div>\n");
        str.append("\t<div class=\"addr-state\">" + obj.getState() + "</div>\n");
        str.append("\t<div class=\"addr-country\">" + obj.getCountry() + "</div>\n");
        str.append("\t<div class=\"addr-zip\">" + obj.getZip() + "</div>\n");
        str.append("</div>\n");

        return str.toString();
    }

    // grouping all constants, removing string concatenations
    public static String formatFine(Address obj) {
        StringBuilder str = new StringBuilder();
        str.append("<div class=\"address-wrapper\">\n\t<div class=\"addr-line\">");
        str.append(obj.getLine1());
        str.append("</div>\n\t<div class=\"addr-line\">");
        str.append(obj.getLine2());
        str.append("</div>\n\t<div class=\"addr-city\">");
        str.append(obj.getCity());
        str.append("</div>\n\t<div class=\"addr-state\">");
        str.append(obj.getState());
        str.append("</div>\n\t<div class=\"addr-country\">");
        str.append(obj.getCountry());
        str.append("</div>\n\t<div class=\"addr-zip\">");
        str.append(obj.getZip());
        str.append("</div>\n</div>\n");

        return str.toString();
    }

    public static String formatRich(Address obj) {
        return "<div class=\"address-wrapper\">\n"
        + "\t<div class=\"addr-line\">" + obj.getLine1() + "</div>\n"
        + "\t<div class=\"addr-line\">" + obj.getLine2() + "</div>\n"
        + "\t<div class=\"addr-city\">" + obj.getCity() + "</div>\n"
        + "\t<div class=\"addr-state\">" + obj.getState() + "</div>\n"
        + "\t<div class=\"addr-country\">" + obj.getCountry() + "</div>\n"
        + "\t<div class=\"addr-zip\">" + obj.getZip() + "</div>\n"
        + "</div>\n";
    }
}

I get the following results when running this program in Eclipse:

Test cases: 1000
Average time to format (SB Poor): 13513 ns
Average time to format (SB Fine): 7052 ns
Average time to format (String) : 14088 ns

Test cases: 10000
Average time to format (SB Poor): 3061 ns
Average time to format (SB Fine): 3290 ns
Average time to format (String) : 1618 ns

Test cases: 100000
Average time to format (SB Poor): 3486 ns
Average time to format (SB Fine): 1568 ns
Average time to format (String) : 589 ns

Test cases: 1000000
Average time to format (SB Poor): 616 ns
Average time to format (SB Fine): 547 ns
Average time to format (String) : 497 ns

Test cases: 10000000
Average time to format (SB Poor): 657 ns
Average time to format (SB Fine): 626 ns
Average time to format (String) : 191 ns

***End of test***

Why String version is faster than StringBuilder version?

Why average time is reducing after every iteration?

EDIT: I have added another formatting function by removing all concatenation operations from 'StringBuilder' version (as pointed out by one answer).

In the first iteration 'String' version is the slowest.

In the last iteration 'String' version is the fastest.

The second part of your question is easy: The JVM is recognizing repeated executions and optimizing the machine code, which is why it's important to handle benchmarks carefully .

Here's what is happening to explain the difference in implementations:

Your " StringBuilder " implementation is very poorly written. Instead of appending each component, you're performing string concatenation (creating and then discarding a new StringBuilder ) for each method call and then appending the result of that. If you correctly used .append for each element, you'd see much less of a difference.

However, modern Java compilers turn a series of string concatenations with + into an implicit StringBuilder invocation to minimize object creation. The Java compiler also merges compile-time string constants that are concatenated. Therefore, your format2 method is also using a StringBuilder , with one important difference--all of the line-wrapped adjacent string constants are merged. Therefore, even if you were to fix your format method, format2 will be faster because it is grouping more of the fixed content.

I expected the byte code of formatRich and formatFine were equivalent, yet it was not. So I tried to get two equivalent methods:

Rewrite your StringBuilder method to

public static String formatFine(Address obj) {
    return new StringBuilder("<div class=\"address-wrapper\">\n\t<div class=\"addr-line\">")
        .append(obj.getLine1())
        .append("</div>\n\t<div class=\"addr-line\">")
        .append(obj.getLine2())
        .append("</div>\n\t<div class=\"addr-city\">")
        .append(obj.getCity())
        .append("</div>\n\t<div class=\"addr-state\">")
        .append(obj.getState())
        .append("</div>\n\t<div class=\"addr-country\">")
        .append(obj.getCountry())
        .append("</div>\n\t<div class=\"addr-zip\">")
        .append(obj.getZip())
        .append("</div>\n</div>\n").toString();
}

This method is equivalent to following in java byte code:

public static String formatRich(Address obj) {
    return "<div class=\"address-wrapper\">\n\t<div class=\"addr-line\">"
        + obj.getLine1()
        + "</div>\n\t<div class=\"addr-line\">"
        + obj.getLine2()
        + "</div>\n\t<div class=\"addr-city\">"
        + obj.getCity()
        + "</div>\n\t<div class=\"addr-state\">"
        + obj.getState()
        + "</div>\n\t<div class=\"addr-country\">"
        + obj.getCountry()
        + "</div>\n\t<div class=\"addr-zip\">"
        + obj.getZip()
        + "</div>\n</div>\n";
}

Executing your main program resolves (on my machine) to:

...

Test cases: 10000000
Average time to format (SB Poor): 633 ns
Average time to format (SB Fine): 151 ns
Average time to format (String) : 152 ns

Explanation:

  • The explicit statement str.append has to load str from the stack. The result is pushed on the stack but never used.
  • The direct concatenation (and the chained StringBuilder ) reuses the result of str.append which is already on the stack
  • Both ( str and the result of str.append ) point to the same heap location, yet I do not know if this can be derived by the compiler. It seems that the current optimization level is not able to optimize it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM