Do you still use + concatenation strings in the for loop? Tomorrow is not for work!

Author: Keats in post puberty https://www.cnblogs.com/keatsCoder/p/13212289.html

introduction

It is said that StringBuilder is more efficient than String in handling String splicing, but sometimes there may be some deviation in our understanding. Recently, when I tested the efficiency of data import, I found that my previous understanding of StringBuilder was wrong. Later, I found out the logic of this piece by means of practical test + finding principle. Now let's share the process

test case

There are generally two cases when our code splices strings in a loop

  • The first is to splice several fields in the object into a new field each time, and then assign a value to the object
  • The second operation is to create a string object outside the loop and splice new content to the string each time. After the loop ends, the spliced string is obtained

For both cases, I created two control groups

Group 1:

Concatenate strings in each For loop, that is, use them and destroy them when they are used up. Use String and StringBuilder to splice respectively

    /**
     * String concatenates strings in a loop and is destroyed after one loop
     */
    public static void useString(){
        for (int i = 0; i < CYCLE_NUM_BIGGER; i++) {
            String str = str1 + i + str2 + i + str3 + i + str4 ;
        }
    }

    /**
     * String builder is used to splice strings in the loop and destroy them after one loop
     */
    public static void useStringBuilder(){
        for (int i = 0; i < CYCLE_NUM_BIGGER; i++) {
            StringBuilder sb = new StringBuilder();
            String s = sb.append(str1).append(i).append(str2).append(i).append(str3).append(i).append(str4).toString();
        }
    }

Group 2:

Multiple For loops splice a String. The String is used at the end of the loop and recycled by the garbage collector. It is also spliced using String and StringBuilder respectively

    /**
     * Concatenate multiple loops into a String using String
     */
    public static void useStringSpliceOneStr (){
        String str = "";
        for (int i = 0; i < CYCLE_NUM_LOWER; i++) {
            str += str1 + str2 + str3 + str4 + i;
        }
    }

    /**
     * Concatenate multiple loops into a string with StringBuilder
     */
    public static void useStringBuilderSpliceOneStr(){
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < CYCLE_NUM_LOWER; i++) {
            sb.append(str1).append(str2).append(str3).append(str4).append(i);
        }
    }

In order to ensure the test quality, before each test item is carried out. The thread rested for 2s, and then ran for 5 times to warm up. Finally, calculate the time by averaging the time for 5 times

    public static int executeSometime(int kind, int num) throws InterruptedException {
        Thread.sleep(2000);
        int sum = 0;
        for (int i = 0; i < num + 5; i++) {
            long begin = System.currentTimeMillis();

            switch (kind){
                case 1:
                    useString();
                    break;
                case 2:
                    useStringBuilder();
                    break;
                case 3:
                    useStringSpliceOneStr();
                    break;
                case 4:
                    useStringBuilderSpliceOneStr();
                    break;
                default:
                    return 0;
            }

            long end = System.currentTimeMillis();

            if(i > 5){
                sum += (end - begin);
            }
        }
        return sum / num;
    }

Main method

public class StringTest {
    public static final int CYCLE_NUM_BIGGER = 10_000_000;
    public static final int CYCLE_NUM_LOWER = 10_000;
    public static final String str1 = "Zhang San";
    public static final String str2 = "Li Si";
    public static final String str3 = "Wang Wu";
    public static final String str4 = "Zhao Liu";


    public static void main(String[] args) throws InterruptedException {
        int time = 0;
        int num = 5;

        time = executeSometime(1, num);
        System.out.println("String Splicing "+ CYCLE_NUM_BIGGER +" Times," + num + "Time average:" + time + " ms");

        time = executeSometime(2, num);
        System.out.println("StringBuilder Splicing "+ CYCLE_NUM_BIGGER +" Times," + num + "Time average:" + time + " ms");

        time = executeSometime(3, num);
        System.out.println("String Splice a single string "+ CYCLE_NUM_LOWER +" Times," + num + "Time average:" + time + " ms");

        time = executeSometime(4, num);
        System.out.println("StringBuilder Splice a single string "+ CYCLE_NUM_LOWER +" Times," + num + "Time average:" + time + " ms");

    }
}

test result

The test results are as follows

Result analysis

first group

10_ 000_ 000 times of loop splicing, the efficiency of using String and StringBuilder in the loop is the same! Why?

Use javap -c StringTest.class to decompile and view the files compiled by the two methods:

It can be found that StringBuilder is used after String method splicing and String compiler optimization, so the efficiency of use case 1 and use case 2 is the same.

Group 2

The result of the second group is loved by everyone, because 10_ 000_ 000 loop String splicing is too slow, so I used 10_ 000 splices to analyze.

Analysis case 3: Although the compiler will optimize String splicing, it creates StringBuilder objects in the loop and destroys them in the loop every time. The next cycle he has created. In comparison, use case 4 creates new objects outside the loop n times, destroys objects, and converts StringBuilder into String n - 1 times. Low efficiency is also a matter of course.

extend

There is another way to write the test of the first group:

    /**
     * String builder is used to splice strings in the loop and destroy them after one loop
     */
    public static void useStringBuilderOut(){
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < CYCLE_NUM_BIGGER; i++) {
//            sb.setLength(0);
            sb.delete(0, sb.length());
            String s = sb.append(str1).append(i).append(str2).append(i).append(str3).append(i).append(str4).toString();
        }
    }

Create a StringBuilder outside the loop, empty the contents of the StringBuilder at the beginning of each loop, and then splice. No matter sb.setLength(0) is used in this way; Or sb.delete(0, sb.length()); Efficiency is slower than using String / StringBuilder directly within a loop. However, I don't understand why he is slow. I guess the speed of the new object is slower than the length, so I tested the following:

    public static void createStringBuider() {
        for (int i = 0; i < CYCLE_NUM_BIGGER; i++) {
            StringBuilder sb = new StringBuilder();
        }
    }

    public static void cleanStringBuider() {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < CYCLE_NUM_BIGGER; i++) {
            sb.delete(0, sb.length());
        }
    }

But the result is that cleanStringBuider is faster. I can't figure it out

If a great God sees hope, he can help analyze it

conclusion

  • The compiler will optimize String splicing to use StringBuilder, but there are still some defects. It is mainly reflected in the use of String splicing in the loop. The compiler will not create a single StringBuilder for reuse
  • For the need to splice a String in multiple loops: StringBuilder is fast because it avoids n operations of new object and object destruction, and n - 1 operations of converting StringBuilder into String
  • StringBuilder splicing is not applicable to the operation mode of each splicing in the loop. Because the compiler optimized String splicing also uses StringBuilder, the efficiency of both is the same. The latter is easy to write

-END-

Posted on Thu, 25 Nov 2021 18:37:54 -0500 by tuuga