在Java语言中,字符串起着非常重要的作用,字符串的声明与初始化主要有如下两种情况:
1.String s1=“abc”
对于String s1=“abc”与String s2=“abc”语句,在JVM中存在这一个字符串常量池(其中保存着很多String对象,并且可以被共享使用,s1和s2引用的是同一个常量池中的对象。
注:运行时常量池(包含字符串常量池),在jdk1.7之前存放在方法区中,在jdk1.7字符串常量池从运行时常量池中分离出来,放在了堆里。jdk1.8中由于取消了永久代,因此方法区的的实现方式有变动,大家可以自行了解。
由于String的实现采用了Flyweight的设计模式,在创建一个字符串常量的时候,例如String s=“abc”,会首先在字符串常量池中查找是否已经有相同的字符串被定义,其判断依据是String equals(Object obj)方法的返回值。若已经定义了,则直接获取对其的引用,此时不再创建新的对象;如果没有定义的话,则首先创建这个对象,然后把它加入到字符串池中,再将它的引用返回。由于String是不可变类,一旦创建好了就不能够被修改,因此String对象可以被共享而且不会导致程序的混乱。
2.String s1=new String(“abc”)
对于String s1=new String(“abc”)语句与String s2=new String(“abc”),存在两个引用对象s1、s2,两个内容相同的字符串“abc”,它们在内存中的地址是不同的,即只要用到new总会生成新的对象。不过,字面量“abc”依旧会存在字符串常量池里面。
具体而言:
String s=“abc”; //把“abc”放到字符串常量池中,该过程在编译时产生
String s=“ab”+“c”; //把“ab”+“c”转换为字符串常量“abc”放到字符串常量池。
String s=new String(“abc”); //在运行时把“abc”放到堆里。
例如:
String s1=“abc”; //在字符串常量池中存放了一个“abc”字符串对象
String s2=“abc”; //s2引用字符串常量池中的对象,因此不会产生新对象
String s3=new String (“abc”); //在队中创建新的对象
String s4=new String (“abc”); //在队中又创建了一个新的对象
为了便于理解,可以将String s=new String(“abc”)的语句的执行人为地分解成两个过程。
第一个过程是新建对象的过程,即new String (“abc”);第二个过程是赋值的过程,即String s=。
由于第二个过程只是定义了一个名为s的String类型的变量,将一个String类型对象的引用赋值给s,因此在这个过程中不会创建新的对象。
第一个过程中new String(“abc”)会调用String类的构造函数:
public String(String original){
//body
}
在调用这个构造函数的时候,传入了一个字符串常量,因此语句new String(“abc”)也就等价于“abc”和new String()两个操作了。若在字符串常量池中不存在“abc”,则会创建一个字符串常量“abc”,并将其添加到字符串常量池中(可以认为在常量池中创建一个“abc”对象);若存在,则不创建,然后new String()会在队中创建一个新的对象,所以s3,s4指向的是队中不同的String对象,因此s3和s4的地址自然也就不相同了。
如图:
好的,既然学了知识,那么接下来就来回答一个问题:对于String类型的变量s,赋值语句s=null与s=“”是否相同?
思考一下......
怎么样,有答案了咩?
让我来揭晓答案吧——
对于赋值语句s=null,其中s是一个字符串类型的引用,它不指向任何一个字符串。而赋值语句s=“”中的s虽然也是一个字符串类型的引用,但是它指向另外一个字符串(这个字符串的值为“”,即空字符串),因此这两者是不同的。
这题是不是很简单呀?那我们再来试试下一个问题:
new String(“abc”)创建了几个对象?
创建了几个呢?
答案揭晓:一个或者两个。如果字符串常量池中原来有“abc”,那么只创建一个对象;如果字符串常量池中没有字符串“abc”,那么就会创建两个对象。
下面是一些例子方便大家理解:
1. 执行语句String str="abc";时。首先查看字符串池中是否存在字符串"abc",如果存在则直接将“abc”赋给str,如果不存在则先在 字 符串池中新建一个字符串"abc",然后再将其赋给str.
2. 执行语句String str = new String("abc");时。不管字符串池中是否存在字符串“abc”,直接新建一个字符串“abc”,(注意,新建的字符串“abc”不是在字符串池中), 然后将其赋给str。由此可见 1.的效率要高于2的效率。
3. String str1="java";//指向字符串池
String str2="blog";//指向字符串池
String s = str1+str2;
按照以前的说法,+运算符会在堆中建立起两个String对象,这两个对象的值分别是“java”,"blog",也就是说从字符串常量池中复制这两个值,然后再堆中创建两个对象。然后再建立对象s,然后将“javablog”的堆地址赋给s. 这句话共创建了3个String对象。
但是JDK1.8现在对于这个语句时用StringBiulder来执行的,因此只生成了一个对象。
System.out.println(s=="javablog");//结果是false;
JVM确实对形如String str="javablog";的对象放在常量池中,但是它是在编译时name做的。而String s=str1+str2;是在运行时候才能知道的,也就是说str1+str2是在堆里创建的,所以结果为false了。
String s="java"+"blog";//直接将javablog对象放入字符串池中。 System.out.println(s=="javablog");//结果是true;
String s=str1+"blog";//不放在字符串池中,而是在堆中分分配。 System.out.println(s=="javablog");//结果是false;
总之,创建字符串有两种方式:两种内存区域(pool,heap)
1.""创建的字符串在字符串池中。
2.new 创建字符串时,首先查看池中是否有相同的字符串,如果有则拷贝一份放到堆中,然后返回堆中的地址;如果池中没有则在堆中创建一分,然后返回堆中的地址,
3.在对字符串赋值时,如果右操作数含有一个或一个以上的字符串引用时,则在堆中再建立一个字符串对象,返回引用如:String s= str1+"blog";
之间的区别
第1种: String a="abc"; String b="abc"; System.out.print(a==b); 结果:true 原因:编译时,这两个"abc"被认为是同一个对象保存到了常量池中;运行时JVM则认为这两个变量赋的是同一个对象,所以返回true。 --------------------- 第2种: String a=new String("abc"); String b=new String("abc"); System.out.print(a==b); 结果:false 原因:用构造器创建的对象,是不会被放入常理池中的,也很明显这完全是两个对象,只是内容相同罢了,结果当然为false了。用equals()或者System.out.print(a.intern()==b.intern());就返回true了。 ------------------------------ 第3种 String a="abc"; String b=new String("abc"); System.out.print(a==b); 结果:false 原因:同上。此外,a的类加载时就完成了初始化,而b要在执行引擎执行到那一行代码时才完成初始化。 --------------------------- 第4种 String a="abcdef"; System.out.print(a=="abcdef"); 结果:true 原因:运行出现的字符串常量,若是在常量池中出现过,则JVM会认为同一个对象,以节省内存开销,所以这两个字符串会被认为是同一个对象。 ------------------------------------------- 第5种 String a="abcdef"; String b=""; String c=a+b; System.out.print(c=="abcdef"); 结果:false 原因:编译时,先将"abcedf"放在常量池中,而c的值则是在运行时在堆里创建的。所以为false。
- Object o与Object o=null的区别
具体差别如下:
class Test {
public static void main(String[] args) {
Object o1;
o1.toString(); /*这里编译不能通过,编译器只认定o1是个引用,没指向任何对象,所以不能调用方法。*/
Object o2 = null;
o2.toString(); /*这里编译可以过,但是有空指针异常,编译器认定o2是一个对象,虽然是一个空对象。*/
}
}
null对象是一个特殊的对象,他可以是任何类型。他只是作为一个标记而已,只是为了标记不存在而存在的。也没必要去追究他在内存是什么样。null就是一个标记而已。容器可以接受一个空对象,但是一个空引用则是不接受的。
Object o; //这种写法只是分配一个引用,没有分配内存。
Object o = null; //这么写则是给引用指向了一个空对象。分配了内存(空对象),所以编译不会报错,运行时报空指针异常。
好啦,以上就是关于字符串的创建和存储机制的知识总结啦~如果大家有什么疑问,或者发现文中有什么描述不对的地方,欢迎大家留言评论,我们一起学习呀~~
Biu~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~pia!