Bootstrap

字符串常量池问题

字符串常量池问题

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个对象:

  1. 通过new创建一个String对象。
  2. 类加载完后会在字符串常量池中创建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. 对象1:new StringBuilder()对象
  2. 对象2:new String(“abc”)对象
  3. 对象3:常量池中的abc字符串实例
  4. 对象4:new String(“ABC”)对象
  5. 对象5:常量池中的ABC字符串实例
  6. 对象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
;