String对象创建的过程详解

String对象创建的过程详解

本文主要摘自于 http://blog.csdn.net/xiabing082/article/details/49759071这篇博客。

public class Class Test {
private String itemS ="我们 ";
private final int itemI =100 ;
public void setItemS (String para ){...}
}

常量池(Constant Pool)指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据编译器将源程序编译成class文件后,会用一部分字节分类存储这些粗体代码。而这些字节我们就称为常量池(它包括一个有序集和,包括直接常量(String,Integer和 Floating point常量)和对其他类型,字段和方法的符号引用)。事实上,只有JVM 加载class后,在方法区中为它们开辟了空间才更像一个“池”。
                                                                         表1. class文件共有11种常量表,如下所示:
常量表类型
标志值(占1 byte)
描述
CONSTANT_Utf8
1
UTF-8编码的 Unicode字符串
CONSTANT_Integer
3
int类型的字面值
CONSTANT_Float
4
float类型的字面值
CONSTANT_Long
5
long类型的字面值
CONSTANT_Double
6
double类型的字面值
CONSTANT_Class
7
对一个类或接口的符号引用
CONSTANT_String
8
String类型字面值的引用
CONSTANT_Fieldref
9
对一个字段的符号引用
CONSTANT_Methodref
10
对一个类中方法的符号引用
CONSTANT_InterfaceMethodref
11
对一个接口中方法的符号引用
CONSTANT_NameAndType
12
对一个字段或方法的部分符号引用

1、String s = "abc"; 
创建过程分析:在class文件被JVM装载到内存中,JVM会创建一块String Pool(String缓冲池)。当执行String s = “abc”;时,JVM首先在String Pool中查看是否存在字符串对象“abc”(如何查看呢?用equals()方法判断),如果已存在该对象,则不用创建新的字符串对象“abc”,而直接使用String Pool中已存在的对象“abc”,然后将引用s指向该对象;如果不存在该对象,则先在String Pool中创建一个新的字符串对象“abc”,然后将引用s指向String Pool中创建的新对象。 

注意:使用“字符串常量”引号创建的字符串对象时,在编译期就已经确定将该对象存储到String Pool中了。因此,String s = “abc”只会在编译期,在String Pool中创建一个对象。  

例如: 
[java] view plain copy
  1. String s1 = "abc";    
  2. String s2 = "abc";    
  3. System.out.println(s1 == s2);//true    
结果说明:JVM创建了两个引用str1和str2,但在String Pool中只创建了一个对象,而且两个引用都指向了同一个对象。 


2、String s = new String("abc"); 

创建过程分析:当执行String s = new String(“abc”);时,JVM首先在String Pool中查看是否存在字符串对象“abc”,如果不存在该对象,则先在String Pool中创建一个新的字符串对象“abc”,然后执行new String(“abc”)构造方法,在Heap里又创建一个新的字符串对象“abc”(new出来的对象都放在Heap里面),并将引用s指向Heap中创建的新对象;如果已存在该对象,则不用创建新的字符串对象“abc”,而直接使用String Pool中已存在的对象“abc”, 然后执行new String(“abc”)构造方法,在Heap里又创建一个新的字符串对象“abc”,并将引用s指向Heap中创建的新对象。 

注意:使用new String(“”)创建的字符串对象时,会在运行期创建新对象存储到Heap中。因此,new String(“abc”)创建字符串对象时,会创建2个对象,编译期在String Pool中创建一个,运行时Heap中创建一个。 

这里使用了
  1. * Initializes a newly created String object so that it
         * represents the same sequence of characters as the argument; in other
         * words, the newly created string is a copy of the argument string. Unless 
         * an explicit copy of original is needed, use of this 
         * constructor is unnecessary since Strings are immutable.
  2. public String(String original)  
翻译如下:这个构造方法来用来初始化新创建的String对象,使之具有与参数有相同的字符串顺序流。换句话说,新创建的字符串对象只是一个参数的拷贝。因为String对象是不可改变的,所以除非有必要显式地生成参数的副本,否则没必要用此构造方法来构造String对象。
这个构造方法,作用:初始化一个新创建的String对象,使其表示一个与参数相同的字符序列;换句话说,新创建的字符串是该参数字符串的副本。 
例如: 
  1. String s1 = new String("abc");  
  2. String s2 = new String("abc");  
  3. System.out.println(s1 == s2);//false  

