字符串常量池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方法时,如果常量池中存在该字符串,直接返回常量池中该字符串的地址。否则,将该对象的引用地址复制一份,保存在常量池中,并返回常量池中的引用地址。

 

 

posted @ 2020-10-30 18:02  wulinsam  阅读(200)  评论(0)    收藏  举报