1、背景

    话说jdk1.5以前字符串拼接如果使用“+”号时会消耗性能和内存,因为每次使用“+”号编译的时候都会转换出多个String对象(String对象是不可变的),所以都推荐使用StringBuilder。而jdk1.5之后,编译器对“+”号做了改进,会自动转换成StringBuilder再构造String对象,使得字符串拼接变得更方便,然而就代表着程序员可以随便使用"+"号而不考虑性能问题么?答案是:做梦吧。

2、“+”号照妖镜

    既然我们所见的“+”号并不是“+”号,那我们通过反编译apk文件来看看“+”号的本质是什么?

首先先看简单的字符串拼接:

String result = "1111"+"2222"+"3333"+"4444";

编译后变成如下smali代码
    const-string v0, "1111222233334444"
    .local v0, "result":Ljava/lang/String;

这种直接拼接字符串常量的用法,编译器已经帮你合成好了,"1111"+"2222"+"3333"+"4444"跟"1111222233334444"编译后是一模一样的。

如果他们中出了个叛徒呢?把"2222"改成数字 2222

String result = "1111"+2222+"3333"+"4444";

编译后如下:
    const-string v0, "1111222233334444"

    .local v0, "result":Ljava/lang/String;

发现结果也是一样,说明编译器会把数字2222一起编译到同一个字符串中

StringBuilder正在热身.... ....

如果我们不在一行拼接完字符串,而是分几次拼接呢?

String result = "1111";
result = result+"2222";
result = result+"3333";
result = result+"4444";

编译后:
    .line 26
    const-string v0, "1111"

    .line 27
    .local v0, "result":Ljava/lang/String;
    new-instance v1, Ljava/lang/StringBuilder;

    invoke-direct {v1}, Ljava/lang/StringBuilder;-><init>()V

    invoke-virtual {v1, v0}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

    const-string v2, "2222"

    invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

    invoke-virtual {v1}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;

    move-result-object v0

    .line 28
    new-instance v1, Ljava/lang/StringBuilder;

    invoke-direct {v1}, Ljava/lang/StringBuilder;-><init>()V

    invoke-virtual {v1, v0}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

    const-string v2, "3333"

    invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

    invoke-virtual {v1}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;

    move-result-object v0
.line 29
    new-instance v1, Ljava/lang/StringBuilder;

    invoke-direct {v1}, Ljava/lang/StringBuilder;-><init>()V

    invoke-virtual {v1, v0}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

    const-string v2, "4444"

    invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

    invoke-virtual {v1}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;

    move-result-object v0

额,“+”号现在是转换成了StringBuilder了,可是每个“+”号都做了 new StringBuilder、append、toString,三倍的操作,这种情况下会导致性能和内存倍增

如果我们把字符串变量在一行拼接起来呢?

String str2 = "2222";
String str3 = "3333";
String str4 = "4444";
String result = "1111"+str2+str3+str4;

编译后:

.line 48
    const-string v0, "2222"

    .line 49
    .local v0, "str2":Ljava/lang/String;
    const-string v1, "3333"

    .line 50
    .local v1, "str3":Ljava/lang/String;
    const-string v2, "4444"

    .line 51
    .local v2, "str4":Ljava/lang/String;
    new-instance v3, Ljava/lang/StringBuilder;

    invoke-direct {v3}, Ljava/lang/StringBuilder;-><init>()V

    const-string v4, "1111"

    invoke-virtual {v3, v4}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

    invoke-virtual {v3, v0}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

    invoke-virtual {v3, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

    invoke-virtual {v3, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

    invoke-virtual {v3}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;

    move-result-object v3

    .line 52
    .local v3, "result":Ljava/lang/String;

这种情况下编译后,只new 一个 StringBuilder,append 4次,toString一次

3、结论和建议

    通过上面的源码和编译后smali比对后我们发现字符串相加的编译器处理有以下规律:

1、如果在一个表达式中连续“+”了多个字符串常量,编译器会把这些连续的字符串常量自动拼接成一个("1"+"2"+"3"+"4"==>"1234")

2、如果出现了数字常量,编译器会自动转换成字符串常量

3、如果在一个表达式中出现了字符串变量,这个时候编译器会new StringBuilder、append、toString,每个表达式至少都会走一遍这三个操作

4、如果在一个表达式码连续“+”多个字符串,只会new 一个 StringBuilder,多次append,且只有一次toString

建议如下:

1、如果可以提高代码可读性,一个表达式中的字符串常量相加并不会增加性能损耗,这个时候推荐用“+”

2、尽量将所有的字符串变量能在一个表达式中“+”完

3、如果在一个表达式“+”不完的情况下,推荐使用StringBuilder