String的小笔记
String类的对象是不可变的!
在使用String类的时候要始终记着这个观念。一旦创建了String对象,它就不会改变。
String类中也有可以改变String中字符串的方法,但只要是涉及改变的方法,都是通过创造并返回一个全新的String对象来实现的。而原先那个String对象是没有被改动过的。
String对操作符“+”的重载
String对象是不可变的,而这个不可变性往往会带来性能上、效率上的问题。
Java中,为String类重载的“+”操作符就是一个例子。 操作符的重载的意思是,一个操作符作用于一个特定的类的时候,被赋予了特殊的意义。用于String的“+”和“+=”是Java中仅有的l两个重载过的操作符,而且Java并不允许程序员重载任何操作符。
我们知道操作符“+”可以用来连接String,并且当操作符的另一边不是String的时候,会尝试把它变成String。
public class Concatenation {
public static void main(String[] args) {
String mango = "mango";
String s = "abc" + mango + "def" + 47;
System.out.println(s);
}
/* Output:
abcmangodef47
这个我们可能会想象,它是这样工作的:
String中应该有个append()方法,一开始String“abc”会调用这个append方法,然后创建一个新的String对象,连接abc和Stringmango,然后再创建新的String对象,以此类推。
因为String对象是不能改变的嘛。意味着这几个String的+运算,会产生好几个String对象,这样的猜想确实没错。
这个猜想确实行得通,但这样的话,垃圾回收器就要回收一堆中间的String对象。很浪费资源噢。
为了看Java底层真正是怎么实现这个Java的Stirng加法的,我们用到了jdk自带的反编译工具——Javap工具。
输入命令行:
javap -c Concatenation
-c代表着生成JVMz字节码,下面是删减后的g关键字节码:
public static void main(java.lang.String[]);
Code:
Stack=2, Locals=3, Args_size=1
0: ldc #2; //String mango
2: astore_1
3: new #3; //class StringBuilder
6: dup
7: invokespecial #4; //StringBuilder."<init>":()
10: ldc #5; // String abc
12 invokevirtual #6; //StringBuilder.append:(String)
15 aload_1
16 invokevirtual #6; //StringBuilder.append:(String)
19 ldc #7; //String def
21 invokevirtual #6; //StringBuilder.append:(String)
24 bipush 47
26 invokevirtual #8; //StringBuilder.append:(I)
29 invokevirtual #9; //StringBuilder.toString:()
32 astore_2
33 getstatic #10; //Field System.out:PrintStream;
36 aload_2
37 invokevirtual #11; // PrintStream.println:(String)
40 return
这上面是我们的JVM的汇编语言。
我们可以看到,虽然我们在自己写的的源代码中完全没有提到StringBuilder类,但编译器却自作主张自己引入了!!——因为它大大地提高了效率!
在这个例子中,编译器创建了一个StringBuilderd对象来实现这个s的String的加法。调用了StringBuilder的append()方法四次,然后最后调用了StringBuilder对象的toString()方法来产生一个String对象并存在s中返回。
可见Java编译器对String的加法操作是会自动优化的!
现在我们可以随意地使用String了,但有时候编译器的优化工作做的不是很好,需要我们手动地优化,看个例子:
//: strings/WhitherStringBuilder.java
public class WhitherStringBuilder {
public String implicit(String[] fields) {
String result = "";
for(int i = 0; i < fields.length; i++)
result += fields[i];
return result;
} public String explicit(String[] fields) {
StringBuilder result = new StringBuilder();
for(int i = 0; i < fields.length; i++)
result.append(fields[i]);
return result.toString();
}
} ///:~
这两个方法的目的都一样,把一个String数组中的元素都加起来,最后返回一个String对象。
然后我们用上面一样的方法来看看它的执行过程:
implicit方法的执行汇编字节码:
public java.lang.String implicit(java.lang.String[]);
Code:
0: ldc #2; //String
2: astore_2
3: iconst_0
4: istore_3
5: iload_3
6: aload_1
7: arraylength
8: if_icmpge 38
11: new #3; //class StringBuilder
14: dup
15: invokespecial #4; // StringBuilder.”<init>”:()
18: aload_2
19: invokevirtual #5; // StringBuilder.append:()
22: aload_1
23 iload_3
24 aaload
25: invokevirtual #5; // StringBuilder.append:()
28: invokevirtual #6; // StringBuiIder.toString:()
31: astore_2
32: iinc 3, 1
35: goto 5
38: aload_2
39 areturn
书上的小解析:
大概意思就是第八行到第35行构成了循环:

然后我们要关注的重点是:
在循环中,一个StringBuilder的构造器被调用了,说明你每次进入循环都会创建一个新的StringBuilder对象。
然后是explicit的执行字节码:
public java.lang.String explicit(java.lang.String[]);
Code:
0: new #3; //class StringBuilder
3: dup
4: invokespecial #4; // StringBuilder.”<init>”:()
7: astore_2
8: iconst_0
9: istore_3
10: iload_3
11: aload_1
12: arraylength
13: if_icmpge 30
16: aload_2
17: aload_1
18: iload_3
19: aaload
20 invokevirtual #5; // StringBuilder.append:()
23 pop
24: iinc 3,1
27: goto 10
30: aload_2
31: invokevirtual #6; // StringBuiIder.toString:()
34: areturn
可以看到:
这里只创建了一个StringBuilder对象,显然效率更好!
如果你知道字符串的长度,还可以体现分配好size,这样还避免了多次重新分配缓存。
所以说,如果你要重写一个类的toString方法,如果字符串操作不是很复杂,就可以依赖编译器本身的优化;如果很复杂的话,建议自己创建一个StringBuilder类。
StringBuilder是jdk5后引入的。之前用的是StringBuffer,是线程安全的,所以开销也更大,显然StringBuilder效率更高。
无意识的递归
下面例子是基于这样的一个想法:
你想在某个类的toString中打印出这个对象的地址,然后你想到了关键字this于是就写出了这样的代码:

但是,当你尝试去打印这个对象的话,你会看到很长的一个异常报错。
为什么会这样呢?因为发生了递归调用。
当编译器看到String后面跟了个加号“+”,会尝试将后面的this变成String类型,这就变成调用toString方法,然后就递归了……
正确的做法应该是调用根类Object的toString()方法,也就是super.toString()。
String的小笔记的更多相关文章
- 转:【iOS开发每日小笔记(十一)】iOS8更新留下的“坑” NSAttributedString设置下划线 NSUnderlineStyleAttributeName 属性必须为NSNumber
http://www.bubuko.com/infodetail-382485.html 标签:des class style 代码 html 使用 问题 文件 数据 ...
- HTML5版的String Avoider小游戏
HTML5版的String Avoider小游戏 http://www.newgrounds.com/portal/view/300760 蛮简单也蛮考验耐心,从游戏起始点移动鼠标到终点位置,鼠标移动 ...
- 小笔记:Timer定时间隔时间操作
小笔记:Timer定时间隔时间操作,后面有时间再补充和完善: public class TimingSvc { /// <summary> /// 定时器,执行定时任务 /// </ ...
- 关于 linux中TCP数据包(SKB)序列号的小笔记
关于 SKB序列号的小笔记 为了修改TCP协议,现在遇到了要改动tcp分组的序列号,但是只是在tcp_sendmsg函数中找到了SKB的end_seq 一直没有找到seq 不清楚在那里初始化了,就 ...
- Linux下postgres9.4 版本的单机版安装小笔记
1.添加RPMyum install https://download.postgresql.org/pub/repos/yum/9.4/redhat/rhel-7-x86_64/pgdg-redha ...
- 【手记】小心在where中使用NEWID()的大坑 【手记】解决启动SQL Server Management Studio 17时报Cannot find one of more components...的问题 【C#】组件分享:FormDragger窗体拖拽器 【手记】注意BinaryWriter写string的小坑——会在string前加上长度前缀length-prefixed
[手记]小心在where中使用NEWID()的大坑 这个表达式: ABS(CHECKSUM(NEWID())) % 3 --把GUID弄成正整数,然后取模 是随机返回0.1.2这三个数,不可能返回其它 ...
- 深入剖析Nginx一点小笔记
前几天在图书馆看书,恰好看到这本<深入剖析nginx>,花了快一周的时间看完了这本书,写点笔记心得便于以后复习. 以前对nginx的认识就只是停留在一个反向代理服务器上.百度了一下ngin ...
- String一点小发现
今天面试官问了几个关于java内存方面的问题,其中有一个是关于内存重复使用的.突然想到java中String比较特殊的地方,根据自己的理解所以稍微记录一下以免遗忘. 对于下面这个小程序: public ...
- [java小笔记] 关于数组内存管理的理解
数组是大多数编程语言都提供的一种复合结构,如果程序需要多个类型相同的变量时,就可以考虑定义一个数组,java语言的数组变量时引用类型的变量,因此具有java引用变量的特性.在使用数组之前必须对数组对象 ...
随机推荐
- nginx-upsync-module安装
1.安装nginx-upsync-module wget https://github.com/weibocom/nginx-upsync-module/archive/master.zip 作用:n ...
- [练习]使用dx.bat、dexdump.exe、javap、Baksmali
PART0 这几个工具的关系可以这样描述: 用例: public class Hello { public int foo(int a , int b ) { return (a+b) * (a-b) ...
- Chapter3——进入Android Dalvik虚拟机(二)
Dalvik汇编语言基础 Dalvik虚拟机为自己设计了一套指令集,并制定了自己的指令格式和调用规范. 位描述约定如下: 每16位的字采用空格分隔开来 每个字母表示4位,每个字母按顺序从高字节开始,排 ...
- Unable to resolve target 'android-16'
Just now when I imported the "android-support-v7-appcompat" to ADT,the console pointed out ...
- chan_ss7 呼出的时候指定使用某个CICs,或者CICs范围 的方法
Linkset one: 1-31 Incoming 33-58 Outgoing 58-63 Emergency Traffic Linkset two: 1-31 Incoming 33-58 ...
- nginx开发_字符串操作函数
由于ngx_str_t为非NULL结尾的函数,且网络请求中有大量忽略大小写的需求,所以nginx内部封装了许多字符串操作相关的函数,函数名称极其相识,且使用时有有些约定,特此整理. 赋值&拷贝 ...
- Spring 事务管理高级应用难点剖析: 第 1 部分
Spring 的事务管理是被使用得最多的功能之一,虽然 Spring 事务管理已经帮助程序员将要做的事情减到了最小.但在实际开发中,如果使用不当,依然会造成数据连接泄漏等问题.本系列以实际应用中所碰到 ...
- H264解码器源码(Android 1.6 版)
H264解码器源码,移植ffmpeg中的H264解码部分到Android,深度删减优化,在模拟器(320x480)中验证通过. 程序的采用jni架构.界面部分,文件读取,视频显示都是用java做的,底 ...
- 使用Axis2创建Web Service
Axis2是新一代Web Service开发工具,目前最新版本是1.5.本文主要介绍如何用Axis2创建Web Service. 首先下载二进制包和war包,将war包复制到Tomcat的webapp ...
- UVaLive 3902 Network (无根树转有根树,贪心)
题意:一个树形网络,叶子是客户端,其他的是服务器.现在只有一台服务器提供服务,使得不超k的客户端流畅,但是其他的就不行了, 现在要在其他结点上安装服务器,使得所有的客户端都能流畅,问最少要几台. 析: ...