字符串这个非常非常常用的数据结构,平时用的最多,但它到底是怎么工作的,可能没多少人去关心过。下面就来谈谈这个String到底有什么特殊的。
String s1 = "abc";
String s2 = "abc";
String s3 = "ab" + "c";
String s4 = new String("abc");
判断上面s1,s2,s3,s4是否相等,用==
答案是:s1==s2==s3!=s4。
在java里判断==的条件是引用地址相同,可以理解为指向这块内存的指针是相等的,当s1,s2,s3都相等时说明等号后面的"abc"只有一份,它们只是指向同一个内存块而已。
再看一个普通的class对象。有个Person类,有个int age属性。
Person p = new Person();
p.setAge(10);
给一个对象p的age赋值为10.此时如果把p作为参数传给别的方法,譬如有个
void change(Person p) {p.setAge(5);}
当把p传给change方法后,再查看原来的p的age,发现已经被修改成5了。说明通过new Person()得到的p在内存中只有1份,当在任何地方被修改后,p的属性就被修改了。
刚才说了s1,s2,s3它们都指向了同一块内存地址,"abc"只有唯一的一份,那是否可以猜测s2+"d"后,s1和s3也会随之改变呢。
答案是no。只有s2自己变成了"abcd",s1和s3不会有任何变化,因为系统又开辟了一块新的内容来装"abcd"。
那假如是下面的情况呢:
String s4 = new String("abc");
String s5 = s4;
s5 += "d";
求s4?
答案是s4依旧没有变化。看起来貌似和Person不太一样是吧。String就是比较特殊。
特殊在哪里呢?
1.String存放的地方和普通的类不一样
2.String不可改变,一旦一个String对象定义完毕,没有任何修改它的地方,它的所有属性都不可修改。修改后的那就是另外一个String了
我们主要关心的有栈,堆,和String常量存放的地方——方法区。(http://zangxt.iteye.com/blog/472236)
栈里主要存放的基本数据类型(int,double等),和对象的引用(如Person p = new Person()中的p),有时递归过深时发生栈溢出,说的就是这个,因为存放了调用关系层次过多导致栈溢出。
堆里是供线程共享的内存区域,存放的是对象实例,譬如Person被new后,里面的所有属性都在堆里开辟了内存来存放,这一块也是gc需要频繁关注的地方。
String有一块常量区(方法区),用来存放String。
当执行String s1 = "abc";时,系统便在这块常量区里,开辟了内存保存“abc”,并将指向这块内存的指针s1返回给调用者。以后只要常量区里还有"abc",再去定义String s2 = "abc",系统就不会再次开辟内存,而是直接将地址返回给s2.所以s1==s2。
所以当你定义String s2 = "abc"时,系统可能创建一个对象或者不创建对象,如果已经有了,那就直接返回地址,如果没有,那就创建。
但是String s4 = new String("abc")时,系统至少创建一个对象,用了new关键字,也就是至少会在栈里创建一个s4,然后再去常量区判断“abc”是否存在,不存在时new String这一步就会创建两个对象。可以看到,这样会额外耗费一个栈里的空间。所以平时,多用直接=的方式来创建String。
所以s4!=s2.
然后再看另外一个问题:
String a = "abc";
String b = "ab";
String c = b + "c";
问a==c吗?
答案是不等。因为a和b都是字符串常量,在编译期就被确定了,内存地址已被确定。
而c里面有个b是个变量,b是存放在栈里的一个引用而已,所以c不会在编译期确定,只会在运行时确定。那么a!=c。
而且,上面说过,String类是final的,里面也没有任何修改属性的方法,String是不可变的。当执行b + "c"时,底层是用StringBuilder类的append方法进行连接字符串的,连接完毕再toString返回引用地址的。
那为什么"ab" + "c" 却 == "abc"呢?
这是Jvm在编译期做的优化。
String s = "ab" + "c";这种只有常量的,会在编译期被合并,等同于String s = "abc"。然后将"abc"作为常量存放在常量区。或者这样的,也是==的
"a" + 1 + "b" == "a1b"
假如是这样:
int x = 1;
String s = "a" + x + "b";
这样就不等了。
在编译期内,只会优化合并所有变量之前的常量。"a" + "1" + x + "b",像这种就相当于先合并"a1",然后再new一个StringBuilder再去append后面的几个值。
参考:http://www.cnblogs.com/ITtangtang/p/3976820.html
总结一下,直接指定的String,如String s = "abc",对象"abc"是存放在常量池中的,s在栈里。
String s = new String("abc"),对于通过 new 产生一个字符串时,会先去常量池中查找是否已经有了 ”abc” 对象,如果没有则在常量池中创建一个此字符串对象,然后堆中再创建一个常量池中此 ”abc” 对象的拷贝对象。
不管存放在哪,String都是不可变的,用+号连接时,走的是StringBuiler创建了新对象.
参考:http://blog.csdn.net/u014082714/article/details/50087563