总结了一下StringTable/StringPool以及intern方法一些相关的问题,通俗易懂

是什么?

StringTable / String pool也称为字符串常量池 ,用来专门放字符串常量的。

大家都知道,在Java中我们经常会去创建字符串对象,而每创建一个字符串对象都需要消耗一定的内存空间,这个时候就出现了字符串常量池,当我们池中存在这个字符串时,就直接可以使用池中的这个,不需要再去创建一个。

String s1 = "a";
String s2 = "a";
// s1 == s2 ---> true

在JDK1.6之前,StringTable被存放在老年代中,而在JDK1.7之后,StringTable被存放在堆中。

String常量是什么时候被放入StringTable中的?

看下面这段代码:

String s1 = "a";
String s2 = "b";
String s3 = "ab";

其实在该类被初始化时,这些字符串常量并没有立刻被方法StringTable中,它只是作为一个符号被放到常量池中了,注意是符号!这个时候还没有创建对象。比如上述代码,在类被加载的时候,将 a b ab 这三个符号存储到了常量池中,如下所示:
在这里插入图片描述

通过javap -v [].class来反编译字节码文件。

这是因为java字符串采用了延迟加载,也就是懒加载的模式,只有在执行到对应的代码,比如在执行String s1 = "a"这行代码的时候,才会先去串池中查找a这个字符串存不存在,如果存在直接使用,如果不存在,那就创建"a"字符串对象到池中。

new String()做了啥?

比如,new String("a"),这行代码首先在堆中肯定是存放了一份String对象,并且将"a"字符串对象也放入了串池中,也就是创建了两个对象分别在堆和非堆当中。

不一样的是,String s = "a",是直接将"a"存入串池中,前提是串池中没有"a",并不会创建在堆中,只是在串池放入"a"字符串对象。

String# intern方法

为了引出我所遇到的StringTable中的坑,直接来说一下这个方法。

首页介绍一下它的作用:
该方法用来尝试将一个字符串对象放入串池中。 如果串池中存在这个字符串对象(它是用当前调用方法的字符串对象.equals(池中字符串对象) == true来判断的),那就返回池中字符串对象的引用。

注意,在JDK1.7之后,如果池中不存在,不会立即创建,而是先去堆中查找该字符串对象存不存在,如果存在则将堆中字符串的引用存到池中,注意这里是引用!

StringTable串池中到底存的是引用还是对象?

读到这里,想必你也会有这个疑问,StringTable串池中到底存的是引用还是对象?

首页,这一点可以明确的告诉你,**runtime constant pool(运行常量池)**中存放的是引用!!但是StringTable/StringPool可不好说…
某大神R的回答:
在这里插入图片描述

原文链接:https://www.zhihu.com/question/57109429

网络上关于这个问题的解释,我几乎翻了个遍,回答不一,有说存对象,有说存引用,也就说即存放对象又存放引用…在踩过一些坑之后,个人总结:即存放对象,又存放引用。

intern()的源码就说了
在这里插入图片描述
它说的很清楚,将StringObject放入串池中。

而上面又说过:
在这里插入图片描述
这两点是我总结的依据之一。

我想在这里就告诉你这一观点,否则下面的案例代码,你可能会很懵。

先从简单开始

以下代码基于JDK1.8。

String s = new String("1");
String s2 = "1";
String intern = s.intern();
System.out.println(s == intern);	// false
System.out.println(s == s2);	// false
System.out.println(s2 == intern);	// true

分析一下:

  • String s = new String("1"),创建String字符串对象实例到堆中,将"1"创建到串池中,s指向堆中的字符串对象;
  • String s2 = "1",先去串池中找是否存在字符串"1",发现有,则直接使用串池中的这个;
  • String intern = s.intern(),将s尝试放入串池中,实际上串池有有这个"1",所以这边返回"1"这个字符串对象在串池中的引用,也就是intern指向了串池中的"1"。

在这里插入图片描述

其实这段代码没什么好分析的,看图就一目了然了,确实很简单,但是先别着急离开。

下一个例子:

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

