关于java字符串编译优化问题
2016-04-20 00:32
351 查看
情景一:不好的字符串拼接习惯
起因是这样的:一个大牛在写了一篇关于java字符串优化问题的讲解,他提到:不要使用strObj+otherValue的方法将otherValue转换为字符串形式,因为底层操作会让你吓一跳的。那么底层的实质是怎么样的呢?他的意思是这样的:
比如: String s = "I have";
int total = 12;
Dog dog = new Dog(); //假设Dog类重写了toString方法
String msg = s + total+dog;
在运行时,msg的赋值语句会执行为: msg = new StringBuilder().append(s).append(total).append(dog.toString()).toString();
我们发现,运行时创建了一个匿名的StringBuilder对象,来拼接字符串,存储到缓冲区,最后调用toString方法返回拼接后的结果。显然,StringBuilder在这里小材大用了,而且消耗了内存资源,不可取。我发现很多人都喜欢写类似 ""+num的代码,我也是。所以以后需要注意了,特别是性能敏感的环境下。应该改用String.valueOf(value),或者包装类型的toString方法,比如Double.toString(value)。
如果需要对字符串进行频繁的修改操作,那就使用StringBuilder,或者StringBuffer(线程安全的),从而避免大量的中间字符串垃圾碎片。
情景二:为什么一个是true,一个是false
一位网友提到了这样一个问题:
String str = "abc";
String str1 = "ab" + "c";
String str2 = "ab";
String str3 = str2 + "c"; //试试运行这段代码,就会发现第一个为true 第二个为false ,why?
下面是我的解释:
这是因为javac的编译优化造成的。str1是由2个字符串常量拼接的,常量一定是不会改变的量,那么在编译阶段,javac就有胆量将它给简化一下:str1就优化为:str1="abc",又因为str也是"abc",所以str和str1共享了内存据“abc”,所以str和str1都指向"abc"。由于==是浅比较,比较的是字符串的内存地址,所以他们相等。
而str3的拼接中包含了一个变量str2,而变量是在运行时动态确定的,所以javac不敢再编译时对它优化。也就是说,str3会在运行时在堆中new出字符串对象"abc",显然它的地址和前面的"abc"的地址不一样,所以是false。
我对上面的程序导出jar后进行反编译,验证了我的猜想,结果如图:
![](https://images2015.cnblogs.com/blog/858860/201604/858860-20160420003115648-1023293182.png)
为了进一步证实我的猜想,我把上面的代码改为:
那么问题来了,不是说str2是变量的吗。怎么这次也打印是true呢?注意我给str2加上了修饰符final,声明str2位常量,那么同样的道理,javac也能保证str2不会改变,于是把str3优化为:str3="abc"。
反编译class文件截图:
![](https://images2015.cnblogs.com/blog/858860/201604/858860-20160420003824507-412571767.png)
注意:不要使用 == 去比较字符串,因为字符串时引用类型,== 比较的是内存地址,而不是字符串内容。请使用equals() 或者equalsIgnoreCase()。
只有字符串常量是共享内存的,而字符串变量或者运行时动态生成的字符串,则非如此。
起因是这样的:一个大牛在写了一篇关于java字符串优化问题的讲解,他提到:不要使用strObj+otherValue的方法将otherValue转换为字符串形式,因为底层操作会让你吓一跳的。那么底层的实质是怎么样的呢?他的意思是这样的:
比如: String s = "I have";
int total = 12;
Dog dog = new Dog(); //假设Dog类重写了toString方法
String msg = s + total+dog;
在运行时,msg的赋值语句会执行为: msg = new StringBuilder().append(s).append(total).append(dog.toString()).toString();
我们发现,运行时创建了一个匿名的StringBuilder对象,来拼接字符串,存储到缓冲区,最后调用toString方法返回拼接后的结果。显然,StringBuilder在这里小材大用了,而且消耗了内存资源,不可取。我发现很多人都喜欢写类似 ""+num的代码,我也是。所以以后需要注意了,特别是性能敏感的环境下。应该改用String.valueOf(value),或者包装类型的toString方法,比如Double.toString(value)。
如果需要对字符串进行频繁的修改操作,那就使用StringBuilder,或者StringBuffer(线程安全的),从而避免大量的中间字符串垃圾碎片。
情景二:为什么一个是true,一个是false
一位网友提到了这样一个问题:
String str = "abc";
String str1 = "ab" + "c";
String str2 = "ab";
String str3 = str2 + "c"; //试试运行这段代码,就会发现第一个为true 第二个为false ,why?
下面是我的解释:
这是因为javac的编译优化造成的。str1是由2个字符串常量拼接的,常量一定是不会改变的量,那么在编译阶段,javac就有胆量将它给简化一下:str1就优化为:str1="abc",又因为str也是"abc",所以str和str1共享了内存据“abc”,所以str和str1都指向"abc"。由于==是浅比较,比较的是字符串的内存地址,所以他们相等。
而str3的拼接中包含了一个变量str2,而变量是在运行时动态确定的,所以javac不敢再编译时对它优化。也就是说,str3会在运行时在堆中new出字符串对象"abc",显然它的地址和前面的"abc"的地址不一样,所以是false。
我对上面的程序导出jar后进行反编译,验证了我的猜想,结果如图:
![](https://images2015.cnblogs.com/blog/858860/201604/858860-20160420003115648-1023293182.png)
为了进一步证实我的猜想,我把上面的代码改为:
String str = "abc"; final String str2 = "ab"; String str3 = str2 + "c"; System.out.println(str == str3); //打印出true
那么问题来了,不是说str2是变量的吗。怎么这次也打印是true呢?注意我给str2加上了修饰符final,声明str2位常量,那么同样的道理,javac也能保证str2不会改变,于是把str3优化为:str3="abc"。
反编译class文件截图:
![](https://images2015.cnblogs.com/blog/858860/201604/858860-20160420003824507-412571767.png)
注意:不要使用 == 去比较字符串,因为字符串时引用类型,== 比较的是内存地址,而不是字符串内容。请使用equals() 或者equalsIgnoreCase()。
只有字符串常量是共享内存的,而字符串变量或者运行时动态生成的字符串,则非如此。
相关文章推荐
- thinkeing in java--java中的基本对象类型及其如何存储
- Java线程安全
- spring与hibernate整合之:继承HibernateDAOSupport方式+理解xml与AutoWired、Resource的区别
- java concurrency
- (19)Spring Boot 添加JSP支持【从零开始学Spring Boot】
- 马士兵JAVA基础实录教程
- 学习Spring(六) -- Spring中Bean的作用域以及生命周期
- Spring事务管理高级应用难点剖析
- Spring事务的传播特性和隔离级别
- ActiveMQ与Spring集成
- Leetcode(二):Add Two Numbers
- java quartz 中的时间格式
- hibernate与spring整合
- "."(点)与"/"(斜杠)在java中的意思
- 前台如何将多个json对象传入java后台
- 【JBehave】集成spring测试
- Java类成分图、面向对象简述
- spring配置中,ref=dataSource背后的方法调用
- 线程的生命周期以及控制线程
- 条件队列