字符串常量池StringTable
一、String基本特性:
(1)字符串使用双引号""表示;
String str = "hello world!";//字面量的定义方式
String str2 = new String("hello");
debug查看str,每个字符占用一个char,一共有12个字符。

(2)声明为final,不可被继承;
(3)实现Serializable接口,表示支持序列化;
(4)实现Comparable接口,表示可比较大小;
(5)jdk1.8内部定义使用final char value[]存储字符串数据;
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
private final char value[];
...
}
(6)String字符串是不可变的。
//对字符串进行重新赋值,需要重新指定内存区域赋值。
String s1 = "abc";//字面量定义的方式,"abc"存储在字符串常量池中
String s2 = "abc";
System.out.println(s1 == s2);//true
s1 = "hello";
System.out.println(s1 == s2);//false
//现有字符串的连接操作,需要重新指定内存区域赋值。
String s1 = "abc";
String s2 = "abc";
s2 += "def";
System.out.println(s1 == s2);//false
System.out.println(s2);//abcdef
System.out.println(s1);//abc
//调用replace()方法时,需要重新指定内存区域赋值。
String s1 = "abc";
String s2 = s1.replace('a', 'm');
System.out.println(s1 == s2);//false
System.out.println(s1);//abc
System.out.println(s2);//mbc
(7)字符串常量池(String Pool)存储的字符串都是不同的。String Pool是一个固定大小的Hashtable,当存储非常多的String时,当它的StringTable越小,会造成Hash冲突,导致链表很长,调用String.intern时会影响性能。
二、String的内存分配
(1)常量池类似一个java系统级别提供的缓存。可通过以下两种方法实现String类型的常量池。
String str1 = "hello";//直接使用双引号声明的String对象会直接存储在常量池中
String str2 = new String("hello");
System.out.println(str1 == str2);//false
//使用String的intern()方法
String ss = str2.intern();
System.out.println(str1 == ss);//true
(2)从jdk1.7之后,字符串常量存放在堆中。
三、String拼接操作
(1)常量与常量的拼接是在常量池中,在编译期优化
String s1 = "a" + "b" + "c";//编译期优化:等同于"abc" String s2 = "abc"; //"abc"一定是放在字符串常量池中,将此地址赋给s2 /* * 最终.java编译成.class,再执行.class * String s1 = "abc"; * String s2 = "abc" */ System.out.println(s1 == s2); //true System.out.println(s1.equals(s2)); //true
(2)只要其中有一个是变量,结果就在堆中。变量拼接的原理是StringBuilder
String s1 = "a";
String s2 = "b";
String s3 = "ab";
/*
如下的s1 + s2 的执行细节:(变量s是我临时定义的)
① StringBuilder s = new StringBuilder();
② s.append("a")
③ s.append("b")
④ s.toString() --> 约等于 new String("ab")
补充:在jdk5.0之后使用的是StringBuilder,在jdk5.0之前使用的是StringBuffer
*/
String s4 = s1 + s2;//
System.out.println(s3 == s4);//false
(3)如下例子,当拼接的结果调用intern方法时,如果常量池中没有的该字符串,则将该对象引用放入池中,返回此对象在堆中地址。如果常量池已存在该字符串,则直接返回字符串在常量池中的地址。
String s1 = "hadoop"; String s2 = "javaEEhadoop"; String s3 = "javaEE" + s1; //intern():判断字符串常量池中是否存在javaEEhadoop值,如果存在,则返回常量池中javaEEhadoop的地址; //如果字符串常量池中不存在javaEEhadoop,则在常量池中会保存该对象的地址引用,并返回此常量池中的引用地址。 String s4 = s3.intern(); System.out.println(s2 == s4);//true
(4) 字符串拼接时如果符号两边都是字符串常量或常量引用,则在编译期优化
final String s1 = "a"; final String s2 = "b"; String s3 = "ab"; String s4 = s1 + s2; System.out.println(s3 == s4);//true
四、intern()的使用
(1)intern会从字符串常量池中查询当前字符是否存在,不存在就将当前字符串放入常量池中。
String s1 = new String("test").intern();
String s2 = "test";
System.out.println(s1 == s2);//true
(2)intern确保字符串在内存中只有一份拷贝,节省内存空间,字符串存放在常量池中
String s1 = "hello";//常量池中存在hello
String s2 = new String("hello");//堆中有String对象,但会引用常量池中hello的地址
System.out.println(s1 == s2);//false
String s3 = s2.intern();//因为常量池中存在hello,所以直接返回hello在常量池中的地址
System.out.println(s1 == s3);//true

(3)面试题
//1 new String("ab")会创建几个对象?
一个对象是:new关键字在堆空间创建的
另一个对象是:字符串常量池中的对象"ab"。 字节码指令:ldc
//s1指向堆空间的对象地址 String s1 = new String("a"); s1.intern(); //s2指向堆空间中常量池中的a String s2 = "a"; System.out.println(s1 == s2);//false
//2 new String("a")+new String("b")对创建几个对象
对象1:new StringBuilder()
对象2: new String("a")
对象3: 常量池中的"a"
对象4: new String("b")
对象5: 常量池中的"b"
深入剖析: StringBuilder的toString():
对象6 :new String("ab")
强调一下,toString()的调用,在字符串常量池中,没有生成"ab"
String s3 = new String("a") + new String("b");//等价new String("ab"),但常量池不生成字符串"ab" s3.intern();//由于常量池并不存在"ab",所以会将s3记录的对象地址存入常量池中 String s4 = "ab"; System.out.println(s3 == s4);//true s3 s4指向同一个地址
注:上述代码运行结果基于JDK1.8环境下运行
总结:
(1)调用intern方法时,如果常量池中存在该字符串,直接返回常量池中该字符串的地址。否则,将该对象的引用地址复制一份,保存在常量池中,并返回常量池中的引用地址。


浙公网安备 33010602011771号