This article comes from the question "Best Practices for Java String Connections?"
There are many ways to connect strings in Java, such as the + operator and the StringBuilder.append method. What are the advantages and disadvantages of each of these methods (the implementation of each method can be appropriately explained Details)?
According to the principle of efficiency, what are the best practices for string concatenation in Java?
What other best practices are there for string processing?
Without further ado, let’s start directly. Environment As follows:
JDK version: 1.8.0_65
CPU: i7 4790
Memory: 16G
Use + splicing directly
Look at the code below:
@Test public void test() { String str1 = "abc"; String str2 = "def"; logger.debug(str1 + str2); }
In the above code, we use the plus sign to connect the four Strings, the advantages of this string splicing method are obvious: the code is simple and intuitive, but compared with StringBuilder and StringBuffer, in most cases it is lower than the latter. Here is the majority of the cases. We use the javap tool to compile the above code. The generated bytecode is decompiled to see what the compiler did to this code.
public void test(); Code: 0: ldc #5 // String abc 2: astore_1 3: ldc #6 // String def 5: astore_2 6: aload_0 7: getfield #4 // Field logger:Lorg/slf4j/Logger; 10: new #7 // class java/lang/StringBuilder 13: dup 14: invokespecial #8 // Method java/lang/StringBuilder."<init>":()V 17: aload_1 18: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 21: aload_2 22: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 25: invokevirtual #10 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 28: invokeinterface #11, 2 // InterfaceMethod org/slf4j/Logger.debug:(Ljava/lang/String;)V 33: return
Judging from the decompilation results, the + operator is actually used to splice strings. The compiler will optimize the code to use the StringBuilder class during the compilation phase, call the append method for string splicing, and finally call the toString method. , does it seem that under normal circumstances, I can actually use + directly, and the compiler will help me optimize it to use StringBuilder anyway?
StringBuilder source code analysis
The answer is naturally no, the reason lies in the internals of the StringBuilder class When something was done.
Let’s take a look at the constructor of the StringBuilder class
public StringBuilder() { super(16); } public StringBuilder(int capacity) { super(capacity); } public StringBuilder(String str) { super(str.length() + 16); append(str); } public StringBuilder(CharSequence seq) { this(seq.length() + 16); append(seq); }
StringBuilder provides 4 default constructors. In addition to the no-argument constructor, it also provides 3 other overloaded versions, and internally calls the super( of the parent class int capacity) construction method, its parent class is AbstractStringBuilder, the construction method is as follows:
AbstractStringBuilder(int capacity) { value = new char[capacity]; }
You can see that StringBuilder actually uses char arrays internally to store data (String, StringBuffer as well), here the value of capacity specifies the array size. Combined with the parameterless constructor of StringBuilder, you can know that the default size is 16 characters.
That is to say, if the total length of the strings to be spliced ??is not less than 16 characters, then there is not much difference between direct splicing and manually writing StringBuilder. However, we can specify the size of the array by constructing the StringBuilder class ourselves to avoid allocating too many Memory.
Now let’s take a look at what is done inside the StringBuilder.append method:
@Override public StringBuilder append(String str) { super.append(str); return this; }
The append method of the parent class that is directly called:
public AbstractStringBuilder append(String str) { if (str == null) return appendNull(); int len = str.length(); ensureCapacityInternal(count + len); str.getChars(0, len, value, count); count += len; return this; }
The ensureCapacityInternal method is called inside this method. When the total size of the spliced ??string is greater than When the size of the internal array value is determined, it must be expanded first before splicing. The expansion code is as follows:
void expandCapacity(int minimumCapacity) { int newCapacity = value.length * 2 + 2; if (newCapacity - minimumCapacity < 0) newCapacity = minimumCapacity; if (newCapacity < 0) { if (minimumCapacity < 0) // overflow throw new OutOfMemoryError(); newCapacity = Integer.MAX_VALUE; } value = Arrays.copyOf(value, newCapacity); }
StringBuilder increases the capacity to twice the current capacity + 2 during expansion. This is very scary. If it is constructed If the capacity is not specified, it is very likely that a large amount of memory space will be occupied and wasted after expansion. Secondly, the Arrays.copyOf method is called after the expansion. This method copies the data before expansion to the expanded space. The reason for this is: StringBuilder uses char arrays internally to store data. Java arrays cannot be expanded, so only It can re-apply for a memory space and copy the existing data to the new space. Here it finally calls the System.arraycopy method to copy. This is a native method. The bottom layer directly operates the memory, so it is better than using a loop to copy. There are many blocks. Even so, the impact of applying for a large amount of memory space and copying data cannot be ignored.
Comparison between using + splicing and using StringBuilder
@Test public void test() { String str = ""; for (int i = 0; i < 10000; i++) { str += "asjdkla"; } }
The above code is optimized to be equivalent to:
@Test public void test() { String str = null; for (int i = 0; i < 10000; i++) { str = new StringBuilder().append(str).append("asjdkla").toString(); } }
You can see at a glance that too many StringBuilder objects are created, and str is getting bigger and bigger after each loop. As a result, the memory space requested each time becomes larger and larger, and when the length of str is greater than 16, it must be expanded twice each time! In fact, when the toString method creates the String object, it calls the Arrays.copyOfRange method to copy the data. This This is equivalent to expanding the capacity twice and copying the data three times every time it is executed. This cost is quite high.
public void test() { StringBuilder sb = new StringBuilder("asjdkla".length() * 10000); for (int i = 0; i < 10000; i++) { sb.append("asjdkla"); } String str = sb.toString(); }
The execution time of this code is 0ms (less than 1ms) and 1ms on my machine, while the above code is about 380ms! The difference in efficiency is quite obvious.
The same code above, when adjusting the number of loops to 1,000,000, on my machine, it takes about 20ms when capacity is specified, and about 29ms when capacity is not specified. Although this difference is different from using the + operator directly It has been greatly improved (and the number of cycles has increased by 100 times), but it will still trigger multiple expansions and replications.
Change the above code to use StringBuffer. On my machine, it takes about 33ms. This is because StringBuffer adds the synchronized keyword to most methods to ensure thread safety and execution efficiency to a certain extent. of reduction.
Use String.concat to splice
Now look at this code:
@Test public void test() { String str = ""; for (int i = 0; i < 10000; i++) { str.concat("asjdkla"); } }
這段代碼使用了String.concat方法,在我的機(jī)器上,執(zhí)行時(shí)間大約為130ms,雖然直接相加要好的多,但是比起使用StringBuilder還要太多了,似乎沒(méi)什么用。其實(shí)并不是,在很多時(shí)候,我們只需要連接兩個(gè)字符串,而不是多個(gè)字符串的拼接,這個(gè)時(shí)候使用String.concat方法比StringBuilder要簡(jiǎn)潔且效率要高。
public String concat(String str) { int otherLen = str.length(); if (otherLen == 0) { return this; } int len = value.length; char buf[] = Arrays.copyOf(value, len + otherLen); str.getChars(buf, len); return new String(buf, true); }
上面這段是String.concat的源碼,在這個(gè)方法中,調(diào)用了一次Arrays.copyOf,并且指定了len + otherLen,相當(dāng)于分配了一次內(nèi)存空間,并分別從str1和str2各復(fù)制一次數(shù)據(jù)。而如果使用StringBuilder并指定capacity,相當(dāng)于分配一次內(nèi)存空間,并分別從str1和str2各復(fù)制一次數(shù)據(jù),最后因?yàn)檎{(diào)用了toString方法,又復(fù)制了一次數(shù)據(jù)。
結(jié)論
現(xiàn)在根據(jù)上面的分析和測(cè)試可以知道:
Java中字符串拼接不要直接使用+拼接。
使用StringBuilder或者StringBuffer時(shí),盡可能準(zhǔn)確地估算capacity,并在構(gòu)造時(shí)指定,避免內(nèi)存浪費(fèi)和頻繁的擴(kuò)容及復(fù)制。
在沒(méi)有線(xiàn)程安全問(wèn)題時(shí)使用StringBuilder, 否則使用StringBuffer。
兩個(gè)字符串拼接直接調(diào)用String.concat性能最好。
關(guān)于String的其他最佳實(shí)踐
用equals時(shí)總是把能確定不為空的變量寫(xiě)在左邊,如使用"".equals(str)判斷空串,避免空指針異常。
第二點(diǎn)是用來(lái)排擠第一點(diǎn)的.. 使用str != null && str.length() != 0來(lái)判斷空串,效率比第一點(diǎn)高。
在需要把其他對(duì)象轉(zhuǎn)換為字符串對(duì)象時(shí),使用String.valueOf(obj)而不是直接調(diào)用obj.toString()方法,因?yàn)榍罢咭呀?jīng)對(duì)空值進(jìn)行檢測(cè)了,不會(huì)拋出空指針異常。
使用String.format()方法對(duì)字符串進(jìn)行格式化輸出。
在JDK 7及以上版本,可以在switch結(jié)構(gòu)中使用字符串了,所以對(duì)于較多的比較,使用switch代替if-else。

Hot AI Tools

Undress AI Tool
Undress images for free

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)