- 浏览: 515594 次
- 性别:
- 来自: 杭州
文章分类
最新评论
-
ben_wu007:
没数据库设计 而且这样要写代码 还是做成配数据库好 ...
使用AOP做权限控制 -
邢邢色色:
支持楼主,但这本书没有讲trident,有些过时了~到amaz ...
《Storm入门》中文版 -
java_web_hack1:
我在FunctionProvider中,获取的Property ...
在Osworkflow中使用PropertySet存储业务数据 -
greemranqq:
腾飞 ~。~
Java并发和多线程译者征集 -
fantasy:
leonevo 写道hi, 我也在设计cmdb. 我觉得基于传 ...
ITSM-CMDB数据库设计-四种方案任你选
在JAVA6中,编译器会始终对字符串的累加操作做优化编译。
编译器将字符串累加编译成StringBuilder。
源代码 |
String aa = "aa"; String bb = "bb"; String cc = "cc"; String result = aa + bb + cc;
编译后 |
NEW StringBuilder DUP ALOAD 1: aa INVOKESTATIC String.valueOf(Object) : String INVOKESPECIAL StringBuilder.<init>(String) : void ALOAD 2: bb INVOKEVIRTUAL StringBuilder.append(String) : StringBuilder ALOAD 3: cc INVOKEVIRTUAL StringBuilder.append(String) : StringBuilder INVOKEVIRTUAL StringBuilder.toString() : String ASTORE 4: result
编译器将字符串累加编译成一个字符串
源代码 |
String s="aa"+"bb"+"cc";
编译后 |
LDC "aabbcc"
最佳实践:先拼接字符串再拼接变量
从上可知第二种字符串累加的效率更高。
aa + "bb"+"cc" 效率小于 "bb"+"cc"+aa。
因为"bb"+"cc"会先编译为"bbcc"。而aa + "bb" + "cc"中出现变量,在变量后的字符串就会编译成StringBuilder。
源代码 |
String aa = "aa"; String result3 = aa + "bb"+"cc"; String result4 = "bb"+"cc"+aa;
编译后 |
L0 (0) LDC "aa" ASTORE 1: aa L1 (3) NEW StringBuilder DUP ALOAD 1: aa INVOKESTATIC String.valueOf(Object) : String INVOKESPECIAL StringBuilder.<init>(String) : void LDC "bb" INVOKEVIRTUAL StringBuilder.append(String) : StringBuilder LDC "cc" INVOKEVIRTUAL StringBuilder.append(String) : StringBuilder INVOKEVIRTUAL StringBuilder.toString() : String ASTORE 2: result3 L2 (15) NEW StringBuilder DUP LDC "bbcc" INVOKESPECIAL StringBuilder.<init>(String) : void ALOAD 1: aa INVOKEVIRTUAL StringBuilder.append(String) : StringBuilder INVOKEVIRTUAL StringBuilder.toString() : String ASTORE 3: result4 L3 (24) RETURN L4 (26)
最佳实践:在循环中不要使用累加操作
因为StringBuilder是在循环内创建的。建议手动在外面创建StringBuilder。
StringBuilder eqPhone = new StringBuilder(); // 将#转换为- for (String phoneNum : wwPhone.split("#")) { if (StringUtil.isNotBlank(phoneNum)) { eqPhone.append(phoneNum).append("-"); } }
源代码:
public static void a() { String s1="1"; for (int i = 0; i <10; i++) { s1+="2"; } }字节码:
public static void a(); 0 ldc <String "1"> [56] 2 astore_0 [s1] 3 iconst_0 4 istore_1 [i] 5 goto 31 8 new java.lang.StringBuilder [54] //在循环里创建对象 11 dup 12 aload_0 [s1] 13 invokestatic java.lang.String.valueOf(java.lang.Object) : java.lang.String [77] 16 invokespecial java.lang.StringBuilder(java.lang.String) [58] 19 ldc <String "2"> [69] 21 invokevirtual java.lang.StringBuilder.append(java.lang.String) : java.lang.StringBuilder [61] 24 invokevirtual java.lang.StringBuilder.toString() : java.lang.String [65] 27 astore_0 [s1] 28 iinc 1 1 [i] 31 iload_1 [i] 32 bipush 10 34 if_icmplt 8 //跳转到第8行 37 return
分析工具java asm
评论
30 楼
weimingtom
2011-05-15
用拼接可以在调试时注释掉字符串的一部分。
个人觉得能不用拼接就尽量不用。
个人觉得能不用拼接就尽量不用。
29 楼
fantasy
2011-05-15
xly_971223 写道
有人会这么写吗? "aa" + "bb"
直接“aabb” 不好吗
直接“aabb” 不好吗
当拼接复杂SQL语句的时候会。
当拼接日志的时候会。
这样做主要是让代码更加易读,如果直接写在同一行里,超过一定的数量,java编辑器出现横向滚动条。
那么当你看代码的时候既要竖向托滚动条,又要横向拖动滚动条。
如:
String sql = "select ... from user" +" left out join userGroup ....." +" left out join B ....." +" where ..." +" order by user_name,.. desc";
28 楼
ljl_ss
2011-05-15
有意思吗码农们?
27 楼
xly_971223
2011-05-15
有人会这么写吗? "aa" + "bb"
直接“aabb” 不好吗
直接“aabb” 不好吗
26 楼
bitray
2011-05-14
不要光看这些优化的,你亲自写个循环20万的试试,立竿见影.
花时间研究理论如果一辈子用不上的话,不如亲自上手试试,你还能记住一辈子
花时间研究理论如果一辈子用不上的话,不如亲自上手试试,你还能记住一辈子
25 楼
徐风子
2011-05-13
看了这篇文章,忽然想试试另外的编译器优化:
编译后是什么样子?
相当于:
所有,要尽量使用常量。
PS: java编译器到现在还不支持尾递归,强烈BS之……
final String a = "aa"; final String b = "bb"; final String h = a + b; System.out.println(h); System.out.println(h + "cc");
编译后是什么样子?
0 ldc [16] 2 astore_1 [a] 3 ldc [18] 5 astore_2 [b] 6 ldc [20] 8 astore_3 [h] 9 getstatic java.lang.System.out : java.io.PrintStream [22] 12 ldc [20] 14 invokevirtual java.io.PrintStream.println(java.lang.String) : void [28] 17 getstatic java.lang.System.out : java.io.PrintStream [22] 20 ldc [34] 22 invokevirtual java.io.PrintStream.println(java.lang.String) : void [28]
相当于:
final String a = "aa"; final String b = "bb"; final String h = "aabb"; System.out.println("aabb"); System.out.println("aabbcc");
所有,要尽量使用常量。
PS: java编译器到现在还不支持尾递归,强烈BS之……
24 楼
徐风子
2011-05-13
jdk 一直都有这个功能,不要把编译器想得那么笨,
这个应该是编译器文档里面承诺了会这样做的,所以不会担心编译器依赖问题。
这个应该是编译器文档里面承诺了会这样做的,所以不会担心编译器依赖问题。
23 楼
RednaxelaFX
2011-05-13
miaofm 写道
fantasy 写道
String s="aa"+"bb"+"cc";
这个干嘛不直接写成
String s="aabbcc";
因为实际的状况可能是:
String message = "Crusader 写道\n" + "这个讨论意义不大\n" + "代码不应该依赖于jdk版本或者JVM实现\n" + "否则可移植性太差了";
22 楼
miaofm
2011-05-13
<div class="quote_title">fantasy 写道</div>
<div class="quote_div">String s="aa"+"bb"+"cc";<br>
</div>
<p> </p>
<p><span style="background-color: #fafafa;">这个干嘛不直接写成
<div class="quote_div">String s="aabbcc";<br>
</div>
</span></p>
<div class="quote_div">String s="aa"+"bb"+"cc";<br>
</div>
<p> </p>
<p><span style="background-color: #fafafa;">这个干嘛不直接写成
<div class="quote_div">String s="aabbcc";<br>
</div>
</span></p>
21 楼
kill_all
2011-05-13
解释的真详细,thx
20 楼
fantasy
2011-05-13
<div class="quote_title">Crusader 写道</div>
<div class="quote_div">
<div class="quote_title">skzr.org 写道</div>
<div class="quote_div">
<div class="quote_title">qianhd 写道</div>
<div class="quote_div">不要写任何依赖语法糖 依赖编译器的代码 <br>久而久之 你就总会以为有虚拟机给你优化 <br>这是不好的习惯</div>
<p> </p>
<div class="quote_title">Crusader 写道</div>
<div class="quote_div">这个讨论意义不大<br>代码不应该依赖于jdk版本或者JVM实现<br>否则可移植性太差了</div>
<p><br><br>两位有点过于追求完美了,你不会从jdk1.6切换会jdk1.4以下吧。<br><br>我指导得最多的就是不要在简单的sql拼接时使用过多stringbuild,比如:<br> execute("select * from " + tableName + " where id=?", 1);<br><br>有人过度强调原则,如前两位,结果写了<br> execute(<br> new StringBuilder("select * from ").append(tableName).append(" where id=?"), 1);<br><br>如果给人看还是<span style="color: #ff0000;"><strong>直接+更加容易读</strong></span><br><br>所以需要权衡,我的一般原则:<br> <span style="color: #ff0000; font-size: medium;"><strong>1. 循环中操作字符串,使用StringBuilder<br> 2. 如果字符串特别长,同时还不能一次使用+完成所有操作 时使用StringBuilder</strong></span><br><br>这个只是个人经验。</p>
<p> </p>
</div>
<p>如果只是为了易读性,个人认为得不尝失</p>
<p>而且并不一定是为了向前兼容,也可能是平行,向后,谁也能保证JVM(不同厂商,不同版本)会不会优化,如何优化</p>
</div>
<p>如果觉得使用StringBuilder来进行累加不麻烦,其实也可以,虽然易读性差点。就怕人偷懒写成这样就悲剧了。new StringBuilder("select * from ").append(tableName).append(" where id in<span style="color: #ff0000;"> ("+userIdString+")"</span>);这样让编译器情何以堪!</p>
<div class="quote_div">
<div class="quote_title">skzr.org 写道</div>
<div class="quote_div">
<div class="quote_title">qianhd 写道</div>
<div class="quote_div">不要写任何依赖语法糖 依赖编译器的代码 <br>久而久之 你就总会以为有虚拟机给你优化 <br>这是不好的习惯</div>
<p> </p>
<div class="quote_title">Crusader 写道</div>
<div class="quote_div">这个讨论意义不大<br>代码不应该依赖于jdk版本或者JVM实现<br>否则可移植性太差了</div>
<p><br><br>两位有点过于追求完美了,你不会从jdk1.6切换会jdk1.4以下吧。<br><br>我指导得最多的就是不要在简单的sql拼接时使用过多stringbuild,比如:<br> execute("select * from " + tableName + " where id=?", 1);<br><br>有人过度强调原则,如前两位,结果写了<br> execute(<br> new StringBuilder("select * from ").append(tableName).append(" where id=?"), 1);<br><br>如果给人看还是<span style="color: #ff0000;"><strong>直接+更加容易读</strong></span><br><br>所以需要权衡,我的一般原则:<br> <span style="color: #ff0000; font-size: medium;"><strong>1. 循环中操作字符串,使用StringBuilder<br> 2. 如果字符串特别长,同时还不能一次使用+完成所有操作 时使用StringBuilder</strong></span><br><br>这个只是个人经验。</p>
<p> </p>
</div>
<p>如果只是为了易读性,个人认为得不尝失</p>
<p>而且并不一定是为了向前兼容,也可能是平行,向后,谁也能保证JVM(不同厂商,不同版本)会不会优化,如何优化</p>
</div>
<p>如果觉得使用StringBuilder来进行累加不麻烦,其实也可以,虽然易读性差点。就怕人偷懒写成这样就悲剧了。new StringBuilder("select * from ").append(tableName).append(" where id in<span style="color: #ff0000;"> ("+userIdString+")"</span>);这样让编译器情何以堪!</p>
19 楼
ak121077313
2011-05-13
<div class="quote_title">fantasy 写道</div>
<div class="quote_div">
<p>请仔细看贴。“在循环中不要使用累加操作”</p>
<p> </p>
</div>
<p>没有看到这个。 而且我只关心循环中累加操作的效率。其他非循环的地方无所谓</p>
<div class="quote_div">
<p>请仔细看贴。“在循环中不要使用累加操作”</p>
<p> </p>
</div>
<p>没有看到这个。 而且我只关心循环中累加操作的效率。其他非循环的地方无所谓</p>
18 楼
Crusader
2011-05-13
<div class="quote_title">skzr.org 写道</div>
<div class="quote_div">
<div class="quote_title">qianhd 写道</div>
<div class="quote_div">不要写任何依赖语法糖 依赖编译器的代码 <br>久而久之 你就总会以为有虚拟机给你优化 <br>这是不好的习惯</div>
<p> </p>
<div class="quote_title">Crusader 写道</div>
<div class="quote_div">这个讨论意义不大<br>代码不应该依赖于jdk版本或者JVM实现<br>否则可移植性太差了</div>
<p><br><br>两位有点过于追求完美了,你不会从jdk1.6切换会jdk1.4以下吧。<br><br>我指导得最多的就是不要在简单的sql拼接时使用过多stringbuild,比如:<br> execute("select * from " + tableName + " where id=?", 1);<br><br>有人过度强调原则,如前两位,结果写了<br> execute(<br> new StringBuilder("select * from ").append(tableName).append(" where id=?"), 1);<br><br>如果给人看还是<span style="color: #ff0000;"><strong>直接+更加容易读</strong></span><br><br>所以需要权衡,我的一般原则:<br> <span style="color: #ff0000; font-size: medium;"><strong>1. 循环中操作字符串,使用StringBuilder<br> 2. 如果字符串特别长,同时还不能一次使用+完成所有操作 时使用StringBuilder</strong></span><br><br>这个只是个人经验。</p>
<p> </p>
</div>
<p>如果只是为了易读性,个人认为得不尝失</p>
<p>而且并不一定是为了向前兼容,也可能是平行,向后,谁也能保证JVM(不同厂商,不同版本)会不会优化,如何优化</p>
<div class="quote_div">
<div class="quote_title">qianhd 写道</div>
<div class="quote_div">不要写任何依赖语法糖 依赖编译器的代码 <br>久而久之 你就总会以为有虚拟机给你优化 <br>这是不好的习惯</div>
<p> </p>
<div class="quote_title">Crusader 写道</div>
<div class="quote_div">这个讨论意义不大<br>代码不应该依赖于jdk版本或者JVM实现<br>否则可移植性太差了</div>
<p><br><br>两位有点过于追求完美了,你不会从jdk1.6切换会jdk1.4以下吧。<br><br>我指导得最多的就是不要在简单的sql拼接时使用过多stringbuild,比如:<br> execute("select * from " + tableName + " where id=?", 1);<br><br>有人过度强调原则,如前两位,结果写了<br> execute(<br> new StringBuilder("select * from ").append(tableName).append(" where id=?"), 1);<br><br>如果给人看还是<span style="color: #ff0000;"><strong>直接+更加容易读</strong></span><br><br>所以需要权衡,我的一般原则:<br> <span style="color: #ff0000; font-size: medium;"><strong>1. 循环中操作字符串,使用StringBuilder<br> 2. 如果字符串特别长,同时还不能一次使用+完成所有操作 时使用StringBuilder</strong></span><br><br>这个只是个人经验。</p>
<p> </p>
</div>
<p>如果只是为了易读性,个人认为得不尝失</p>
<p>而且并不一定是为了向前兼容,也可能是平行,向后,谁也能保证JVM(不同厂商,不同版本)会不会优化,如何优化</p>
17 楼
skzr.org
2011-05-13
<div class="quote_title">qianhd 写道</div>
<div class="quote_div">不要写任何依赖语法糖 依赖编译器的代码 <br>久而久之 你就总会以为有虚拟机给你优化 <br>这是不好的习惯</div>
<p><br></p>
<div class="quote_title">Crusader 写道</div>
<div class="quote_div">这个讨论意义不大<br>代码不应该依赖于jdk版本或者JVM实现<br>否则可移植性太差了</div>
<p><br><br>两位有点过于追求完美了,你不会从jdk1.6切换会jdk1.4以下吧。<br><br>我指导得最多的就是不要在简单的sql拼接时使用过多stringbuild,比如:<br> execute("select * from " + tableName + " where id=?", 1);<br><br>有人过度强调原则,如前两位,结果写了<br> execute(<br> new StringBuilder("select * from ").append(tableName).append(" where id=?"), 1);<br><br>如果给人看还是<span style="color: #ff0000;"><strong>直接+更加容易读</strong></span><br><br>所以需要权衡,我的一般原则:<br> <span style="color: #ff0000; font-size: medium;"><strong>1. 循环中操作字符串,使用StringBuilder<br> 2. 如果字符串特别长,同时还不能一次使用+完成所有操作 时使用StringBuilder</strong></span><br><br>这个只是个人经验。</p>
<p> </p>
<div class="quote_div">不要写任何依赖语法糖 依赖编译器的代码 <br>久而久之 你就总会以为有虚拟机给你优化 <br>这是不好的习惯</div>
<p><br></p>
<div class="quote_title">Crusader 写道</div>
<div class="quote_div">这个讨论意义不大<br>代码不应该依赖于jdk版本或者JVM实现<br>否则可移植性太差了</div>
<p><br><br>两位有点过于追求完美了,你不会从jdk1.6切换会jdk1.4以下吧。<br><br>我指导得最多的就是不要在简单的sql拼接时使用过多stringbuild,比如:<br> execute("select * from " + tableName + " where id=?", 1);<br><br>有人过度强调原则,如前两位,结果写了<br> execute(<br> new StringBuilder("select * from ").append(tableName).append(" where id=?"), 1);<br><br>如果给人看还是<span style="color: #ff0000;"><strong>直接+更加容易读</strong></span><br><br>所以需要权衡,我的一般原则:<br> <span style="color: #ff0000; font-size: medium;"><strong>1. 循环中操作字符串,使用StringBuilder<br> 2. 如果字符串特别长,同时还不能一次使用+完成所有操作 时使用StringBuilder</strong></span><br><br>这个只是个人经验。</p>
<p> </p>
16 楼
Crusader
2011-05-13
这个讨论意义不大
代码不应该依赖于jdk版本或者JVM实现
否则可移植性太差了
代码不应该依赖于jdk版本或者JVM实现
否则可移植性太差了
15 楼
coffeesweet
2011-05-13
RednaxelaFX 每次都是终结者
14 楼
fantasy
2011-05-13
<div class="quote_title">ak121077313 写道</div>
<div class="quote_div">
<p> </p>
<pre name="code" class="java"> public static void main(String[] args) {
StringBuffer sb = new StringBuffer();
long l3 = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
sb.append("a").append(i);
}
long l4 = System.currentTimeMillis();
System.out.println(l4 - l3);
System.out.println(sb.length());
String s = "";
long l = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
s = s + "a" + i;
}
long l2 = System.currentTimeMillis();
System.out.println(l2 - l);
System.out.println(s.length());
}</pre>
<p> </p>
<p>结果:</p>
<p>stringbuffer:</p>
<p> </p>
<p style="display: inline !important;">0</p>
<p>48890</p>
<p> </p>
<p>String:</p>
<p>1281</p>
<p>48890</p>
<p> </p>
<p>jdk1.6</p>
<p> </p>
<p>根本就没优化过啊啊,或者说优化之后也是这个鸟样!坑爹呐</p>
</div>
<p>请仔细看贴。“在循环中不要使用累加操作”</p>
<p> </p>
<div class="quote_div">
<p> </p>
<pre name="code" class="java"> public static void main(String[] args) {
StringBuffer sb = new StringBuffer();
long l3 = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
sb.append("a").append(i);
}
long l4 = System.currentTimeMillis();
System.out.println(l4 - l3);
System.out.println(sb.length());
String s = "";
long l = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
s = s + "a" + i;
}
long l2 = System.currentTimeMillis();
System.out.println(l2 - l);
System.out.println(s.length());
}</pre>
<p> </p>
<p>结果:</p>
<p>stringbuffer:</p>
<p> </p>
<p style="display: inline !important;">0</p>
<p>48890</p>
<p> </p>
<p>String:</p>
<p>1281</p>
<p>48890</p>
<p> </p>
<p>jdk1.6</p>
<p> </p>
<p>根本就没优化过啊啊,或者说优化之后也是这个鸟样!坑爹呐</p>
</div>
<p>请仔细看贴。“在循环中不要使用累加操作”</p>
<p> </p>
13 楼
ak121077313
2011-05-13
循环10W次
stringbuffer耗时:31ms
string:等不下去了
stringbuffer耗时:31ms
string:等不下去了
12 楼
ak121077313
2011-05-13
<p>
</p>
<pre name="code" class="java"> public static void main(String[] args) {
StringBuffer sb = new StringBuffer();
long l3 = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
sb.append("a").append(i);
}
long l4 = System.currentTimeMillis();
System.out.println(l4 - l3);
System.out.println(sb.length());
String s = "";
long l = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
s = s + "a" + i;
}
long l2 = System.currentTimeMillis();
System.out.println(l2 - l);
System.out.println(s.length());
}</pre>
<p> </p>
<p>结果:</p>
<p>stringbuffer:</p>
<p>
</p>
<p style="display: inline !important;">0</p>
<p>48890</p>
<p> </p>
<p>String:</p>
<p>1281</p>
<p>48890</p>
<p> </p>
<p>jdk1.6</p>
<p> </p>
<p>根本就没优化过啊啊,或者说优化之后也是这个鸟样!坑爹呐</p>
</p>
<pre name="code" class="java"> public static void main(String[] args) {
StringBuffer sb = new StringBuffer();
long l3 = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
sb.append("a").append(i);
}
long l4 = System.currentTimeMillis();
System.out.println(l4 - l3);
System.out.println(sb.length());
String s = "";
long l = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
s = s + "a" + i;
}
long l2 = System.currentTimeMillis();
System.out.println(l2 - l);
System.out.println(s.length());
}</pre>
<p> </p>
<p>结果:</p>
<p>stringbuffer:</p>
<p>
</p>
<p style="display: inline !important;">0</p>
<p>48890</p>
<p> </p>
<p>String:</p>
<p>1281</p>
<p>48890</p>
<p> </p>
<p>jdk1.6</p>
<p> </p>
<p>根本就没优化过啊啊,或者说优化之后也是这个鸟样!坑爹呐</p>
11 楼
fantasy
2011-05-13
RednaxelaFX 写道
javabkb 写道
听过sajia老师讲的视频 记得1.4后的hotspot就有这个编译优化效果了
<< 我肯定没有这样说过…这帖里说的东西都跟HotSpot(或者其它JVM实现)一点关系都没有,还在Java源码级编译器层次上…
javabkb 写道
但是一直有个问题,即使是编译优化了,用加号拼接的效率还是不如用stringbuilder的
<< 这个也不对…要分情况说。前面也有说了,String的+本来就是用StringBuilder或StringBuffer实现的。
关键问题是:是不是同一个StringBuilder/StringBuffer。在循环里用+=之类的方式来拼接字符串的问题就出在每轮循环里都new了一个StringBuilder/StringBuffer来做拼接,然后toString()完就抛弃了,等下轮循环进来又再new一个。
sswh 写道
JDK6并没有特别优化。
嗯,从Java字节码一层能看到的情况看,确实是如此的。
让我演示一下某代码在JDK 1.0.2上编译出来的样子:
public class X { public static void main(String[] args) { String a = "alpha"; String b = "beta"; String c = "charlie"; String d = a + b + c; } }
Compiled from "X.java" public class X extends java.lang.Object SourceFile: "X.java" minor version: 3 major version: 45 Constant pool: const #1 = String #16; // alpha const #2 = String #8; // beta const #3 = String #22; // charlie const #4 = class #17; // java/lang/Object const #5 = class #10; // X const #6 = Method #4.#7; // java/lang/Object."<init>":()V const #7 = NameAndType #20:#23;// "<init>":()V const #8 = Asciz beta; const #9 = Asciz ConstantValue; const #10 = Asciz X; const #11 = Asciz Exceptions; const #12 = Asciz LineNumberTable; const #13 = Asciz SourceFile; const #14 = Asciz LocalVariables; const #15 = Asciz Code; const #16 = Asciz alpha; const #17 = Asciz java/lang/Object; const #18 = Asciz main; const #19 = Asciz ([Ljava/lang/String;)V; const #20 = Asciz <init>; const #21 = Asciz X.java; const #22 = Asciz charlie; const #23 = Asciz ()V; { public static void main(java.lang.String[]); Code: Stack=1, Locals=4, Args_size=1 0: ldc #1; //String alpha 2: astore_1 3: ldc #2; //String beta 5: astore_2 6: ldc #3; //String charlie 8: astore_3 9: return LineNumberTable: line 3: 0 line 4: 3 line 5: 6 line 2: 9 public X(); Code: Stack=1, Locals=1, Args_size=1 0: aload_0 1: invokespecial #6; //Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 1: 0 }
<< 留意一下Class文件的版本号,这个是用JDK 1.0.2里的javac编译的,如假包换。
看到了么,main()方法里的字节码根本就没有字符串拼接的动作发生,对吧?
然后用JDK 1.0.2的javac -O来编译:
Compiled from "X.java" public class X extends java.lang.Object SourceFile: "X.java" minor version: 3 major version: 45 Constant pool: const #1 = class #11; // java/lang/Object const #2 = class #6; // X const #3 = Method #1.#4; // java/lang/Object."<init>":()V const #4 = NameAndType #14:#16;// "<init>":()V const #5 = Asciz ConstantValue; const #6 = Asciz X; const #7 = Asciz Exceptions; const #8 = Asciz SourceFile; const #9 = Asciz LocalVariables; const #10 = Asciz Code; const #11 = Asciz java/lang/Object; const #12 = Asciz main; const #13 = Asciz ([Ljava/lang/String;)V; const #14 = Asciz <init>; const #15 = Asciz X.java; const #16 = Asciz ()V; { public static void main(java.lang.String[]); Code: Stack=0, Locals=1, Args_size=1 0: return public X(); Code: Stack=1, Locals=1, Args_size=1 0: aload_0 1: invokespecial #3; //Method java/lang/Object."<init>":()V 4: return }
加了-O参数之后main()方法里啥也不剩了,只有个return。
这才是Java层的“优化”…
然则从Sun的JDK 1.3开始javac就忽略-O参数了。现在大家用JDK6里的javac就看不到这种效果。
================================================
换个例子,
public class Y { public static void main(String[] args) { String a = "alpha"; String b = "beta"; String c = "charlie"; String d = a + b + c; System.out.println(d); } }
还是用JDK 1.0.2里的javac,不带-O参数来编译:
Compiled from "Y.java" public class Y extends java.lang.Object SourceFile: "Y.java" minor version: 3 major version: 45 Constant pool: const #1 = String #32; // alpha const #2 = String #22; // beta const #3 = String #45; // charlie const #4 = class #35; // java/lang/StringBuffer const #5 = class #36; // java/lang/Object const #6 = class #25; // java/io/PrintStream const #7 = class #24; // Y const #8 = class #42; // java/lang/System const #9 = Method #5.#18; // java/lang/Object."<init>":()V const #10 = Method #4.#17; // java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer; const #11 = Field #8.#15; // java/lang/System.out:Ljava/io/PrintStream; const #12 = Method #4.#18; // java/lang/StringBuffer."<init>":()V const #13 = Method #6.#16; // java/io/PrintStream.println:(Ljava/lang/String;)V const #14 = Method #4.#19; // java/lang/StringBuffer.toString:()Ljava/lang/String; const #15 = NameAndType #33:#41;// out:Ljava/io/PrintStream; const #16 = NameAndType #20:#34;// println:(Ljava/lang/String;)V const #17 = NameAndType #44:#39;// append:(Ljava/lang/String;)Ljava/lang/StringBuffer; const #18 = NameAndType #40:#46;// "<init>":()V const #19 = NameAndType #31:#21;// toString:()Ljava/lang/String; const #20 = Asciz println; const #21 = Asciz ()Ljava/lang/String;; const #22 = Asciz beta; const #23 = Asciz ConstantValue; const #24 = Asciz Y; const #25 = Asciz java/io/PrintStream; const #26 = Asciz Exceptions; const #27 = Asciz LineNumberTable; const #28 = Asciz SourceFile; const #29 = Asciz LocalVariables; const #30 = Asciz Code; const #31 = Asciz toString; const #32 = Asciz alpha; const #33 = Asciz out; const #34 = Asciz (Ljava/lang/String;)V; const #35 = Asciz java/lang/StringBuffer; const #36 = Asciz java/lang/Object; const #37 = Asciz main; const #38 = Asciz ([Ljava/lang/String;)V; const #39 = Asciz (Ljava/lang/String;)Ljava/lang/StringBuffer;; const #40 = Asciz <init>; const #41 = Asciz Ljava/io/PrintStream;; const #42 = Asciz java/lang/System; const #43 = Asciz Y.java; const #44 = Asciz append; const #45 = Asciz charlie; const #46 = Asciz ()V; { public static void main(java.lang.String[]); Code: Stack=2, Locals=5, Args_size=1 0: ldc #1; //String alpha 2: astore_1 3: ldc #2; //String beta 5: astore_2 6: ldc #3; //String charlie 8: astore_3 9: new #4; //class java/lang/StringBuffer 12: dup 13: invokespecial #12; //Method java/lang/StringBuffer."<init>":()V 16: aload_1 17: invokevirtual #10; //Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer; 20: aload_2 21: invokevirtual #10; //Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer; 24: aload_3 25: invokevirtual #10; //Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer; 28: invokevirtual #14; //Method java/lang/StringBuffer.toString:()Ljava/lang/String; 31: astore 4 33: getstatic #11; //Field java/lang/System.out:Ljava/io/PrintStream; 36: aload 4 38: invokevirtual #13; //Method java/io/PrintStream.println:(Ljava/lang/String;)V 41: return LineNumberTable: line 3: 0 line 4: 3 line 5: 6 line 6: 9 line 7: 33 line 2: 41 public Y(); Code: Stack=1, Locals=1, Args_size=1 0: aload_0 1: invokespecial #9; //Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 1: 0 }
当时的Java就已经是用StringBuffer来实现字符串拼接(+)的了。
这也毫无出奇之处,在Java语言规范里就有这么写:
Java语言规范第一版 写道
15.17.1.2 Optimization of String Concatenation
An implementation may choose to perform conversion and concatenation in one step to avoid creating and then discarding an intermediate String object. To increase the performance of repeated string concatenation, a Java compiler may use the StringBuffer class (§20.13) or a similar technique to reduce the number of intermediate String objects that are created by evaluation of an expression.
For primitive objects, an implementation may also optimize away the creation of a wrapper object by converting directly from a primitive type to a string.
An implementation may choose to perform conversion and concatenation in one step to avoid creating and then discarding an intermediate String object. To increase the performance of repeated string concatenation, a Java compiler may use the StringBuffer class (§20.13) or a similar technique to reduce the number of intermediate String objects that are created by evaluation of an expression.
For primitive objects, an implementation may also optimize away the creation of a wrapper object by converting directly from a primitive type to a string.
顺带一提现在在JDK6里用的是Java语言规范第三版,而JDK7即将使用的是Java语言规范第四版(还没出)。
================================================
JVM的实现内部也有一些针对字符串拼接的优化的。但那些看Java字节码是看不出来的——在这个抽象层之下,屏蔽了“优化”这种细节。
例如说JRockit里有特制的jrockit.vm.StringMaker类专门用来优化字符串拼接,JRockit的JIT编译器会识别出StringBuilder的使用模式,发现符合条件的时候就把一些StringBuilder“悄悄的”转换为这种特别的StringMaker类来处理。
大家用得比较多的Oracle/Sun的HotSpot VM的比较新的版本里也有类似的优化,可以把符合条件的一些相邻的StringBuilder合并为一个,用于优化这种场景:
String a = x + y + z; String b = a + x + y;
(假定变量a在后面就没有再被使用过了)
本来这段代码会被javac编译为等价于下面的代码:
String a = new StringBuilder().append(x).append(y).append(z).toString(); String b = new StringBuilder().append(a).append(x).append(y).toString();
而被HotSpot的JIT编译器优化后,会变成类似下面的样子:
String b = new StringBuilder().append(x).append(y).append(z).append(x).append(y).toString();
也就是把相邻的StringBuilder合并掉了。再次留意这个例子的前提是变量a在后面就没有被用过了,只有变量b在后面还有被用到。
这才是“JVM做的优化”。字节码层面就能看出来的所谓“优化”根本还没到JVM那层。
说的很详细和清楚,赞一个!
发表评论
-
Google Guava官方教程(中文版)
2014-10-24 23:48 2966原文链接 译文链接 ... -
《Storm入门》中文版
2014-05-28 12:38 2351本文翻译自《Getting Star ... -
《Java 7 并发编程指南》中文版
2013-11-03 17:00 8980原文链接 作者: Javier Fernández Gonz ... -
Java NIO系列教程
2013-06-25 17:03 4187原文地址:http://tutorials.jenkov.c ... -
[并发译文]Java内存模型手册
2013-01-20 21:13 2155原文地址:http://gee.cs.oswego.edu/ ... -
[并发编程]聊聊并发
2012-12-23 03:44 2288聊聊并发系列文章是我在InfoQ发表的并发编程连载文 ... -
构建JSONP字符串
2012-09-21 10:28 23821.咱们需要一个这样的JSONP字符串 var cal ... -
聊聊并发(二)Java SE1.6中的Synchronized
2012-05-24 13:51 2114本文属于作者原创,原文发表于InfoQ中文站。 ... -
聊聊并发(一)深入分析Volatile的实现原理
2012-02-22 09:39 2860本文属于作者原创,原文发表于InfoQ中文站。 ... -
JVM运行时数据区(2011年6月的JVM规范)
2011-10-18 13:30 1970本文是《The Java Virtual Machine Sp ... -
AOP的实现机制
2011-10-18 10:15 7774附件中有本文的源代码和Pdf版。本文写的很长的原因,是不希望大 ... -
单点登录设计
2011-09-25 15:20 4704使用独立的单点登录应用程序来做单点登录,这样可扩展性和安全性会 ... -
Java虚拟机
2011-08-25 19:50 2014JVM JVM即Java虚拟机,它是一个想象中的机器,通 ... -
HttpClient
2011-08-16 15:27 5878HttpClient是一个模拟HTTP请求的工具类,目前我们在 ... -
Eclipse插件开发
2011-07-22 11:15 1889插件开发工具 我觉得进行RCP开发还是用专门的ID ... -
Opencomet之Session设计
2011-06-24 13:08 1993本文主要介绍Opencomet 里的Session设计。见附件 ... -
单元测试用例设计
2011-06-15 22:42 1552测试用例设计的原则是短,精和易读。 如何做到短: ... -
使用AOP做权限控制
2011-06-06 20:49 6747见附件! -
开发中的小心得
2011-02-04 01:13 1650总结一下自己在开发上的一些小心得,持续更新,欢迎大家补充! ... -
单元测试利器之Jtester
2011-02-04 00:30 7275名词解释: Junit:众所周知的单元测试。 官方网址: ...
相关推荐
本实验报告对 Java 基本语法中字符串与数值型数据转换、从键盘输入数据的相关知识点进行了详细的介绍,并对流程控制中的 if 条件语句、for 循环语句、while 循环语句和 switch 语句的使用进行了深入的讲解。
Java 中的字符串可以使用 String 类来表示,而数值型数据可以使用基本数据类型如 int、double 等来表示。字符串与数值型数据之间的转换是 Java 编程中非常重要的一部分。 1. 字符串转换为数值型数据 Java 提供了...
在Java语言中,我们可以使用多种方法来按照字节截取字符串。其中一种常用方法是使用StringBuffer类和charAt()方法来实现字节截取。 在上面的示例代码中,我们使用了StringBuffer类来实现字节截取。首先,我们定义了...
在Java中,我们可以利用`Integer.parseInt()`方法将十六进制字符串转换为整数,再使用位运算符`^`来进行异或操作。首先,我们需要将输入的十六进制字符串按空格分隔,然后对每个分隔后的十六进制数进行异或运算。...
这个压缩包内包含的可能是Java代码示例(如csdn-demo),用于批量处理字符串,统计其中的中英文标点符号的数量,并根据指定的字符编码(GBK或UTF)来计算字符串的总长度。如果字符串的总长度超过预设的最大长度,...
传统的字符串累加方法是使用`+`运算符,然而,随着字符串长度的增长,这种方法的性能可能会下降。这主要是因为每次使用`+`拼接字符串时,JavaScript都会创建一个新的字符串对象,这在处理大量数据时会带来显著的内存...
在Java编程中,字符串处理是常见的任务之一。本问题提供了两个具体的字符串操作需求:字符串过滤和字符串压缩。接下来,我们将详细讨论这两个知识点。 1. **字符串过滤** 字符串过滤的目标是移除字符串中非首次...
16进制转10进制的过程涉及将16进制字符串的每一位数值按照16的幂次转换成对应的10进制数值,并累加这些数值以得到最终的整数。 给定的文件内容中描述的C++代码片段展示了如何将16进制字符串转换为10进制的整数。...
在编程领域,尤其是在使用C++、Java或C#等面向对象的语言时,经常需要将字符串数组转换为单一的string类型。这种操作在处理数据输入、输出或者格式化时非常常见。下面我们将详细讨论如何在不同语言中实现这个过程,...
其中,DJB2算法以其简单性和良好的散列性能被广泛使用,其基本思想是将字符串的每个字符的ASCII码累加,并用一个大的素数与之相乘,再对某个大数取模。 内存监控类则通常用于程序运行过程中监控内存的使用情况,...
`ProgramService.java` 文件展示了一种方法,通过使用 `BigDecimal` 类来执行字符串表示的数学公式。`BigDecimal` 是Java提供的一个类,用于进行高精度、任意精度的十进制算术运算,它能够避免浮点数运算时可能出现...
本篇文章详细地讲解了 Java 语言中的基本语法,包括字符串与数值型数据转换、从键盘输入数据等内容,并通过四个实验分别介绍了 if 条件语句、for 循环语句、while 循环语句和 Switch 语句的使用。
"chongfu.rar_Java字符串空格_单词统计_统计单词"这个标题暗示了我们将重点研究基于空格分隔的字符串进行单词统计的方法。 首先,我们需要理解Java中的字符串对象。`String`类在Java中是不可变的,这意味着一旦创建...
通过遍历字符串,使用 TreeMap 的 put 方法将字母作为键,出现次数作为值,并使用 get 方法获取键对应的值,进行累加计算。 ```java public static void letterCount1(String s) { s = s.replaceAll(" +", ""); ...
Java 基本语法—字符串与数值型数据转换、从键盘输入数据 在 Java 编程语言中,字符串与数值型数据转换是非常重要的一部分,本文将详细介绍字符串与数值型数据转换的基本语法,并通过四个实验实例来深入理解 Java ...
在编程领域,字符串操作是日常...它们虽然语法不同,但核心思想是一致的:遍历字符串,遇到目标字符时记录下标并累加计数。通过这种方式,我们可以高效地处理这类问题,无论是在简单的命令行应用还是复杂的软件系统中。
在进行编程实践时,判断字符串的长度是一个常见需求。...读者通过阅读这些实例,可以更好地理解Java中字符串操作的细节,尤其是在处理中文字符时需要注意的问题,从而在自己的编程实践中加以应用。
计算这个值需要累加每个字符串的长度,然后除以字符串的总数。继续使用之前的例子,平均长度为`(5 + 15 + 7) / 3 = 9.0`。在Python中,可以这样计算: ```python sum_lengths = sum(len(s) for s in strings) ...
- 使用 `str.getBytes(charset).length` 方法计算字符串按照指定编码 `charset` 的实际字节数。 3. **匹配中文字符**: - 通过正则表达式 `^[\u4e00-\u9fa5]$` 匹配中文字符,若匹配成功则认为该字符占用 2 个字节...
字符串与基本数据类型之间可以相互转换,如使用`Integer.parseInt()`和`String.valueOf()`。 9. **引用数据类型**:除了基本数据类型,Java还有类、接口和数组等引用数据类型。它们存储的是对象的引用,而不是实际...