I was busy with the logic implementation of the project during the weekdays. I had some time on Saturday, so I took out the thick English version of Thinking In Java from the bookcase and read about the splicing of string objects. Make a translation with reference to this book, add your own thoughts, and write this article to record it.
Immutable String object
In Java, String objects are immutable. In code, you can create multiple aliases for a String object. But these aliases all refer to the same thing.
For example, s1 and s2 are both aliases of the "droidyue.com" object, and the aliases store references to the real objects. So s1 = s2
Copy the code code as follows:
String s1 = "droidyue.com";
String s2 = s1;
System.out.println("s1 and s2 has the same reference =" + (s1 == s2));
The only overloaded operator in Java
In Java, the only overloaded operator is related to string concatenation. +,+=. In addition, Java designers do not allow overloading of other operators.
Splicing analysis
Is there really a performance cost?
After understanding the above two points, you may have this thought. Since Sting objects are immutable, splicing multiple (three or more) strings will inevitably produce redundant intermediate String objects.
Copy the code code as follows:
String userName = "Andy";
String age = "24";
String job = "Developer";
String info = userName + age + job;
To get the above info, userName and age will be spliced to generate a temporary String object t1, the content is Andy24, and then t1 and job will be spliced to generate the final info object we need. Among them, an intermediate t1 is generated, and t1 is created Afterwards, if there is no active recycling, it will inevitably occupy a certain amount of space. If it is a splicing of many (assuming hundreds of them, mostly in calls to toString of objects) strings, the cost will be even greater, and the performance will be reduced a lot.
Compiler optimization processing
Is there really a performance cost above? Is there no special processing optimization for string concatenation so commonly used? The answer is yes. This optimization is performed when the compiler compiles .java into bytecode.
If a Java program wants to run, it needs to go through two periods, compilation time and runtime. During compilation, the Java compiler (Compiler) converts the java file into bytecode. At runtime, the Java Virtual Machine (JVM) runs the bytecode generated at compile time. Through these two periods, Java achieved the so-called compilation in one place and run everywhere.
Let's experiment with what optimizations have been done during compilation, and we can create a piece of code that may have a performance penalty.
Copy the code code as follows:
public class Concatenation {
public static void main(String[] args) {
String userName = "Andy";
String age = "24";
String job = "Developer";
String info = userName + age + job;
System.out.println(info);
}
}
Compile Concatenation.java. getConcatenation.class
Copy the code code as follows:
javacConcatenation.java
Then we use javap to decompile the compiled Concatenation.class file. javap -c Concatenation. If the javap command is not found, consider adding the directory where javap is located to the environment variable or using the full path to javap.
Copy the code code as follows:
17:22:04-androidyue~/workspace_adt/strings/src$ javap -c Concatenation
Compiled from "Concatenation.java"
public class Concatenation {
public Concatenation();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: ldc #2 // String Andy
2: astore_1
3: ldc #3 // String 24
5: astore_2
6: ldc #4 // String Developer
8: astore_3
9: new #5 // class java/lang/StringBuilder
12: dup
13: invokespecial #6 // Method java/lang/StringBuilder."<init>":()V
16: aload_1
17: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
20: aload_2
21: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
24: aload_3
25: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
28: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
31: astore 4
33: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
36: load 4
38: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
41: return
}
Among them, ldc, astore, etc. are Java bytecode instructions, similar to assembly instructions. The following comments use Java-related content for explanation. We can see that there are many StringBuilders above, but we do not call them explicitly in the Java code. This is the optimization done by the Java compiler. When the Java compiler encounters string splicing, it will create a StringBuilder object, and the following Splicing actually calls the append method of the StringBuilder object. In this way, there will be no problems that we worried about above.
Compiler optimization alone?
Since the compiler has done the optimization for us, is it enough to just rely on the compiler's optimization? Of course not.
Below we look at a piece of unoptimized code with lower performance
Copy the code code as follows:
public void implicitUseStringBuilder(String[] values) {
String result = "";
for (int i = 0; i < values.length; i ++) {
result += values[i];
}
System.out.println(result);
}
Use javac to compile and javap to view
Copy the code code as follows:
public void implicitUseStringBuilder(java.lang.String[]);
Code:
0: ldc #11 // String
2: astore_2
3: iconst_0
4: istore_3
5: iload_3
6: aload_1
7:array length
8: if_icmpge 38
11: new #5 // class java/lang/StringBuilder
14: dup
15: invokespecial #6 // Method java/lang/StringBuilder."<init>":()V
18: aload_2
19: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
22: aload_1
23: iload_3
24: aaload
25: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
28: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
31: astore_2
32: iinc 3, 1
35: goto 5
38: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
41: aload_2
42: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
45: return
Among them, 8: if_icmpge 38 and 35: goto 5 form a loop. 8: if_icmpge 38 means that if the integer comparison of the JVM operand stack is greater than or equal to (the opposite result of i < values.length), jump to line 38 (System.out). 35: goto 5 means jumping directly to line 5.
But one very important thing here is that the creation of StringBuilder objects occurs between loops, which means how many StringBuilder objects will be created in as many loops, which is obviously not good. Naked low-level code.
A little optimization can instantly improve your performance.
Copy the code code as follows:
public void explicitUseStringBuider(String[] values) {
StringBuilder result = new StringBuilder();
for (int i = 0; i < values.length; i ++) {
result.append(values[i]);
}
}
Corresponding compiled information
Copy the code code as follows:
public void explicitUseStringBuider(java.lang.String[]);
Code:
0: new #5 // class java/lang/StringBuilder
3: dup
4: invokespecial #6 // Method java/lang/StringBuilder."<init>":()V
7: astore_2
8: iconst_0
9: istore_3
10: iload_3
11: aload_1
12:arraylength
13: if_icmpge 30
16: aload_2
17: aload_1
18: iload_3
19: aaload
20: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
23: pop
24: iinc 3, 1
27: goto 10
30: return
As can be seen from the above, 13: if_icmpge 30 and 27: goto 10 form a loop, and 0: new #5 is outside the loop, so StringBuilder will not be created multiple times.
In general, we need to try to avoid implicitly or explicitly creating StringBuilder in the loop body. Therefore, those who understand how the code is compiled and how it is executed internally can write higher-level code.
If there are any errors in the above article, please criticize and correct them.