参考String内存机制管理:
VM运行的时候,将内存分为两个部分,一部分是堆,一部分是栈。堆中存放的是创建对象,而栈中存放的则是方法调用过程中的局部变量或引用。在设计JAVA字符串对象内存实现的时候,在堆中又开辟了一块很小的内存,称之为字符串常量池,专门用来存放特定的字符串对象的。
Java中的new关键字在运行的时候才执行,编译只是检查语法,运行时才真正装入内存。
字符串常量
常量池(constant pool):在编译期被确定,并被保存在已编译的.class文件中的一些数据,它包括了关于类、方法、接口等中的常量,也包括字符串常量。
常量字符串池(pool)
字符串常量
如下特性:
- 字符串必须会用双引号括起来。(不同于new String(string)构造的字符串)
- 字符串的字符使用Unicode国际统一编码,一个字符占两个字节。
- String是一个final类,代表不可变的字符序列。
- 字符串是不可变(immutable)的。一个字符串对象一旦被赋值,其内容就是固定不可变的。
String str = "Just";
str = "Justin";
上述程序,前一句是创建一个String对象,后一句是改变变量的指向,整个过程如下图所示。我们可知,“Just”的内容并没有改变,改变的只是变量str的指向,它指向了内存中另一个字符串对象。
对于可以共享的字符串对象,会现在字符串池中查找是否有相同的String内容,例如下代码:
String s0 = "kvill";
String s1 = "kvill";
String s2 = "kv" + "ill";
System.out.println(s0 == s1);
System.out.println(s0 == s2);
输出结果为:true true
结果解析:当直接在程序中使用双引号创建字符串对象时,该字符串就被存储于字符串常量池中,而对于s1在创建之前会先检查字符串常量池中是否已经存在“kvill”,如果已经存在,则无须创建,直接指向该字符串即可。
Java会确保一个字符串常量只有一个拷贝。s0和s1都是字符串常量,在编译时期就被确定,两个变量都是指向同一引用,故s0==s1为true;而“kv”和“ill”也都是字符串常量,当一个字符串由多个字符串常量拼接而成时,其肯定也是字符串常量,所以s2在编译期就被解析成一个字符串常量,同样指向kvill,故s0==s1==s2。
用new String()创建的字符串不是常量,不能在编译期确定,故new String()创建的字符串不放入常量池,他们有自己的地址空间。
String s0= "kvill";
String s1=new String("kvill");
String s2= "kv" + new String("ill");
System.out.println( s0==s1 );
System.out.println( s0==s2 );
System.out.println( s1==s2 );
输出结果:false false false
存在于.class文件中的常量池,在运行期被JVM装载,并且可以扩充。
String的intern()方法就是扩充常量池的一个方法;当一个String实例str调用intern()方法时,Java查找常量池中是否有相同Unicode的字符串常量,相同与否由equals()方法决定,如果有,则返回其的引用,如果没有,则在常量池中增加一个Unicode等于str的字符串并返回它的引用。
String str1 = "fly";
String str2 = "weight";
String str3 = "flyweight";
String str4 = null;
str4 = str1 + str2;
System.out.println("str3=?str4 " + (str3 == str4));
String str5 = "fly" + "weight";
System.out.println("str4=?str5 " + (str4 == str5));
str4 = (str1 + str2).intern();
System.out.println("str3=?str4 " + (str3 == str4));
System.out.println("str4=?str5 " + (str4 == str5));
输出结果:str3=?str4 false str4=?str5 false str3=?str4 true str4=?str5 true
结果解析:str1,str2,str3,str4,str5对应的内存存储样式如下图所示上部分所示,执行程序后字符串常量内存储样式如下部分所示。可以看出str3,str4,str5所指向的地址并不一样,故str3!=str4!=str5。当执行语句str4 = (str1 + str2).intern();时,会先在字符串常量池中寻找值为“flyweight”的地址,如果找到就将str4变量指向这个地址,如果没有找到就创建一个字符串常量并将其加入到常量池中,此处已经有这个值,所以str4变量直接指向这个地址。也就是说str3,str4,str5都指向同一个地址,三个引用指向同一个字符串对象。
关于equals()和==
这个对于String简单来说就是比较两字符串的Unicode序列是否相当,如果相等返回true;而==是比较两字符串的地址是否相同,也就是是否是同一个字符串的引用。
大家只要知道String的实例一旦生成就不会再改变了,比如说:String str=”kv”+”ill”+” “+”ans”;就是有4个字符串常量,首先”kv”和”ill”生成了”kvill”存在内存中,然后”kvill”又和” “ 生成 ”kvill “存在内存中,最后又和生成了”kvill ans”;并把这个字符串的地址赋给了str,就是因为String的“不可变”产生了很多临时变量,这也就是为什么建议用StringBuffer的原因了,因为StringBuffer是可改变的。
对于引用数据类型:引用数据类型数据.toString();因public String toString() 是类Object的一个方法,而java中其他类都是类Object的子类,所以所有的java类均会含有成员方法toString.在调用Syetem,out.println或者Syetem.out.print时,如果他们的参数是非字符串类型的数据,则一般将自动调用toString方法将其转换成字符串,然后输出。
两者是不一样的:
1、String s="java"; 这里产生得到一个字符串常量。系统首先会去“常量池”寻找,看是否有“java”常量存在,如果有那么就将这个常量“java”赋给s,如果没有则系统在常量池里创建“java”,并将其给s。
2、String s = new String("java"); 意思是根据常量“java”创建一个字符串的对象,如此他首先会执行1中的操作,然后根据常量“java”在堆中创建对象,并为其分配内存。
参考文章: http://blog.csdn.net/vebasan/article/details/5271063