字符串的两种定义方式
String s1 = "abc" ; // 字面量的定义方式,字符串存储在常量池中
String s2 = new String("hello"); // new 对象的方式
注意事项:
-
String
被声明为final
的,不可被继承。 -
String
实现了Serializable接口:表示字符串是支持序列化的。实现了Comparable
接口:表示String
可以比较大小。 -
String
在jdk8
及以前内部定义了final char value[]
用于存储字符串数据。JDK9
时改为byte[]
。改变的主要原因是节省空间,因为很多字符串都是一个字节。 - 字符串常量池不会存储相同内容的字符串。
-
jdk7
之后将字符串常量池放到了堆中,之前是在永久代里面。
字符串拼接操作注意事项:
- 常量与常量的拼接结果在常量池,原理是编译期优化,常量池中不会存在相同内容的变量。拼接的字符串中只要其中有一个是变量,结果就在堆中。
- 变量拼接的原理是
StringBuilder
,如果拼接的结果调用intern()
方法,根据该字符串是否在常量池中存在,分为:
2.1 存在: 则返回字符串在常量池中的地址如果字符串常量池中
2.2 不存在: 则在常量池中创建一份,并返回此对象的地址
String s1 = "a" + "b" + "c";//编译期优化:等同于"abc"
String s2 = "abc"; //"abc"一定是放在字符串常量池中,将此地址赋给s2
字符串拼接的底层操作
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"),但不等价 */
String s4 = s1 + s2;//
System.out.println(s3 == s4);//false
// 但是如果用final修饰了,s4+s5就相当于"a"+"b"
final String s4 = "a";
final String s5 = "b";
String s6 = "ab";
String s7 = s4 + s5;
System.out.println(s6 == s7);//true
注意:
- 通过
StringBuilder
的append()
的方式添加字符串的效率要远高于使用String
的字符串拼接方式! - 原因1:
StringBuilder
的append()
的方式:自始至终中只创建过一个StringBuilder
的对象 - 原因2:使用
String
的字符串拼接方式:创建过多个StringBuilder
和String
(调的toString
方法)的对象,内存占用更大;如果进行GC
,需要花费额外的时间(在拼接的过程中产生的一些中间字符串可能永远也用不到,会产生大量垃圾字符串)。
intern方法
-
intern
是一个native
方法,调用的是底层C的方法字符串常量池池最初是空的,由String类私有地维护。 - 在调用
intern
方法时,如果池中已经包含了由equals(object)
方法确定的与该字符串内容相等的字符串,则返回池中的字符串地址。否则,该字符串对象将被添加到池中,并返回对该字符串对象的地址。
new String()的说明
-
new String("ab")
会创建几个对象?
- 一个对象是:
new
关键字在堆空间创建的 - 另一个对象是:字符串常量池中的对象
"ab"
。(如果之前字符串常量池中没有“ab”
的话)
-
new String(“a”) + new String(“b”)
会创建几个对象?代码
- 对象1:
new StringBuilder()
- 对象2:
new String("a")
- 对象3: 常量池中的
"a"
(如果之前字符串常量池中没有“a”
的话) - 对象4:
new String("b")
- 对象5: 常量池中的
"b"
(如果之前字符串常量池中没有“b”
的话) - 对象6 :
new String("ab")
,此处注意在字符串常量池中,没有生成"ab"
。
- 特殊情况
String s = new String("1");
s.intern();//调用此方法之前,字符串常量池中已经存在了"1"
String s2 = "1";
System.out.println(s == s2);//jdk6:false jdk7/8:false
/*
1、s3变量记录的地址为:new String("11")
2、经过上面的分析,我们已经知道执行完pos_1的代码,在堆中有了一个new String("11")
这样的String对象。但是在字符串常量池中没有"11"
3、接着执行s3.intern(),在字符串常量池中生成"11"
3-1、在JDK6的版本中,字符串常量池还在永久代,所以直接在永久代生成"11",也就有了新的地址
3-2、而在JDK7的后续版本中,字符串常量池被移动到了堆中,此时堆里已经有new String("11")了于节省空间的目的,直接将堆中的那个字符串的引用地址储存在字符串常量池中。没错,字符串常量池存的是new String("11")在堆中的地址,所以s4指向了new String("11")。
4、所以在JDK7后续版本中,s3和s4指向的完全是同一个地址。
*/
String s3 = new String("1") + new String("1");//pos_1
s3.intern();
String s4 = "11";//s4变量记录的地址:使用的是上一行代码代码执行时,在常量池中生成的"11"的地址
System.out.println(s3 == s4);//jdk6:false jdk7/8:true