Under what conditions and how can StringBuider be used more efficiently?

introduction

It is said that StringBuilder is more efficient than String in processing string splicing, but sometimes our understanding may be biased. Recently, when I tested the efficiency of data import, I found that part of my previous understanding of StringBuilder was wrong. Later, I figured out the logic of this piece through practical testing + finding principles. Now share the process with everyone

Test case

Our code generally has two situations for concatenating strings in a loop

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

For both cases, I created two control groups

First group:

Concatenate strings in each For loop, you can spell it out and destroy it when you run out. Use String and StringBuilder respectively to splice

    /**
     * 循环内 String 拼接字符串,一次循环后销毁
     */
    public static void useString(){
        for (int i = 0; i < CYCLE_NUM_BIGGER; i++) {
            String str = str1 + i + str2 + i + str3 + i + str4 ;
        }
    }

    /**
     * 循环内 使用 StringBuilder 拼接字符串,一次循环后销毁
     */
    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();
        }
    }

Second Group:

Multiple For loops are used to splice a string, and the string is used after the loop, and it is recycled by the garbage collector after use. Also use String and StringBuilder respectively to splice

    /**
     * 多次循环拼接成一个字符串 用 String
     */
    public static void useStringSpliceOneStr (){
        String str = "";
        for (int i = 0; i < CYCLE_NUM_LOWER; i++) {
            str += str1 + str2 + str3 + str4 + i;
        }
    }

    /**
     * 多次循环拼接成一个字符串 用 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 quality of the test, before each test item is carried out. The thread rests for 2 seconds, and then runs 5 times to warm up. Finally perform 5 times to calculate the time by averaging time

    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 = "张三";
    public static final String str2 = "李四";
    public static final String str3 = "王五";
    public static final String str4 = "赵六";


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

        time = executeSometime(1, num);
        System.out.println("String拼接 "+ CYCLE_NUM_BIGGER +" 次," + num + "次平均时间:" + time + " ms");

        time = executeSometime(2, num);
        System.out.println("StringBuilder拼接 "+ CYCLE_NUM_BIGGER +" 次," + num + "次平均时间:" + time + " ms");

        time = executeSometime(3, num);
        System.out.println("String拼接单个字符串 "+ CYCLE_NUM_LOWER +" 次," + num + "次平均时间:" + time + " ms");

        time = executeSometime(4, num);
        System.out.println("StringBuilder拼接单个字符串 "+ CYCLE_NUM_LOWER +" 次," + num + "次平均时间:" + time + " ms");

    }
}    

Test Results

The test results are as follows

Result analysis

First group

10_000_000 loop stitching, the efficiency of using String and StringBuilder in the loop is the same! why?

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

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

Second Group

The result of the second set is what everyone likes to hear, because 10_000_000 loop String stitching is too slow, so I used 10_000 stitching to analyze.

Analysis use case 3: Although the compiler optimizes String splicing, it creates a StringBuilder object every time in the loop and destroys it in the loop. He has created the next cycle. Compared with use case 4, when creating outside the loop, there are more operations of new object and object destruction, and n-1 operations of converting StringBuilder to String. Low efficiency is justified.

Expand

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

    /**
     * 循环内 使用 StringBuilder 拼接字符串,一次循环后销毁
     */
    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 StringBuilder outside the loop. Clear the contents of StringBuilder at the beginning of each loop and then splice. This way of writing, whether using sb.setLength(0); or sb.delete(0, sb.length());, is slower than using String / StringBuilder directly in the loop. However, I have never been able to understand why he is so slow. My guess is that the speed of the new object is slower than the reset 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. Make me scratch my head

If a great god sees hope, he can help analyze and analyze

in conclusion

  • The compiler will optimize String splicing to use StringBuilder, but there are still some flaws. Mainly reflected in the use of string splicing in the loop, the compiler will not create a single StringBuilder for reuse

  • For the requirement of splicing a string in multiple loops: StringBuilder is fast, because it avoids the operations of new and destroying objects n-1 times to convert StringBuilder to String

  • StringBuilder splicing is not suitable for the ready-to-use operation mode of each splicing in the loop. Because the String splicing optimized by the compiler also uses StringBuilder, the efficiency of the two is the same. The latter is convenient to write...

Guess you like

Origin blog.csdn.net/weixin_43144260/article/details/107037127