结果说明:只要是用new()来新建对象的,都会在堆(Heap)中创建,而且其字符串是单独存值的,即使与String Pool中的数据相同,也不会与String Pool中的数据共享。 

例程1: 
  1. String s1 = "abcdef";  
  2. String s2 = "abcdef";  
  3. String s3 = "abc"+"def";//编译期自动优化为String s3 = "abcdef";  
  4. System.out.println(s1 == s2);  
  5. System.out.println(s1 == s3);  
  6. System.out.println(s2 == s3);  

运行结果: 
true 
true 
true 
结果说明:字符串常量生成的字符串对象在String Pool中只有一个拷贝,且它是在编译期就被确定了,所以“s1 == s2”;“abc”和“def”都是字符串常量,当一个字符串由多个字符串常量连接而成时,它自己也肯定是字符串常量(它在编译期就被解析为一个字符串对象了,即class文件中就已经存在“abcdef”),所以在字符串生成字符串对象时,s3也是String Pool中“abcdef”的一个引用。故JVM对于字符串常量的"+"号连接,在程序编译期,JVM就将常量字符串的"+"连接优化为连接后的值。 

例程2: 
  1. String s1 = "abc";  
  2. String s2 = "def";  
  3. String s3 = "abcdef";  
  4. String s4 = "abc"+"def";  
  5. String s5 = s1 + "def";  
  6. String s6 = "abc"+s2;  
  7. String s7 = s1 + s2;  
  8. System.out.println(s3 == s4);  
  9. System.out.println(s3 == s5);  
  10. System.out.println(s3 == s6);  
  11. System.out.println(s3 == s7);  

运行结果如下: 
true 
false 
false 
false 
结果说明:JVM对于有字符串引用存在的字符串"+"连接中,而引用的值在程序编译期是无法确定的,即s1 + “def”无法被编译器优化,只有在程序运行期来动态分配并将连接后的新地址赋给s5。 

例程3: 
  1. final String s1 = "abc";  
  2. String s2 = "def";  
  3. String s3 = "abcdef";  
  4. String s4 = "abc"+"def";  
  5. String s5 = s1 + "def";  
  6. String s6 = "abc"+s2;  
  7. String s7 = s1 + s2;  
  8. System.out.println(s3 == s4);  
  9. System.out.println(s3 == s5);  
  10. System.out.println(s3 == s6);  
  11. System.out.println(s3 == s7);  

运行结果如下: 
true 
true 
false 
false 

例程4: 
  1. final String s1 = "abc";  
  2. final String s2 = "def";  
  3. String s3 = "abcdef";  
  4. String s4 = "abc"+"def";  
  5. String s5 = s1 + "def";  
  6. String s6 = "abc"+s2;  
  7. String s7 = s1 + s2;  
  8. System.out.println(s3 == s4);  
  9. System.out.println(s3 == s5);  
  10. System.out.println(s3 == s6);  
  11. System.out.println(s3 == s7);  

运行结果如下: 
true 
true 
true 
true 
结果说明:例程3和例程4与例程2的区别是,例程3在字符串s1前加了final修饰,例程4在字符串s1和s2前都加了final修饰。对于final修饰的变量,它在编译时被解析为常量值的一个本地拷贝存储到自己的常量池中或嵌入到它的字节码流中。所以此时的s1 + “def”和"abc" + "def"效果是一样的。接着后面两个含引用的字符串连接,JVM会进行相同的处理。故上面程序后面三个的结果为true。 

可以发现是通过new String(..)返回了一个String对象,也就是说在堆中创建了对象。这时候会不会在池中出现"abc"这个对象呢?(question还没解决) 

生成String s的过程中,编译器使用sb执行的过程:创建一个StringBuffer对象,使用append()向此StringBuffer对象直接添加新的字符串(而不是每次制作一个新的副本)。 

对于String c = "c";String s = "a" + "b" + c;,编译器将会先将"a" + "b"作为编译时常量,优化生成成字面常量"ab" ,然后生成一个StringBuilder对象,接着调用两次 append()方法,即: 
String s = new Builder().append("ab").append(c) .toString(); 

