文档章节

用StringBuilder(StringBuffer)#append替代字符串”+”会带来性能提升吗

反经
 反经
发布于 2017/01/17 22:26
字数 1259
阅读 29
收藏 0

经常看到一些论坛在谈java代码优化的时候讲到要将字符串连接操作”+”换成StringBuilder(或StringBuffer,后面为简单起见,只说StringBuilder)的append操作以提升性能,那么字符串连接使用StringBuilder#append来替代”+”真的会带来性能提升吗?

不忙回答,先看几个例子,代码如下:

public class StringConcat {
	public static void main(String... args) {
		concat1();
		concat2();
		concat3();
	}
	public static void concat1() {
		String s = "today is "+ "a good day";
		System.out.println(s);
	}
	public static void concat2() {
		int count = 2;
		String tmp = " on the desk";
		String s2 = "there are "+ count + " books "+ tmp;
		System.out.println(s2);
	}
	public static void concat3() {
		String s3 = "";
		for(int i=0; i<100; i++) {
			s3 = s3 + i;
		}
		System.out.println(s3);
	}
}


接下来分别分析下这三个操作字符串的方法,通过javap命令反编译.class文件:javap -c StringConcat ,获得字节码指令如下(只摘取concat1,concat2,concat3三个方法的):

public static void concat1();
Code:
0:   ldc     #5; //String today is a good day
2:   astore_0
3:   getstatic       #6; //Field java/lang/System.out:Ljava/io/PrintStream;
6:   aload_0
7:   invokevirtual   #7; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
10:  return

public static void concat2();
Code:
0:   iconst_2
1:   istore_0
2:   ldc     #8; //String  on the desk
4:   astore_1
5:   new#9; //class java/lang/StringBuilder
8:   dup
9:   invokespecial   #10; //Method java/lang/StringBuilder."":()V
12:  ldc     #11; //String there are
14:  invokevirtual   #12; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
17:  iload_0
18:  invokevirtual   #13; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
21:  ldc     #14; //String  books
23:  invokevirtual   #12; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
26:  aload_1
27:  invokevirtual   #12; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
30:  invokevirtual   #15; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
33:  astore_2
34:  getstatic       #6; //Field java/lang/System.out:Ljava/io/PrintStream;
37:  aload_2
38:  invokevirtual   #7; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
41:  return

public static void concat3();
Code:
0:   ldc     #16; //String
2:   astore_0
3:   iconst_0
4:   istore_1
5:   iload_1
6:   bipush  100
8:   if_icmpge       36
11:  new#9; //class java/lang/StringBuilder
14:  dup
15:  invokespecial   #10; //Method java/lang/StringBuilder."":()V
18:  aload_0
19:  invokevirtual   #12; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
22:  iload_1
23:  invokevirtual   #13; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
26:  invokevirtual   #15; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
29:  astore_0
30:  iinc    1, 1
33:  goto5
36:  getstatic       #6; //Field java/lang/System.out:Ljava/io/PrintStream;
39:  aload_0
40:  invokevirtual   #7; //Method java/io/PrintStream.println:(Ljava/lang/String;)V

