【JavaSE】浅析String与StringTable

1. 前言

String类是开发中经常使用的一个类。

String稍加理解的话,都会听到这样的一个词——字符串常量池(也叫StringTable)

StringTable是用来存放字符串常量的,当我们使用相同字符串对象的时候,就不需要重新创建字符串对象,而是直接在常量池中获取,这一点和Integer的缓存有点类似。


2. String的两种创建方式

String类用得最多的两种创建方式

  1. 通过new关键字创建一个字符串对象
  2. 采用双引号的方式来创建字符串对象

2.1 通过new关键字创建一个字符串对象

String s = new String("zhangsan")

通过new关键词创建一个字符串对象,表面上是创建了一个对象,但是实际上创建了两个对象!!

创建的过程如下:

  1. Java虚拟机会先在字符串常量池中查找有没有zhangsan这个字符串对象
  2. 如果有,就不会在StringTable中创建zhangsan这个字符串对象,并直接在堆中创建zhangsan这个字符串对象。
  3. 如果没有,就会在StringTable中创建zhangsan这个字符串对象,并直接在堆中创建zhangsan这个字符串对象。

到这里可能会感觉到奇怪,为什么要在StringTable中创建对象,又在堆中创建对象呢?

这是因为字符串的使用频率实在是太高了,所以 Java 虚拟机为了提高性能和减少内存开销,在创建字符串对象的时候进行了一些优化,特意为字符串开辟了一个字符串常量池


2.2 采用双引号的方式来创建字符串对象

String s = "zhangsan";

采用双引号的方式来创建字符串对象,创建过程如下:

  1. 首先,Java虚拟机会先在StringTable中查看是否存在zhangsan这个字符串
  2. 如果有,直接将StringTable中的该字符串的地址返回并赋值
  3. 如果没,则在字符串常量池中创建该字符串对象,然后将地址返回并赋值

2.3 两种方式的区别

  • 对于通过new关键字创建一个字符串对象这种方式来说,不管StringTable中是否存在该字符串对象,都需要在堆中创建字符串对象。
  • 而对于采用双引号的方式来创建字符串对象这种方式来说,如果StringTable中存在该字符串对象,则不需在堆中创建字符串对象

3. StringTable的位置

在Java8之前,StringTable在永生代中

image-20230122114303812

在Java8之后,移除了永生代,StringTable被移动到堆中

image-20230122114348533


4. String的intern()方法

public native String intern();

Stringintern方法是一个native方法,它的作用就是如果常量池中存在当前字符串, 就会直接返回当前字符串. 如果常量池中没有此字符串, 会将此字符串放入常量池中后, 再返回。

intern方法的实现,是JAVA 使用 jni 调用c++实现的StringTableintern方法, StringTableintern方法跟Java中的HashMap的实现是差不多的, 只是不能自动扩容。默认大小是1009。

**在JDK7之前,**调用intern方法,不管对象在堆中是否已经创建,字符串常量池中仍然会创建一个内容完全相同的新对象。

但是,在JDK7之后由于字符串常量池放在了堆中,执行 String.intern() 方法的时候,如果对象在堆中已经创建了,字符串常量池中就不需要再创建新的对象了,而是直接保存堆中对象的引用,也就节省了一部分的内存空间。

String s1 = new String("zhangsan");
String s2 = s1.intern();
System.out.println(s1 == s2);

上面的代码执行流程如下(假设zhangsanStringTable中不存在):

  1. 首先,StringTable中会创建一个zhangsan字符串,然后堆中也会创建一个zhangsan字符串,s1引用是堆中的对象。
  2. 接着,对s1执行intern方法,则会在StringTable中寻找是否存在zhangsan这个字符串对象,此时是存在的,于是s2引用是StringTable中的对象。
  3. 因此s1和s2的引用地址不一样,最后输出的结果为false

image-20230122121818828

String s1 = new String("zhang") + new String("san");
String s2 = s1.intern();
System.out.println(s1 == s2);

上面的代码的执行流程如下(假设zhang和sanStringTable中不存在)

  1. 首先,会在StringTable中创建两个对象zhangsan,堆中也会创建两个对象zhangsan。还有一个zhangsan的对象(为什么会有这个呢?稍后就会知道了),这时候s1引用的是堆中的zhangsan这个字符串对象。
    1. 使用+号的String字符串拼接,底层其实都是先创建一个StringBuilder对象,然后调用append方法把要+的字符串都append进去,最后toString创建一个新的String对象
  2. 接着,执行s1的intern的方法,这时候,会到StringTable中寻找是否存在zhangsan这个字符串对象,此时很明显是不存在的,但是堆中存在,因为我这里用的是JDK8,所以字符串常量池中就不需要再创建新的对象了,而是直接保存堆中对象的引用(有点懵逼的可以看回上面JDK7前后intern的区别),也就是说直接保存堆中zhangsan对象引用。
  3. 因此s1和s2的引用地址一样,最后输出结果为true

image-20230122121908428


5. 判断两个字符串是否相等

这是面试的高频考点,判断两个字符串有两种方法

  1. ==操作符用于比较两个对象的地址是否相等。
  2. .equals() 方法用于比较两个对象的内容是否相等。

因为equals是比较内容,所以比较简单,考得最多还是==


5.1 equals

先看看Stringequals源码

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}
  1. 首先,先判断地址是不是相同
  2. 接着,判断是不是String类,如果不是就可以直接返回false
  3. 如果是,则判断两个字符串长度是否相等,如果不相等也就证明两个字符串不相等了
  4. 如果字符串也相等,那就比较字符串的每个字符是不是相等

5.2 ==

new String("zhangsan") == "zhangsan"

左侧是在堆中的对象,右侧是在StringTable中的对象,而==比较的是地址,所以这个比较的结果是false

new String("zhangsan") == new String("zhangsan")

左右侧均为new出来的对象,也就是说是两个不同对象,不同对象肯定是不同的内存地址,因此结果是false

"zhangsan" == "zhangsan"
  1. 首先左侧的字符串在StringTable中不存在
  2. 需要在StringTable中创建该字符串
  3. 右侧字符串内容和左侧一样,StringTable中存放了左侧的字符串对象,字符串常量池中只会有一个相同内容的对象,因此为true
"zhangsan" == "zhang" + "san"

由于zhangsan都在字符串常量池,所以编译器在遇到+操作符的时候将其自动优化为zhangsan,所以返回 true

new String("zhangsan").intern() == "zhangsan"
  1. new String("zhangsan") 在执行的时候,会先在字符串常量池中创建对象,然后再在堆中创建对象
  2. 执行 intern() 方法的时候发现字符串常量池中已经有了zhangsan这个对象,所以就直接返回字符串常量池中的对象引用了
  3. 那再与字符串常量池中的zhangsan比较,会返回 true 了

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

起名方面没有灵感

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值