编程知识 cdmana.com

When a colleague uses StringBuilder in this way, I give him a bug

String splicing is used very frequently in projects , But a little carelessness often leads to some performance problems . lately Review Code found that colleagues wrote the following code , So I mentioned one to him bug.

@Test
public void testForAdd() {
    String result = "NO_";
    for (int i = 0; i < 10; i++) {
        result += i;
    }
    System.out.println(result);
}

This article will take you from the surface to the bottom to talk about , Why does this writing have performance problems .

IDE A hint of

If you use it IDE Plug in with code checking installed , It's easy to see the code above “+=” The operation will have a yellow background , This is the plug-in prompt , There's something wrong with the use here .

Let's take a look at “+=”,IDEA Details of the tips given :

String concatenation ‘+=’ in loop
Inspection info: Reports String concatenation in loops. As every String concatenation copies the whole String, usually it is preferable to replace it with explicit calls to StringBuilder.append() or StringBuffer.append().

The simple translation of this passage is : In circulation , String splicing uses “+=”. Inspection information : String stitching in the report loop . Every time String All the stitches will copy the whole String. It is generally recommended to replace it with StringBuilder.append() or StringBuffer.append().

The reason is given in the message , And give the solution suggestions . But is it really as simple as the hint ?Java8 Use later String Splicing JVM Compile time is not built by default optimization StringBuilder Did you? , Why is there a problem ? Let's take a deep analysis of .

Decompiling bytecode

For the code above , Let's decompile by bytecode , have a look JVM Did you help us optimize in this process , Whether it involves the whole String Copy .

Use javap -c Command to view bytecode content :

public void testForAdd();
Code:
   // Reference from constant pool #2 And push it to the top of the stack , Operation String Initialized variables “NO_”
   0: ldc           #2                  // String NO_
   2: astore_1
   3: iconst_0
   4: istore_2
   5: iload_2
   6: bipush        10
   // If the top two values of the stack are greater than or equal to 0( here 0-10) Then jump 36(code), Here we start to enter for Loop processing 
   8: if_icmpge     36
   // establish StringBuilder object , Its reference is put on the stack 
  11: new           #3                  // class java/lang/StringBuilder
  14: dup
  // call StringBuilder Construction method of 
  15: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
  18: aload_1
  19: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  22: iload_2
  // call append Method 
  23: invokevirtual #6                  // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
  // call toString Method , And will produce String To the top of the stack 
  26: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
  29: astore_1
  30: iinc          2, 1
  33: goto          5
  36: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
  39: aload_1
  40: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
  43: return

The key parts of the above decompilation bytecode operation have been marked out . Number 0 The defined... Will be loaded at “NO_” character string , Number 8 At the beginning of the cycle of judgment , eligible (0-10) The following parts of the loop body are executed . In the circulatory system , Number 11 establish StringBuilder object , Number 15 call StringBuilder Construction method of , Number 23 call append Method , Number 26 call toString Method .

After the above steps, we can find out what ?JVM It does help us optimize at compile time , take for The string splicing in the loop turns into StringBuilder, And pass appen Methods and toString Methods to deal with . Is there a problem in this way ?JVM It has been optimized !

however , Here comes the key question : Every time for The loop creates a new one StringBuilder, It's all going on append and toString operation , And then destroy it. . It's going to be scary , It's about creating String Object and copy is more than .

After the above analysis , The effect of the above code is equivalent to the following code :

@Test
public void testForAdd1() {
    String result = "NO_";
    for (int i = 0; i < 10; i++) {
        result = new StringBuilder(result).append(i).toString();
    }
    System.out.println(result);
}

It's more intuitive in this way ? thus , I think you already understand why you mentioned it to that colleague bug Is that right .

Programme improvement

that , Answer the above question , How to improve the code ? Go straight to the code :

@Test
public void testForAppend() {
    StringBuilder result = new StringBuilder("NO_");
    for (int i = 0; i < 10; i++) {
        result.append(i);
    }
    System.out.println(result);
}

take StringBuilder The creation of objects is out there ,for Call... Directly in the loop append that will do . Let's look at the bytecode operation of this code :

public void testForAppend();
Code:
   0: new           #3                  // class java/lang/StringBuilder
   3: dup
   4: ldc           #2                  // String NO_
   6: invokespecial #10                 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
   9: astore_1
  10: iconst_0
  11: istore_2
  12: iload_2
  13: bipush        10
  15: if_icmpge     30
  18: aload_1
  19: iload_2
  20: invokevirtual #6                  // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
  23: pop
  24: iinc          2, 1
  27: goto          12
  30: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
  33: aload_1
  34: invokevirtual #11                 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
  37: return

Compare the original bytecode content , See if it's a lot simpler , Problem solving .

for The scene within the loop

The usage scenarios described above are mainly aimed at the use of for Loop to get an entire string , But in some business scenarios, the string itself may be spliced only in for Cycle of , Will not be in for Deal with the external loop , such as :

@Test
public void testInfoForAppend() {
    for (int i = 0; i < 10; i++) {
        String result = "NO_" + i;
        System.out.println(result);
    }
}

In the above code for String concatenation inside loops can be more complex , We already know JVM It will be optimized to the one mentioned above StringBuilder To deal with . meanwhile , Every time you create StringBuilder object , So in this case , Just let it go ?

Actually , There is another way to think about , That is in for Outside the loop, create a StringBuilder, After the internal use is finished, it will be emptied . There are two ways to empty :delete Methods delete and setLength Method .

Two methods directly on the sample code :

@Test
public void testDelete() {
    StringBuilder result = new StringBuilder();
    for (int i = 0; i < 10; i++) {
        result.delete(0,result.length());
        result.append(i);
        System.out.println(result);
    }
}

@Test
public void testSetLength() {
    StringBuilder result = new StringBuilder();
    for (int i = 0; i < 10; i++) {
        result.setLength(0);
        result.append(i);
        System.out.println(result);
    }
}

About the verification and underlying operation of the above example , Interested friends can continue to dig deep , Here's the conclusion . After testing , The performance of both methods is much better than the default processing method . meanwhile delete The way of operation is slightly better than setLength The way , Recommended delete The way .

Summary

adopt IDE A prompt message for , We dig deep into the underlying principle and verify its implementation , I found so much room for improvement and hidden knowledge , Is there a sense of achievement ? Last , Let's sum up a little bit String and StringBuilder Knowledge points involved ( be based on Java8 And above ):

  • String concatenation without loops , Use it directly + Can ,JVM Will help us optimize .
  • String concatenation in concurrent scenarios , Use StringBuffer Instead of StringBuilder,StringBuffer It's thread safe .
  • Within the loop JVM There are some defects in the optimization of , It can be constructed in vitro StringBuilder, It's in the circulatory system append operation .
  • For string concatenation used in pure loop bodies , It can be constructed in vitro StringBuilder, Clear after use (delete or setLength).

Link to the original text :《 Colleagues use StringBuilder, I gave him a Bug


New perspective of procedure

official account “ New perspective of procedure ”, One makes you soft power 、 A platform for synchronous upgrading of hardware technology , Provide massive data

 WeChat official account : New perspective of procedure

版权声明
本文为[New vision of procedure]所创,转载请带上原文链接,感谢
https://cdmana.com/2020/12/20201224224101564V.html

Scroll to Top