【java系列】String,字符串常量池和intern()方法详解

目录

  • 字符串常量池
    • 一、常量池是什么?
    • 二、为什么要有字符串常量池?
    • 三、字符串常量池内的对象怎么生成?
      • 1. 主要的生成方法
      • 2. String 拼接生成
        • 1 字符串拼接
        • 2 引用拼接
        • 3 final拼接
  • Intern()方法
    • 一、intern()方法的作用
    • 二、intern()方法的返回值
  • 做题目
    • 一、String s = new String("abc")创建了几个对象?
    • 二、以下两段代码的输出结果
      • 代码片段1
        • 对于s3和s4
        • 对于s和s2
      • 代码片段2
        • 对于s3和s4
        • 对于s和s2

字符串常量池

一、常量池是什么?

常量池在java用于保存在编译期已确定的,已编译的class文件中的一份数据,包括了关于类,方法,接口等中的常量,也包括字符串常量
字符串常量池是一个固定大小的Hashtable,用来存放字符串。
在堆(heap)区分配了一部分内存空间作为常量池区。

二、为什么要有字符串常量池?

String类的使用频率极高。
为了避免JVM 首先创建大量的字符串对象,然后再进行垃圾回收。
以节约内存和提高运行效率。

三、字符串常量池内的对象怎么生成?

1. 主要的生成方法

  1. 直接使用双引号声明出来的String对象,直接存储在常量池中。
  2. 直接使用 new 关键字创建的字符串对象会先从字符串常量池中找,如果没找到就在字符串常量池中创建一个,然后再在堆中创建字符串对象;如果找到了,就直接在堆中创建字符串对象。
  3. 如果不是用双引号声明的String对象,也不是直接new出来的对象,可以使用intern方法实现常量池的添加。(如 String s1 = new String(“hello”) + new String(“world”);创建的s1对象)

2. String 拼接生成

1 字符串拼接

public class StringTest {
    public static void main(String[] args) {
        String a = "wechat" + "zhou";
    }
}

wechat和 zhou 都是字符串,编译时可知,编译器会自动将这行代码优化为如下代码

public class StringTest {
    public static void main(String[] args) {
      String a = "wechatzhou";  
    }
}

2 引用拼接

(其实是上述主要的生成方法中的第三点,需要使用intern()方法
引用拼接(如s3)的最终内容,是不会在常量池存在的
String s1=“1”;创建了1个"1"对象,在常量池。
String s2=new String(“1”);创建了两个"1"对象,一个在常量池,一个在heap区。
String s3=“1”+“1”;创建了一个"11"对象,不在常量池,在heap区。

public class StringTest {
    public static void main(String[] args) {
        String a = "we";
      	String b = a + "chat";
      	// 上下含义相同
      	String c = "moz";
      	String d = "bad";
      	String f = c + d;
    }
}

由于引用的值在程序编译期是无法确定的
当存在字符串引用(a+"chat"或c+d)的拼接时,Java编译器会创建一个StringBuilder对象,通过append()方法实现拼接。
"wechat"并不会添加到字符串常量池, 但是可以通过b.intern() 方法添加到常量池

3 final拼接

效果和字符串拼接一致

public class StringTest {
    public static void main(String[] args) {
        final String a = "we";
     		final String b = "chat";
      	String c = a + b + "handsome";
    }
}

final修饰的字符串就是在编译期可知的,编译期就会将以上代码优化为

public class StringTest {
    public static void main(String[] args) {
       			final String a = "we";
     			final String b = "chat";
				String c= "wechathandsome";
    }
}

Intern()方法

一、intern()方法的作用

可以实现常量池中的添加
1 如果常量池中存在当前字符串, 就会直接返回当前字符串.
2 如果常量池中不存在当前字符串, 会将此字符串放入常量池中, 再返回.

二、intern()方法的返回值

1 如果常量池中存在当前字符串, 就会直接返回当前字符串的引用。
2 如果常量池中不存在当前字符串:
若heap区存在该字符串的对象,则将该对象移至常量池,且返回该对象的引用
若heap区也不存在该对象,则在常量池创建该字符串,并返回该字符串的引用
(String s="1"等价于String s=“1”.intern())

做题目

常量池在堆区里边,但为了便于描述,以下的堆区都是指堆区除去常量池区以后的地方。

一、String s = new String(“abc”)创建了几个对象?

   创建了1或2个对象。(取决于字符串常量池中原先是否存在abc字符串)
1 如果字符串常量池已存在abc字符串,则创建一个对象,在堆区。
2 如果字符串常量池不存在abc字符串,则创建两个对象,一个在常量池,一个在堆区。

二、以下两段代码的输出结果

jdk6和jdk区的区别:常量区从堆区外移至堆区内。

代码片段1

public static void main(String[] args) {
    String s = new String("1");
    s.intern();
    String s2 = "1";
    System.out.println(s == s2);

    String s3 = new String("1") + new String("1");
    s3.intern();
    String s4 = "11";
    System.out.println(s3 == s4);
}

jdk6 下false false
jdk7 下false true

对于s3和s4

String s3 = new String(“1”) + new String(“1”);
生成了2个最终对象,分别是字符串常量池中的“1” 和 JAVA Heap 中的 s3引用指向的对象。中间还有2个匿名的new String(“1”)不去讨论。
此时s3引用对象内容是”11”,常量池中没有 “11”。
s3.intern();将 s3中的“11”字符串放入 String 常量池中。
jdk6: 在perm区字符串常量池中生成一个 “11” 的对象。
jdk7: 常量池不在 Perm 区域了而在heap区,不需要再创建一份对象了,而是可以直接存储堆中的引用。这份引用指向 s3 引用的对象。 也就是说引用地址是相同的。
最后String s4 = “11”; 这句代码中”11”是显示声明的,会直接去常量池中创建,发现已经存在这一字符串,也就是指向 s3 引用对象的一个引用。所以 s4 引用就指向和 s3 一样了。因此最后的比较 s3 == s4 是 true。

对于s和s2

String s = new String(“1”);生成了2个对象。常量池中的“1” 和 JAVA Heap 中的字符串对象。
s.intern(); 去常量池中寻找后发现 “1” 已经在常量池里了,不做改变。
String s2 = “1”; 生成一个 s2的引用指向常量池中的“1”对象。
s指向JAVA Heap 中的字符串对象
,不指向常量池,故二者不相等。

代码片段2

public static void main(String[] args) {
    String s = new String("1");
    String s2 = "1";
    s.intern();
    System.out.println(s == s2);

    String s3 = new String("1") + new String("1");
    String s4 = "11";
    s3.intern();
    System.out.println(s3 == s4);
}

jdk6 下false false
jdk7 下false false

对于s3和s4

String s3 = new String(“1”) + new String(“1”);生成了2个最终对象,分别是字符串常量池中的“1” 和 JAVA Heap 中的 s3引用指向的对象
String s4 = “11”;常量池中不存在“11”对象,在字符串常量池创建“11”的字符串。s4指向该处。
s3.intern();,常量池中“11”对象已经存在了,因此不发生变化。s3仍是原heap区的一个对象(不在常量池)。s3与s4不同。

对于s和s2

String s = new String(“1”);的时候已经在常量池区生成“1”对象了。
s.intern();不做变化。
s2声明都是直接从常量池中取地址引用。
s指向heap区(常量池以外)的对象“1”。
s 和 s2 的引用地址不相等。

参考链接1
参考链接2
参开链接3

你可能感兴趣的:(java,算法,jvm)