来分析下三个方法的字节码含义
在concat1中,是两个字面值(字符串常量)的连接,从concat1的字节码的第0条(0: ldc #5; //String today is a good day)可以看到,该方法直接从常量池加载”String today is a good day”,也就是说,String s = “today is ” + “a good day”;这条语句在编译后已经变成了一个字符串,等效于String s = “String today is a good day”,运行期间根本无需做连接操作了,所以对于字符串字面值的连接,使用StringBuilder是没有任何意义的。
在concat2中,是变量参与字符串的连接。从反编译的字节码中可以看出,编译期间已经转换成了StringBuilder的append操作,
String s2 = "there are "+ count + " books "+ tmp;
语句在编译之后已经等效于(即[5,30]之间的指令):
String s2 = newStringBuilder().append("there are ").append(count).append(" books").append(tmp).toString();
由此可见,在这样的字符串连接代码里显式使用new StringBuilder().append并不会带来性能的提升,因为String的“+”操作符在编译的时候已经被转换成new StringBuilder().append了。当然,StringBuilder可以传入一个int参数,作为其初始容量,这在生成的代码中是没法做到的,而只有程序可以控制。
最后看concat3,for循环中使用字符串连接,最后在for循环外使用连接后的字符串。字节码中的[11,29]之间是循环体,很容易发现,循环体中做了new StringBuilder的操作,字节码代表的代码含义如下:

String s3 = "";
for(int i=0; i<100; i++) {
	s3 = new StringBuilder().append(s3).append(i).toString();
}

在这种情况下,编译器的优化并不如我们的意,我们想要的优化代码是这样的:

String s3 = "";
StringBuilder tmp = new StringBuilder();
tmp.append(s3);
for(int i=0; i<100; i++) {
	tmp.append(i);
}
s3 = tmp.toString();

这对于编译器来说有些复杂了,我们需要手工才能做到。
综上三个方法的分析发现,用StringBuilder(StringBuffer)#append替代字符串”+”是否会带来性能提升并不是一成不变的,在不同的条件下情况也不相同,字符串字面值的连接在编译期间已经连接好了,普通的字符串连接并不需要显式的使用new StringBuilder().append来增加效率,编译器已经给我们做掉了,除非能在显式使用时能给出有效的初始容量。在这种意义下,个人觉得string的”+”可以认为是StringBuilder#append的一个语法糖;但是如果形如concat3那种循环中的字符串连接,我们就需要显式使用StringBuilder了。在jdk1.4的时候,还没有StringBuilder类,编译器生成的优化代码使用的是StringBuffer。
针对String连接操作编译器生成的StringBuidler#append肯定是单个线程在操作,因此不会有线程安全问题。

本文转载自:

反经
粉丝 36
博文 172
码字总数 43569
作品 0
广州
程序员
私信 提问
(转)StringBuilder与StringBuffer和String 的区别

很多人对String和StringBuffer的区别已经很了解了吧,可能还有人对这两个类的工作原理有些不清楚的地方,复习一下吧,顺便牵出J2SE 5.0(文档)里面带来的一个新的字符操作的类StringBuilder...

王振威
2012/03/08
901
1
Java基础:String、StringBuffer和StringBuilder的区别

1 String String:字符串常量,字符串长度不可变。Java中String是immutable(不可变)的。 String类的包含如下定义: 用于存放字符的数组被声明为final的,因此只能赋值一次,不可再更改。 ...

watermelon11
02/23
29
1
String,StringBuffer与StringBuilder的区别??

String 字符串常量 StringBuffer 字符串变量(线程安全) StringBuilder 字符串变量(非线程安全) 简要的说, String 类型和 StringBuffer 类型的主要性能区别其实在于 String 是不可变的对...

上班不要玩手机
2016/10/09
12
0
StringBuilder与StringBuffer的比较

很多人对String和StringBuffer的区别已经很了解了吧,可能还有人对这两个类的工作原理有些不清楚的地方,复习一下吧,顺便牵出J2SE 5.0里面带来的一个新的字符操作的类StringBuilder。那么这...

Koon.LY
2012/05/14
355
0
String、StringBuilder、StringBuffer 用法比较

String、StringBuilder、StringBuffer 三个类源自JDK的 java/lang/ 目录下: String 字符串常量 StringBuffer 字符串变量(线程安全) StringBuilder 字符串变量(非线程安全,JDK 5.0(1.5.0...

长平狐
2013/01/06
146
0

没有更多内容

加载失败,请刷新页面

加载更多

米联客(MSXBO) 基于VIVADO实现FPGA时序笔记之概述(一)

FPGA时序要满足要求,这个基本原理大家基本都知道,但是如何使用VIVADO IDE工具进行时序设计、时序分析、判断时序是否满足要求,这个对很多FPGA工程师来说,还是比较抽象,因为时序分析的工具...

msxbo
32分钟前
5
0
Centos7 命令行下kvm安装windows,linux

查看是否支持 egrep "svm|vmx" /proc/cpuinfo |uniq 安装软件 yum install libvirt -yyum -y install qemu-kvmsystemctl enable libvirtd && systemctl start libvirtd# 启动lib......

以谁为师
33分钟前
7
0
源码分析 RocketMQ DLedger(多副本) 之日志追加流程

上一篇我们详细分析了源码分析 RocketMQ DLedger 多副本之 Leader 选主,本文将详细分析日志复制的实现。 根据 raft 协议可知,当整个集群完成 Leader 选主后,集群中的主节点就可以接受客户...

中间件兴趣圈
46分钟前
3
0
基于k8s的Ingress部署hexo博客(http和https)

注:kuberntes版本为1.15 什么是 Ingress Ingress 是一个提供对外服务的路由和负载均衡器,其本质是个nginx控制器服务。 k8s文档上Ingress经典数据链路图: internet | [ In...

Kanonpy
今天
7
0
LNMP---访问控制

访问控制 扩展: curl命令用法: curl -v -A 'aaaaaspider/3.0' -e "1111" -x127.0.0.1:80 discuz.tobe.com -I -A 指定user-agent -e 指定referer -x 指定访问目标服务器的ip和por......

tobej
今天
9
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部