Java提高篇之字符串的拼接

时间:2023-02-26 22:06:33

各种拼接方法的效率比较

import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang.StringUtils;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TestString {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Test
public void testPlus() {
String s = "";
long ts = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
s = s + String.valueOf(i);
}
long te = System.currentTimeMillis();
logger.info("+ cost {} ms", te - ts);
}
@Test
public void testConcat() {
String s = "";
long ts = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
s = s.concat(String.valueOf(i));
}
long te = System.currentTimeMillis();
logger.info("concat cost {} ms", te - ts);
}
@Test
public void testJoin() {
List<String> list = new ArrayList<String>();
long ts = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
list.add(String.valueOf(i));
}
StringUtils.join(list, "");
long te = System.currentTimeMillis();
logger.info("StringUtils.join cost {} ms", te - ts);
}
@Test
public void testStringBuffer() {
StringBuffer sb = new StringBuffer();
long ts = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
sb.append(String.valueOf(i));
}
sb.toString();
long te = System.currentTimeMillis();
logger.info("StringBuffer cost {} ms", te - ts);
}
@Test
public void testStringBuilder() {
StringBuilder sb = new StringBuilder();
long ts = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
sb.append(String.valueOf(i));
}
sb.toString();
long te = System.currentTimeMillis();
logger.info("StringBuilder cost {} ms", te - ts);
}
}

运行结果如下:
11:00:22,359 INFO TestString:23 - + cost 1828 ms
11:00:22,921 INFO TestString:34 - concat cost 562 ms
11:00:22,937 INFO TestString:46 - StringUtils.join cost 16 ms
11:00:22,968 INFO TestString:58 - StringBuffer cost 31 ms
11:00:23,031 INFO TestString:70 - StringBuilder cost 63 ms

要特别注意的是:
StringBuilder 循环的次数是其它的10倍,如果是一样,那么返回 0,可见StringBuilder 的速度之快。

总结:
用+的方式效率最差,concat由于是内部机制实现,比+的方式好了不少。
Join 和 StringBuffer,相差不大,Join方式要快些,可见这种JavaScript中快速拼接字符串的方式在Java中也非常适用。

+和concat的字节码中都有用到StringBuilder

StringBuilder 的速度最快,但其有线程安全的问题,而且只有JDK5支持。

Java中仅有的重载运算符

在Java中,唯一被重载的运算符就是字符串的拼接相关的。+,+=。除此之外,Java设计者不允许重载其他的运算符。

+拼接剖析

public class Solution {

public static void main(String [ ] args){

String userName = "Andy";
String age = "24";
String job = "Developer";
String info = userName + age + job;
System.out.println(info);

}

}

对Concatenation.java进行编译一下。得到Concatenation.class

然后我们使用javap反编译一下编译出来的Concatenation.class文件。javap -c Concatenation。如果没有找到javap命令,请考虑将javap所在目录加入环境变量或者使用javap的完整路径。

Compiled from "Solution.java"
class nowcoder.Solution {
nowcoder.Solution();
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.ap
pend:(Ljava/lang/String;)Ljava/lang/StringBuilder;
20: aload_2
21: invokevirtual #7 // Method java/lang/StringBuilder.ap
pend:(Ljava/lang/String;)Ljava/lang/StringBuilder;
24: aload_3
25: invokevirtual #7 // Method java/lang/StringBuilder.ap
pend:(Ljava/lang/String;)Ljava/lang/StringBuilder;
28: invokevirtual #8 // Method java/lang/StringBuilder.to
String:()Ljava/lang/String;
31: astore 4
33: getstatic #9 // Field java/lang/System.out:Ljava/
io/PrintStream;
36: aload 4
38: invokevirtual #10 // Method java/io/PrintStream.printl
n:(Ljava/lang/String;)V
41: return
}

其中,ldc,astore等为java字节码的指令,类似汇编指令。后面的注释使用了Java相关的内容进行了说明。 我们可以看到上面有很多StringBuilder,但是我们在Java代码里并没有显示地调用,这就是Java编译器做的优化,当Java编译器遇到字符串拼接的时候,会创建一个StringBuilder对象,后面的拼接,实际上是调用StringBuilder对象的append方法。这样就不会有我们上面担心的问题了。

注意:
当多个确定量(常量)字符串相加时(情况1),编译器直接将它们编辑为相加后的字符串,这样的情况下用“+”比StringBuilder运行时效率更高。

+与StringBuilder

public void  implicitUseStringBuilder(String[] values) {
String result = "";
for (int i = 0 ; i < values.length; i ++) {
result += values[i];
}
System.out.println(result);
}

这里面有一个很重要的就是StringBuilder对象创建发生在循环之间,也就是意味着有多少次循环会创建多少个StringBuilder对象,这样明显不好。赤裸裸地低水平代码啊。

稍微优化一下,瞬间提升逼格。

public void explicitUseStringBuider(String[] values) {
StringBuilder result = new StringBuilder();
for (int i = 0; i < values.length; i ++) {
result.append(values[i]);
}
}

这样不会多次创建StringBuilder

总的来说,我们在循环体中需要尽量避免隐式或者显式创建StringBuilder. 所以那些了解代码如何编译,内部如何执行的人,写的代码档次都比较高。