文章目录
字符串常量池问题
String基本特性
- String声明为
final
,表示不能被继承。 - String实现了
serializable
接口,表示支持序列化;实现了Comparable
接口,表示可以做比较。 - 在JDK8及以前String内部定义了
final char[] value
用于存储数据,在JDK9改为byte[]
,节约了空间。 - 在JDK8中,字符串常量池属于方法区但存放在堆区,字符串常量池是不会存储相同内容的字符串的。
String不可变的优点:
- String类型使用最多,参数传递时更加安全。
- 在多线程下绝对安全。
- 字符串在常量池中共享,节约内存,提高效率。
String类中的substring/replace/toLowerCase
等方法都是在内部再创建一个新的String类对象。
字符串地址问题
String s1 = "a"; //串池
String s2 = "b"; //串池
String s3 = "ab"; //串池
String s4 = s1 + s2; //堆区,new StringBuilder().append("a").append("b").toString() -> new String("ab")
String s5 = "a" + "b"; //编译期优化,直接转为“ab”
System.out.println(s3 == s4); //false
System.out.println(s3 == s5); //true
final String s1 = "a"; //串池
final String s2 = "b"; //串池
final String s3 = "ab"; //串池
String s4 = s1 + s2; //编译期优化,直接转为“ab”
String s5 = "a" + "b"; //编译期优化,直接转为“ab”
System.out.println(s3 == s4); //true
System.out.println(s3 == s5); //true
String s1 = "abc"; //串池
String s2 = new String("abc"); //String对象在堆区,指向串池
System.out.println(s1 == s2); //false
String对象创建问题
new String(“hello”) 会创建几个对象?
查看字节码:
0 new #2 <java/lang/String>
3 dup
4 ldc #3 <abc>
6 invokespecial #4 <java/lang/String.<init> : (Ljava/lang/String;)V>
9 pop
10 return
会创建2个对象:
- 通过new创建一个String对象。
- 类加载完后会在字符串常量池中创建
hello
字符串实例
new String(“abc”) + new String(“ABC”)会创建几个对象
0 new #2 <java/lang/StringBuilder>
3 dup
4 invokespecial #3 <java/lang/StringBuilder.<init> : ()V>
7 new #4 <java/lang/String>
10 dup
11 ldc #5 <abc>
13 invokespecial #6 <java/lang/String.<init> : (Ljava/lang/String;)V>
16 invokevirtual #7 <java/lang/StringBuilder.append : (Ljava/lang/String;)Ljava/lang/StringBuilder;>
19 new #4 <java/lang/String>
22 dup
23 ldc #8 <ABC>
25 invokespecial #6 <java/lang/String.<init> : (Ljava/lang/String;)V>
28 invokevirtual #7 <java/lang/StringBuilder.append : (Ljava/lang/String;)Ljava/lang/StringBuilder;>
31 invokevirtual #9 <java/lang/StringBuilder.toString : ()Ljava/lang/String;>
34 astore_1
35 return
会创建6个 对象:
- 对象1:new StringBuilder()对象
- 对象2:new String(“abc”)对象
- 对象3:常量池中的
abc
字符串实例 - 对象4:new String(“ABC”)对象
- 对象5:常量池中的
ABC
字符串实例 - 对象6:调用StringBuilder的toString()方法,会创建并返回一个String对象
注意:toString()
方法,本质是调用new String()
重新创建一个String对象,创建的abcABC
字符串不是常量,所以不会放入常量池,String类的本质是char数组。
intern()的使用
情况1
String#intern()
方法会将字符串对象尝试放入串池。如果串池中存在,则不会放入,返回串池中的对象的地址;如果串池中不存在,则会将对象的引用地址复制一份,放入串池,并返回串池中的引用地址。
String s1 = new String("ab") + new String("c");
String s2 = s1.intern();
System.out.println(s1 == s2); //true
说明:s1指向堆中的new String("abc")
的地址,执行intern()
方法池化操作后,串池中的"abc"
指向堆区的new String("abc")
,并将地址返回给s2,所以s1和s2指向同一对象。
情况2
String s1 = new String("ab") + new String("c");
String s2 = "abc";
s1.intern();
System.out.println(s1 == s2); //false
说明:s1指向堆中的new String("abc")
,s2指向串池中的"abc"
,s1执行intern()
方法,尝试将“ab”字符串放入串池,但是串池已经存在所不会放入,所以s1仍然指向堆中,s2指向串池。
情况3
String s1 = new String("ab") + new String("c");
s1.intern();
String s2 = "abc";
System.out.println(s1 == s2); //true
说明:s1指向堆中的new String("abc")
,s1执行intern()
方法池化操作后,将"abc"
放入串池,串池中的"abc"
并指向堆中的地址,s2指向串池中的"abc"
的地址,所以s1和s2指向同一对象。
间接证明字符串常量池的位置
在JDK8下设置:-Xmx5m -XX:-UseGCOverheadLimit
List<String> list = new ArrayList<>();
int i = 0;
try {
for (int j = 0; j < 260000; j++) {
list.add(String.valueOf(j).intern());
i++;
}
} catch (Throwable e) {
e.printStackTrace();
} finally {
System.out.println(i);
}
//抛出异常:java.lang.OutOfMemoryError: Java heap space