java中String常量的存储原理

相关题目(运行结果在代码注释后面)

1、

package StringTest;

public class test1 {

public static void main(String[] args){
String a = "a1";//“a1”在编译的时候就能确定,所以编译的时候,a1被放进了常量池中,同时a指向常量池中的a1对象
String b = "a"+ 1;//a和1这两个常量都能在编译时确定,所以他们相加的结果也能确定,因此编译器检查常量池中是否有值为a1的String对象,发现有了,因此b也指向常量池中的a1对象
System.out.println(a==b);//==判断的是a和b是否指向同一个对象,也就是同一块内存区域
}//true
} 

2、

package StringTest;

public class test2 {
public static void main(String[] args){
String a = "ab";
String bb = "b";
String b = "a"+ bb; //编译器不能确定为常量
System.out.println(a==b);
}//false
} 

3、

package StringTest;

public class test3 {
public static void main(String[] args){
String a = "ab";
final String bb = "b";
String b = "a"+ bb; //bb加final后是常量,可以在编译器确定b
System.out.println(a==b);
}//true

} 

4、

package StringTest;

public class test4 {
public static void main(String[] args){
String a = "ab";
final String bb = getBB();
String b = "a"+ bb;//bb是通过函数返回的,虽然知道它是final的,但不知道具体是啥,要到运行期才知道bb的值
System.out.println(a==b);
}//false
private static String getBB(){ return "b"; }
} 

5、

package StringTest;

public class test5 {
private static String a = "ab";
public static void main(String[] args){
String s1 = "a";
String s2 = "b";
String s = s1 + s2;//+的用法
System.out.println(s == a);
System.out.println(s.intern() == a);
//intern的含义(intern()用来返回常量池中的某字符串,如果常量池中已经存在该字符串,则直接返回常量池中该对象的引用,否则,在常量池中加入该对象,然后 返回引用) }//flase true }

6、

package StringTest;

public class test6 {
private static String a = new String("ab");
public static void main(String[] args){
String s1 = "a";
String s2 = "b";
String s = s1 + s2;
System.out.println(s == a);
System.out.println(s.intern() == a);
System.out.println(s.intern() == a.intern());
}//flase false true
} 

详解

1、String使用private final char value[]来实现字符串的存储,也就是说String对象创建之后,就不能再修改此对象中存储的字符串内容,所以说String类型是不可变的(immutable);

2、单独使用""引号创建的字符串都是常量,编译期就已经确定存储到String Pool中;

3、使用new String("")创建的对象会存储到heap中,是运行期新创建的;

new创建字符串时首先查看池中是否有相同值的字符串,如果有,则拷贝一份到堆中,然后返回堆中的地址;如果池中没有,则在堆中创建一份,然后返回堆中的地址

(注意,此时不需要从堆中复制到池中,否则,将使得堆中的字符串永远是池中的子集,导致浪费池的空间)

4、使用只包含常量的字符串连接符如"aa" + "aa"创建的也是常量,编译期就能确定,已经确定存储到String Pool中;

5、使用包含变量的字符串连接符如"aa" + s1创建的对象是运行期才创建的,存储在heap中;

6、关于equals和==

(1)对于==,如果作用于基本数据类型的变量(byte,short,char,int,long,float,double,boolean ),则直接比较其存储的"值"是否相等;如果作用于引用类型的变量(String),则比较的是所指向的对象的地址(即是否指向同一个对象)。

(2)equals方法是基类Object中的方法,因此对于所有的继承于Object的类都会有该方法。在Object类中,equals方法是用来比较两个对象的引用是否相等,即是否指向同一个对象。

(3)对于equals方法,注意:equals方法不能作用于基本数据类型的变量。如果没有对equals方法进行重写,则比较的是引用类型的变量所指向的对象的地址;而String类对equals方法进行了重写,用来比较指向的字符串对象所存储的字符串是否相等。其他的一些类诸如Double,Date,Integer等,都对equals方法进行了重写用来比较指向的对象所存储的内容是否相等。

7、String、StringBuffer、StringBuilder的区别

(1)可变与不可变:String是不可变字符串对象,StringBuilder和StringBuffer是可变字符串对象(其内部的字符数组长度可变)。

(2)是否多线程安全:String中的对象是不可变的,也就可以理解为常量,显然线程安全。StringBuffer 与 StringBuilder 中的方法和功能完全是等价的,只是StringBuffer 中的方法大都采用了synchronized 关键字进行修饰,因此是线程安全的,而 StringBuilder 没有这个修饰,可以被认为是非线程安全的。

(3)String、StringBuilder、StringBuffer三者的执行效率:

StringBuilder > StringBuffer > String 当然这个是相对的,不一定在所有情况下都是这样。

比如String str = "hello"+ "world"的效率就比 StringBuilder st = new StringBuilder().append("hello").append("world")要高。因此,这三个类是各有利弊,应当根据不同的情况来进行选择使用:

当字符串相加操作或者改动较少的情况下,建议使用 String str="hello"这种形式;

当字符串相加操作较多的情况下,建议使用StringBuilder,如果采用了多线程,则使用StringBuffer。