Java中的字符串缓冲池——String pool
来源:互联网 发布:中国移动短信群发软件 编辑:程序博客网 时间:2024/06/06 08:47
一、简介
字符串操作是计算机程序设计中最常见的行为。正因为常用,Java在对String类的设计上实现的原理时常会发生变化,在后来的jdk6、7、8中,对于优化的改进一直在进行。
对于同一段代码,在不同版本下的运行结果可能会出现不同的情况:
<span style="white-space:pre"></span> String s = new String("1"); s.intern(); String s2 = "1"; System.out.println(s == s2); String s3 = new String("1") + new String("1"); s3.intern(); String s4 = "11"; System.out.println(s3 == s4); String s5 = new String(new char[]{'a' , 'b'}); s5.intern(); String s6 = "ab"; System.out.println(s5 ==s6);对于上面的代码在jdk6下的结果是:false false false但是在jdk7中:false true true;主要是因为JVM对“入池”操作的实现原理发生了些许变化。但是在常用的字符串操作还是遵循“任何看似改变字符串的操作,都不会改变字符串本身,改变的只是字符串的副本”。
二、工作原理
java的虚拟机在内存中开辟出一块单独的区域,用来存储字符串对象,这块内存区域被称为字符串缓冲池。当代码中出现字面量形式创建字符串对象时,JVM首先会对这个字面量进行检查,如果字符串常量池中存在相同内容的字符串对象的引用,则将这个引用返回,否则新的字符串对象被创建,然后将这个引用放入字符串常量池,并返回该引用。
例如代码:String str1 = "aaa";
创建字符串的时候先查找字符串缓冲池中有没有相同的对象,如果有相同的对象就直接返回该对象的引用,如果没有相同的对象就在字符串缓冲池中创建该对象,然后将该对象的应用返回。对于这一步而言,缓冲池中没有aaa这个字符串对象,所以首先创建一个字符串对象,然后将对象引用返回给str。
String str2 = "aaa";
对于这一句,也是想要创建一个对象引用变量str2使其指向aaa这一对象。这时,首先查找字符串缓冲池,发现aaa这个对象已经有了,这是就直接将这个对象的引用返回给str2,此时str1和str2就共用了一个对象aaa,这时会出现一个疑问,两个变量引用同一个对象,此时,str1改变了这个对象,str回不回受影响呢?不用担心,因为字符串都是常量,一旦创建就没办法修改了,除非创建一个新的对象。
所有的String对象都是不可变对象,所有的看似改变String对象的操作都只不过是创建了一个新的String对象,而原先的对象丝毫未变。如:
String str1 = "aaa";String str2 = "aaa";System.out.println(str1.toUpperCase());//AAAstr1 += "bbb";System.out.println(str1);//aaabbbSystem.out.println(str2);//aaa
输出结果为:
AAA
aaabbb
aaa
可以发现无论是转换成大写操作还是拼接操作都没有改变str2的引用值。
继续代码:
String str3 = new String("aaa");
String str4 = new String("ccc");
上面中的代码执行时:
第一句:先判断字符串缓冲池中是否有"aaa"对象,此时已经存在,不再创建,只是将“aaa”的引用返回给str3,此句创建了一个对象。注意,str2 不是对象,只是引用.只有 new 生成的才是对象.
第二句:先判断字符串缓冲池中是否有"ccc"对象,此时不存在,创建“ccc”对象,再将“ccc”的引用返回给str4。
String a = "abc";String b = "abc";String c = new String("xyz");String d = new String("xyz");String e="ab"+"cd";
这个程序与上边的程序比较相似:
String a = “abc”;这一句由于缓冲池中没有abc这个字符串对象,所以会创建一个对象;String b = “abc”;由于缓冲池中已经有了abc这个对象,所以不会再创建新的对象;String c = new String(“xyz”);由于没有xyz这个字符串对象,所以会首先创建一个xyz的对象,然后这个字符串对象由作为String的构造方法,在内存中(不是缓冲池中)又创建了一个新的字符串对象,所以一共创建了两个对象;String d = new String(“xyz”);省略了创建一个对象的过程,所以只创建了一个对象;String e=”ab”+”cd”;由于常量的值在编译的时候就被确定了。所以这一句等价于String e=”abcd”;创建了一个对象。
在学习java时就知道两个字符串对象相等的判断要用equal而不能使用==,但是学习了字符串缓冲池以后,应该知道为什么不能用==,什么情况下==和equal是等价的,首先,必须知道的是,==比较的是两个对象的内存地址是否相等。
String a = "abc";String b = "abc";if(a==b){System.out.println("a==b");}else{System.out.println("a!=b");}
输出结果为: a==b
此时使用==可以进行比较的原因就是a和b引用的是字符串缓冲池中的同一个对象,他们的物理地址相同,所以值是相同的。
String a = "abc";String c = new String("abc");if(a==c){System.out.println("a==c");}else{System.out.println("a!=c");}if(a.equals(c)){System.out.println("a.equals(c)");}else{System.out.println("!a.equals(c)");}
结果为:
a!=ca.equals(c)
String c = new String("abc");这一句话没有在字符串缓冲池中创建新的对象,但是会在内存的其他位置创建一个新的对象,所以a是指向字符串缓冲池的,c是指向内存的其他位置,两者的内存地址不同的。
String a = "abc";String c = new String("abc");c = c.intern();if(a==c){System.out.println("a==c");}else{System.out.println("a!=c");}if(a.equals(c)){System.out.println("a.equals(c)");}else{System.out.println("!a.equals(c)");}
在代码中增加了 c = c.intern();语句后,结果为:
a==ca.equals(c)
intern()方法的作用是返回在字符串缓冲池中的对象的引用,所以c指向的也是字符串缓冲池中的地址,和a是相等的。
String Monday = "Monday"; String Mon = "Mon"; String day = "day"; System.out.println(Monday == "Mon" + "day"); System.out.println(Monday == "Mon" + day);
结果为:
true
false
第一个为true ,因为两者都是常量所以在编译阶段就已经能确定了,在第二个中,day是一个变量,所以不能提前确定他的值,所以两者不相等,从这个例子我们可以看出,只有+连接的两边都是字符串常量时,引用才会指向字符串缓冲池,否则指向内存中的其他地址。
String Monday = "Monday"; String Mon = "Mon"; final String day = "day"; System.out.println(Monday == "Mon" + "day"); System.out.println(Monday == "Mon" + day);
加上final后day也变成了常量,所以第二句的引用也是指向的字符串缓冲池。
在面试的问题中,关于字符串创建的内容很常见:
在下面的语句中字符串常量池中含有的是?
String str = "a" + "b" + "c";
答案是只有一个:"abc",还是上面同样的原理,由于在编译的时候变量str的值就已经确定了,查看一下编译后的文件内容:
String str = "abc";
代码直接优化成了“abc”,这样可以减少在运行时的操作步骤。
在JDk7版本中对于字符串的改变
首先是在switch语句中添加了对字符串的支持。
其次是对字符串常量池使用的内存位置进行了移动,以适应更大的数据量。
关于intern操作
有两篇讲解的很好的博客:
http://www.cnblogs.com/paddix/p/5326863.html#undefined
http://tech.meituan.com/in_depth_understanding_string_intern.html
博客中对于此方面的讲解的总结:
jdk6版本中字符串所有的方法偶遵循“任何看似改变字符串的操作,都不会改变字符串本身,改变的只是字符串的副本”,即使是intern操作也不例外;从JDK 1.7后,HotSpot 将常量池从永久代移到了元空间,正因为如此,JDK 1.7 后的intern方法在实现上发生了比较大的改变,JDK 1.7后,intern方法还是会先去查询常量池中是否有已经存在,如果存在,则返回常量池中的引用,这一点与之前没有区别,区别在于,如果在常量池找不到对应的字符串,则不会再将字符串拷贝到常量池,而只是在常量池中生成一个对原字符串的引用,所以在开始的程序中出现了结果为true的情况,就是因为两个对象指向了同一个引用。此时会不会有这样的疑问:这样会不会出现引用传递的情形,两个对象中的其中一个对引用进行了修改,进而影响了另外一个对象。这样的情形是不会出现的,字符串的操作还是遵循不变的原则的,即使一个引用改变了值也只是改变了自身的引用,并不会改变另外一个对象。对象的引用占用的空间远比实际值占用的空间小,对于含有大量字符串操作的程序,这样的改进能够避免占用大量的内存影响程序运行速度。
- Java中的字符串缓冲池——String pool
- java 字符串缓冲池 String缓冲池
- java 字符串缓冲池 String缓冲池
- java 字符串缓冲池 String缓冲池
- java 字符串缓冲池 String缓冲池
- java 字符串缓冲池 String缓冲池
- java字符串缓冲池 string缓冲池
- java string pool(java 字符串池)
- JAVA中的String Pool .
- Java String Pool (字符串池)
- java 字符串 String 的缓冲池
- Java中的String和String pool
- Java字符串池(String Pool)深度解析
- java String 缓冲池
- java 字符串缓冲池 String缓冲池 == 和equals
- 字符串拘留池(string interning pool)
- java字符串池(string pool)和字符串堆(heap)内存分配
- java 创建string对象机制 字符串缓冲池 字符串拼接机制
- CentOS7架设L2TP实现VPN
- win7下远程连接显凭据无法工作
- CodeForces 25A IQ test 唯一奇偶(简单的题目)
- How to install Maven on Mac OSX
- 【C#】-属性(Property)和字段(Field)的区别
- Java中的字符串缓冲池——String pool
- warning: Could not resolve external type c:objc(cs)NSString
- Seconds_Behind_Master 能正确反映主从延时吗?
- android 键盘弹起 UI上移
- HTTP中的重定向和请求转发的区别
- Intellij AS 添加git功能
- Mysql各种索引区别
- Sublime Text 3 手动安装Emmet插件
- iOS WMPlayer视频播放器