分析:

  • String s3 = new String("1") + new String("1"),其实这段代码分别执行了两次 new String(“1”),在串池中创建了"1"这个字符串对象,最后调用了new String(“11”, 0, count),也就是在堆中创建了一个"11"字符串对象实例,让s3引用指向它,

    如果你有疑惑,为什么new String(“11”, 0, count)只在堆中创建了实例,串池中没有创建"11"字符串对象呢?请看下面StringBuidler中toString方法的介绍,相信你会明白我的解释。

  • s3.intern(),将s3存入串池中,这个时候串池中只有"1",并没有匹配的"11"在串池中,所以它会先去堆中找"11",发现,找到了!所以,将堆中"11"字符串对象的引用存到了串池中;
  • String s4 = "11",直接去串池中寻找有没有"11",发现有,就用它!于是s4引用指向了串池中的保存的引用,如图。

在这里插入图片描述
s3.intern();和下一行换一下顺序?

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

很好理解,String s4 = "11";在串池中找不到"11"这个字符串对象了。

StringBuilder踩过的坑

你需要先了解

在读这一段时,你需要了解两个东西,也是我踩过的大坑!

StringBuilder的toString方法

在这里插入图片描述
能看到,StringBuilder的toString方法其实就是通过new String()去创建一个字符串对象,大家都知道,new String()这个方法会创建两个对象,一个在堆中,一个在非堆中。(上面也介绍了)

如果new String()方法是它们,那么上述说法完全没错
在这里插入图片描述
关键是,它调的new String(x,x,x)有三个参数…
在这里插入图片描述
该方法只将字符串对象创建到堆中,串池中不会创建!!也就是调用new String(x,x,x)只在堆中创建一个对象。

如何证明?那就调用它看看
在这里插入图片描述
如果将value存储串池,肯定会看到一个 idc #指令。

StringBulider append方法

我要说的就是,new StringBuilder()和append()方法都会将字符串存入到常量池中。
在这里插入图片描述
如果不会是不可能出现idc的。

知道上面两点,就可以看下面的例子了。

例1

String a = new StringBuilder("北京").toString();
String internA = a.intern();
System.out.println(a  ==  internA);  //false

在这里插入图片描述
这段代码很简单,看到这里你应该能明白,就不分析了,主要是快12点了,我要睡觉了。

例2

String b = new StringBuilder("计算机").append("软件").toString();
String internB = b.intern();
System.out.println(b ==  internB);    //true

分析:

  • new StringBuilder("计算机").append("软件").toString(),先new StringBuilder(“计算机”),此时创建两个对象分别在堆和串池中,.append(“软件”)拼接堆中字符串,现在堆中也就如下图所示,然后将"软件"也放到串池中,注意,再说一遍.toString()不往串池中放东西!此时b引用指向堆中对象。
  • String internB = b.intern(),将b存入串池中,首页先判断串池是否有"计算机软件"字符串对象,发现没有,这时去堆中看看,发现堆中存在该字符串对象,那就将堆中的这个对象的引用存到串池中,此时innernB指向如图所示。
    在这里插入图片描述

再看一个特例

String b = new StringBuilder("ja").append("va").toString();
String internB = b.intern();
System.out.println(b ==  internB);    //false

原因就是,java是jvm中的关键字,它本来就在串池中存在。

最后在补充一个知识点 String s3 = s1 + s2底层执行了什么?

看下面一段代码:

public class StringTableTest {
    public static void main(String[] args) {
        String s1 = "a";
        String s2 = "b";
        String s3 = s1 + s2;
    }
}

我们使用javap -v xx.class,将这段代码对应的字节码文件反编译,看看String s3 = s1 + s2;这行代码是如何执行的,主要看下面框出来的:
在这里插入图片描述
分析:

  • 6:new StringBuilder();

  • 10:调用StringBuilder的构造方法初始化;

  • 14:调用StringBuilder对象的append方法,这里调用了append(s1);

  • 18:又调用StringBuilder对象的append方法,也就是append(s2);

  • 最后调用了toString()方法返回一个String类型的对象;

所以:

String s1 = "a";
String s2 = "b";
String s0 = "ab";
String s3 = s1 + s2;
System.out.println(s0 == s3);	// false
  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值