对于String a = "a";String s = a + "b" + "c";,编译器分析a为引用变量,后面的"b" + "c"就不会作为编译时常量来运算了。相当于执行: 
String s = new Builder().append(a).append("b") .append("c") .toString(); 

对于String b = "b";String s = "a" + b + "c";],这种形式的就没办法优化了,直接生成StringBuilder对象,然后调用三次 append()方法,即: 
String s = new Builder().append("a").append(b) .append("c") .toString(); 

接着,我们再看以下代码: 
  1. String str1 = "abc";//是字符串常量,它在编译期被确定,放在常量池中(共享内容值)  
  2. //new String()创建的字符串不放入常量池中  
  3. String str2 =new String("abc");//不是字符串常量,不在编译期确定(不共享内容值)  

  1. String str1 = new String("abc");  
  2. String str2 = "abc";  
  3. System.out.println(str1==str2);  //false  

创建了两个引用。创建了两个对象。两个引用分别指向不同的两个对象。 

  1. String str1 = "abc";  
  2. String str2 = new String("abc");  
  3. System.out.println(str1==str2);  //false  

创建了两个引用。创建了两个对象。两个引用分别指向不同的两个对象。 

接下来我们再来看看intern()方法,它的定义如下: 
  1. public native String intern();   

这是一个本地方法。在调用这个方法时,JAVA虚拟机首先检查String Pool中是否已经存在与该对象值相等对象存在,如果有则返回字符串池中对象的引用;如果没有,则先在String Pool中创建一个相同值的String对象,然后再将它的引用返回。 

例程6: 
[java] view plain copy
  1. public class TestString{    
  2.     public static void main(String args[]){    
  3.         String s1 = new String("abc");//语句1    
  4.         String s2 = "abc";//语句2    
  5.         String s3 = new String("abc");//语句3    
  6.     
  7.         System.out.println(s1 == s2);//语句4    
  8.         System.out.println(s1 == s3);//语句5    
  9.         System.out.println(s2 == s3);//语句6    
  10.     
  11.         System.out.println(s1 == s1.intern());//语句7    
  12.         System.out.println(s2 == s2.intern());//语句8    
  13.         System.out.println(s1.intern() == s2.intern());//语句9    
  14.     
  15.         String hello = "hello";//语句10    
  16.         String hel = "hel";//语句11    
  17.         String lo = "lo";//语句12    
  18.     
  19.         System.out.println(hello == "hello");//语句13    
  20.         System.out.println(hello == "hel" + "lo");//语句14    
  21.         System.out.println(hello == "hel" + lo);//语句15    
  22.         System.out.println(hello == hel + lo);//语句16    
  23.     }    
  24. }    
问题1:当执行完语句(1)时,在内存里面生成几个对象?它们是什么?在什么地方? 
--->当执行完语句(1)时,在内存里面创建了两个对象,它们的内容分别都是abc,分别在String Pool(常量池)和Heap(堆)里。
其字符串的创建过程如下:首先在String Pool里面查找查找是否有 "abc",如果有就直接使用,但这是本程序的第一条语句,故不存在一个对象"abc",所以要在String Pool中生成一个对象"abc",接下来,执行new String("abc")构造方法,new出来的对象都放在Heap里面。在Heap里又创建了一个"abc"的对象。这时内存里就有两个对象了,一个在String Pool 里面,一个在Heap里面。 

问题2:当执行完语句(2)时,在内存里面一共有几个对象?它们是什么?在什么地方? 
当执行完语句(2)时,在内存里面一个对象也没有创建。当我们定义语句(2)的时候,如果我们用字符串的常量值(字面值)给s2赋值的话,那么首先JVM还是从String Pool里面去查找有没有内容为abc的这样一个对象存在,我们发现当我们执行完语句(1)的时候,StringPool里面已经存在了内容为abc的对象,那么就不会再在String Pool里面去生成内容为abc的字符串对象了。而是会使用已经存在String Pool里面的内容为abc的字符串对象,并且会将s2这个引用指向String Pool里面的内容为abc的字符串对象,s2存放的是String Pool里面的内容为abc的字符串对像的地址。也就是说当你使用String s2 = "abc",即使用字符串常量("abc")给定义的引用(str2)赋值的话,那么它首先是在String Pool里面去找有没有内容为abc的字符串对象存在,如果有的话,就不用创建新的对象,直接引用String Pool里面已经存在的对象;如果没有的话,就在 String Pool里面去创建一个新的对象,接着将引用指向这个新创建的对象。所以,当执行完语句(2)时内存里面一共有2个对象,它们的内容分别都是abc,在String Pool里面一个内容abc的对象,在Heap里面有一个内容为abc的对象。 

