【Java杂谈】String字符串常量池

String杂谈

String s = new String(“abc”)产生几个对象?

首先会检查常量池中是否有“abc”这个对象

  • 如果没有,则先在常量池中创建“abc”对象,然后在堆中创建String对象,其中的value指向常量池中的“abc”
  • 如果有,则只在堆中创建String对象

总结:常量池中没有“abc”对象则会创建2个对象,常量池中有“abc”对象则会创建1个对象。

关于String.inner()方法

先说结论:

jdk1.7之前,intern()方法会把首次遇到的字符串实例复制到方法区的常量池中,然后返回方法区中这个字符串实例的引用。

jdk1.7之后,intern()方法不再采取复制的方式,而是在常量池中记录首次出现的实例的引用。

String类的intern()方法描述:

String类维护着一个初始化为空的字符串池,当某String实例调用intern()方法的时候,如果字符串池中已包含与此字符串相同的字符串(用equal(obj)方法判断相等),则返回池中的字符串对象。否则,将此字符串对象加入到常量池中,并返回此字符串对象的引用。对于任意两个字符串s和t,当切仅当s.equals(t)为true,s.intern() ==t.intern()才为true。所有字面值字符串和字符串赋值常量表达式都使用intern方法进行处理。

例如字符串为”abc“,则只要程序中使用到”abc“这个值时都会使用intern()方法处理,其目的还是为了重用常量值。

运行环境:java version “11.0.4” 2019-07-16 LTS

String s = new String("ab") + new String("c");
System.out.println(s.intern() == s);
System.out.println(s.intern() == "abc");
System.out.println(s == "abc");
true
true
true

首先执行new String("ab")时,会先在常量池中检查是否有“ab”对象,如果没有则会在常量池中创建“ab”对象,然后在堆中创建String对象。new String("c")同理。

在Java中对于String的‘+’操作是由StringBuilder调用append()操作完成的,StringBuilder继承自AbstractStringBuilder,对于StringBuilder的操作大部分是调用父类方法来完成的。在AbstractStringBuilder中,有一个名为value的byte数组,用来保存字符串ASCII值(String类内部也有value成员变量起着同样的作用)。

调用append()方法时,先得到要添加的字符串的byte数组,将其添加到StringBuilder的byte数组中(其实是AbstractStringBuilder中的成员变量),最后调用StringBuiler.toString()方法时,会用byte数组作为参数来new一个String对象。需要注意的是用byte数组来创建String对象不会去常量池中做检查,也不会在常量池中创建对象,这种方式只是单纯的在堆上new一个String对象。

StringBuilder的toString()方法

	@Override
    @HotSpotIntrinsicCandidate
    public String toString() {
        // Create a copy, don't share the array
        return isLatin1() ? StringLatin1.newString(value, 0, count)
                          : StringUTF16.newString(value, 0, count);
    }
public static String newString(byte[] val, int index, int len) {
        return new String(Arrays.copyOfRange(val, index, index + len),
                          LATIN1);
    }

按上述流程执行完第一条语句后,变量的分布如下图:

在这里插入图片描述

接着来看刚开始那段程序

String s = new String("ab") + new String("c");
System.out.println(s.intern() == s);
System.out.println(s.intern() == "abc");
System.out.println(s == "abc");

当执行s.intern()时,发现常量池中没有“abc”对象,则会在常量池中保存对堆中“abc”对象的引用。

在这里插入图片描述

对于第一条输出语句,s.intern()返回的是堆中“abc”对象的地址,因此s.intern() == s成立。

对于第二条输出语句,s.intern()返回值不变,当再次引用“abc”常量时,会去常量池中查找是否存在“abc”对象,然后返回堆中“abc”对象的实际地址。第三条输出语句原理相同。


再看另一个程序

String s = new String("abc");
System.out.println(s.intern() == s);
System.out.println(s.intern() == "abc");
System.out.println(s == "abc");
false
true
false

String s = new String(“abc”);语句执行后的变量分布:

在这里插入图片描述

​ 与上个程序不同的是在执行输出语句之前先将“abc”保存到了常量池中,然后才在堆中创建String对象。此时调用s.intern()返回的是常量池中“abc”的地址而不是堆中“abc”的地址,因此第一条输出为false。
​ 当再次引用常量值“abc”时,发现常量池中已经有“abc”对象,所以返回的是常量池中的“abc”对象,与s.intern()返回值相同,因此第二条输出为true。
​ 第三条比较的是堆中的“abc”地址与常量池中的“abc”地址,因此返回false。

详细参考:

java8中字符串常量以及GC相应处理机制

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值