最近没事在玩ASM框架,于是乎想将业务代码中的PO对象中的toString方法 在编译期间,自动转换了基于StringBuilder 拼接的代码。发现了一个奇怪的问题: 实体类如下
@Getter
@Setter
@EqualsAndHashCode(of = "id")
@ApiModel("活动")
public class Banner implements Serializable{
private static final long serialVersionUID = 191609922585601269L;
@ApiModelProperty(value = "ID", position = 1)
private Integer id;
@ApiModelProperty(value = "显示次序", position = 2)
private Integer orderNo;
@ApiModelProperty(value = "关联文件", position = 3)
private Integer fileId;
@ApiModelProperty(value = "跳转链接", position = 4)
private String forwardLink = "";
@ApiModelProperty(value = "创建时间", position = 5)
private Long createDateline;
@ApiModelProperty(value = "是否可用:1 可用,0 不可用", position = 6)
private Integer isenable;
@ApiModelProperty(value = "标题", position = 7)
private String title = "";
@ApiModelProperty(value = "备注", position = 8)
private String remark = "";
@ApiModelProperty(value = "banner类型:1.抢单app 2.借款端app", position = 9)
private Integer bannerType;
@ApiModelProperty(value = "图片链接", position = 10)
private String url = "";
@Override
public String toString() {
return "Banner{" + "id=" + id + ", orderNo=" + orderNo + ", fileId=" + fileId + ", forwardLink='" + forwardLink
+ '\'' + ", createDateline=" + createDateline + ", isenable=" + isenable + ", title='" + title + '\''
+ ", remark='" + remark + '\'' + ", bannerType=" + bannerType + ", url='" + url + '\'' + '}';
}
}
我的目的是将toString方法变成StringBuilder的方式:
@Override
public String toString() {
final StringBuilder sb = new StringBuilder(1 << 8);
sb.append("Banner{");
sb.append("id=").append(id);
sb.append(", orderNo=").append(orderNo);
sb.append(", fileId=").append(fileId);
sb.append(", forwardLink=").append(forwardLink);
sb.append(", createDateline=").append(createDateline);
sb.append(", isenable=").append(isenable);
sb.append(", title=").append(title);
sb.append(", remark=").append(remark);
sb.append(", bannerType=").append(bannerType);
sb.append(", url=").append(url);
sb.append('}');
return sb.toString();
}
我想基于修改字节码来实现它,于是乎我对里了前后两者的字节码差异通过beyondCompare工具比较: 上面红色部分就是ASM解析class文件出来的差异性字节码 可是看第90行代码的时候,突然发现 字符串拼接 + 其底层的实现竟然用的StringBuilder,凌乱了。java编译器做了优化 既然这样为什么大家还建议使用StringBuilder, 直接使用 + 拼接好了么,出于无法理解,我测了下字符串凭借不同方式的效率
public class Person {
private String name = "1";
private int age = 2;
@Override
public String toString() {
testAdd(100000);
testConcat(100000);
testStringBuilder(100000);
return "";
}
public static void main(String args[]) {
Person p = new Person();
p.toString();
}
public static void testAdd(int num){
long start = System.currentTimeMillis();
String str = "";
for(int i = 0; i < num; i++){
str += i;
}
System.out.println("字符串拼接使用 + 耗时:" + (System.currentTimeMillis() - start) + "ms");
}
public static void testConcat(int num){
long start = System.currentTimeMillis();
String str = "";
for(int i = 0; i < num; i++){
str.concat(String.valueOf(i));
}
System.out.println("字符串拼接使用 concat 耗时:" + (System.currentTimeMillis() - start) + "ms");
}
public static void testStringBuffer(int num){
long start = System.currentTimeMillis();
StringBuffer stringBuffer = new StringBuffer();
for(int i = 0; i < num; i++){
stringBuffer.append(String.valueOf(i));
}
stringBuffer.toString();
System.out.println("字符串拼接使用 StringBuffer 耗时:" + (System.currentTimeMillis() - start) + "ms");
}
public static void testStringBuilder(int num){
long start = System.currentTimeMillis();
StringBuilder stringBuilder = new StringBuilder();
for(int i = 0; i < num; i++){
stringBuilder.append(String.valueOf(i));
}
stringBuilder.toString();
System.out.println("字符串拼接使用 StringBuilder 耗时:" + (System.currentTimeMillis() - start) + "ms");
}
}
代码网上随便copy的,就测试下 +拼接 , concat拼接, StringBuilder拼接 测试结果让我更疑惑了:
字符串拼接使用 + 耗时:30117ms
字符串拼接使用 concat 耗时:11ms
字符串拼接使用 StringBuilder 耗时:7ms
这是在逗我么,既然 + 做了优化这么效率差这么多,编译器开发人员不可能做无用功的,再次看了下字节码
public static testAdd(I)V
L0
LINENUMBER 27 L0
INVOKESTATIC java/lang/System.currentTimeMillis ()J
LSTORE 1
L1
LINENUMBER 28 L1
LDC ""
ASTORE 3
L2
LINENUMBER 29 L2
ICONST_0
ISTORE 4
L3
FRAME APPEND [J java/lang/String I]
ILOAD 4
ILOAD 0
IF_ICMPGE L4
L5
LINENUMBER 30 L5
NEW java/lang/StringBuilder
DUP
INVOKESPECIAL java/lang/StringBuilder.<init> ()V
ALOAD 3
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
ILOAD 4
INVOKEVIRTUAL java/lang/StringBuilder.append (I)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
ASTORE 3
L6
LINENUMBER 29 L6
IINC 4 1
GOTO L3
L4
LINENUMBER 32 L4
FRAME CHOP 1
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
NEW java/lang/StringBuilder
DUP
INVOKESPECIAL java/lang/StringBuilder.<init> ()V
LDC "\u5b57\u7b26\u4e32\u62fc\u63a5\u4f7f\u7528 + \u8017\u65f6\uff1a"
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
INVOKESTATIC java/lang/System.currentTimeMillis ()J
LLOAD 1
LSUB
INVOKEVIRTUAL java/lang/StringBuilder.append (J)Ljava/lang/StringBuilder;
LDC "ms"
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L7
LINENUMBER 33 L7
RETURN
L8
LOCALVARIABLE i I L3 L4 4
LOCALVARIABLE num I L0 L8 0
LOCALVARIABLE start J L1 L8 1
LOCALVARIABLE str Ljava/lang/String; L2 L8 3
MAXSTACK = 6
MAXLOCALS = 5
发现 + 拼接 每次循环 GOTO L3 接下来继续创建在L5环节中new一个StringBuilder不断在生成新的StringBuilder; 而StringBuilder的没有
public static testStringBuilder(I)V
L0
LINENUMBER 55 L0
INVOKESTATIC java/lang/System.currentTimeMillis ()J
LSTORE 1
L1
LINENUMBER 56 L1
NEW java/lang/StringBuilder
DUP
INVOKESPECIAL java/lang/StringBuilder.<init> ()V
ASTORE 3
L2
LINENUMBER 57 L2
ICONST_0
ISTORE 4
L3
FRAME APPEND [J java/lang/StringBuilder I]
ILOAD 4
ILOAD 0
IF_ICMPGE L4
L5
LINENUMBER 58 L5
ALOAD 3
ILOAD 4
INVOKESTATIC java/lang/String.valueOf (I)Ljava/lang/String;
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
POP
L6
LINENUMBER 57 L6
IINC 4 1
GOTO L3
L4
LINENUMBER 60 L4
FRAME CHOP 1
ALOAD 3
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
POP
L7
LINENUMBER 61 L7
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
NEW java/lang/StringBuilder
DUP
INVOKESPECIAL java/lang/StringBuilder.<init> ()V
LDC "\u5b57\u7b26\u4e32\u62fc\u63a5\u4f7f\u7528 StringBuilder \u8017\u65f6\uff1a"
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
INVOKESTATIC java/lang/System.currentTimeMillis ()J
LLOAD 1
LSUB
INVOKEVIRTUAL java/lang/StringBuilder.append (J)Ljava/lang/StringBuilder;
LDC "ms"
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L8
LINENUMBER 62 L8
RETURN
L9
LOCALVARIABLE i I L3 L4 4
LOCALVARIABLE num I L0 L9 0
LOCALVARIABLE start J L1 L9 1
LOCALVARIABLE stringBuilder Ljava/lang/StringBuilder; L2 L9 3
MAXSTACK = 6
MAXLOCALS = 5
循环每次 GOTO L3 在L3之前已经创建了StringBuilder对象,所以一直使用同一个对象,从而减少GC成本
结论 >因为在编写代码中可能涉及到循环体内的字符串拼接,所以还是建议使用StringBuilder并且要指定初始化容量
[评论][COMMENTS]