问题3:当执行完语句(3)时,在内存里面一共有几个对象?它们是什么?在什么地方? 
当执行完语句(3)时,其执行过程是这样的:它首先在String Pool里面去查找有没有内容为abc的字符串对象存在,发现有这个对象存在,它就不去创建 一个新的对象。接着执行new...,只要在java里面有关键字new存在,不管内容是否相同,都表示它将生成一个新的对象,new多少次,就生成多少个对象,而且新生成的对象都是在Heap里面,所以它会在Heap里面生成一个内容为abc的对象,并且将它的地址赋给了引用s3,s3就指向刚在Heap里面生成的内容为abc的对象。所以,当执行完语句(3)时,内存里面一共有3个对象,其中包含了在String Pool里面一个内容为abc的字符串对象和在Heap里面包含了两个内容为abc的字符串对象。 

问题4:当执行完语句(4)(5)(6)后,它们的结果分别是什么? 
在java里面,对象用"=="永远比较的是两个对象的内存地址,换句话说,是比较"=="左右两边的两个引用是否指向同一个对象。对于java里面的8种原生数据类型来说,"=="比较的是它们的字面值是不是一样的;对应用类型来说,比较的是它们的内存地址是不是一样的。在语句(1)(2)(3)中,由于s1、s2、s3指向不同的对象,它们的内存地址就不一样,因此可以说当执行完语句(4)(5)(6),它们返回的结果都是false。 

问题5:当执行完语句(7)(8)(9)后,它们的结果分别是什么? 
首先,s1这个对象指向的是堆中第一次new...生成的对象,当调用 intern 方法时,如果String Pool已经包含一个等于此 String 对象的字符串(该对象由equals(Object)方法确定),则返回指向String Pool中的字符串对象的引用。因为String Pool中有内容为abc的对象,所以s1.intern()返回的是String Pool中的内容为abc的字符串对象的内存地址,而s1却是指向Heap上内容为abc的字符串对象的引用。因而,两个引用指向的对象不同,所以,s1 == s1.intern() 为false,即语句(7)结果为false。 
对于s2.intern(),它还是会首先检查String Pool中是否有内容为abc的对象,发现有,则将String Pool中内容为abc的对象的地址赋给s2.intern()方法的返回值。因为s2和s2.intern()方法的返回值指向的是同一个对象,所以,s2 == s2.intern()的结果为true,,即语句(8)结果为true。 
对于s1.intern(),它首先检查String Pool中是否有内容为abc的对象,发现有,则将String Pool中内容为abc的对象的赋给s1.intern()方法的返回值。对于s2.intern(),首先检查String Pool中是否有内容为abc的对象,发现有,则将String Pool中内容为abc的对象的地址赋给s2.intern()方法的返回值。因为两者返回的地址都指向同一个对象,所以,s1.intern() == s2.intern()的结果为true,,即是语句(9)结果为true。 
因此,当执行完语句(7)(8)(9)后,它们的结果分别是false、true、true。 

问题6:当执行完语句(13)(14) (15)(16)后,它们的结果分别是什么? 
hello == "hello"引用hello指向的对象就是String Pool中的“hello”,即语句(13)的结果为true。 
hello == "hel" + "lo"当加号两边都是常量值时,就会组成一个新的常量值"hello"在String Pool里面,如果String Pool已经有相同内容的就不会再创建,则直接返回String Pool里面的内容为"hello"的字符串对象的内存地址,所以,hello == "hel" + "lo"结果为true。 
hello =="hel" + lo 当加号两边有一个不是常量值,会在堆里面创建一个新的"hello"对象,一个在String Pool中,一个在Heap中,故输出false 。 
hel + lo 同上,输出false。 
因此,当执行完语句(7)(8)(9)后,它们的结果分别是true、true、false、false。 



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值