String、String()、StringBuffer()、StringBuffer().append()、intern()

最近看到网上一道题(也算不上一道题,是网友遇到的一个现象),觉得挺有意思的,刚开始我也不是很懂,后来看人家解释,以及翻阅一些资料,简单记录下。
题目如下:
public static void main(String[] args) {
       
        String a = "aaa";
        System.out.println(a.intern() == a);
       
        String b = new String("aaa");
        System.out.println(b.intern() == b);
       
        String c = new StringBuffer().append("aaa").toString();
        System.out.println(c.intern() == c);
       
        String d = new StringBuffer("计算机").toString();
        System.out.println(d.intern() == d);
       
        String e = new StringBuffer("计算机").append("aaa").toString();
        System.out.println(e.intern() == e);
       
    }

他的运行结果是什么呢?

运行之后发现运行结果是:
true
false
false
false
true

那么又是为什么呢?

那么这里就要涉及到JAVA 的 内存结构了。

Java内存模型:

Java 虚拟机具有一个堆,堆是运行时数据区域,所有类实例和数组的内存均从此处分配。

JVM主要管理两种类型内存:堆和非堆,堆内存(Heap Memory)是在 Java 虚拟机启动时创建,非堆内存(Non-heap Memory)是在JVM堆之外的内存。

简单来说:

非堆包含方法区、JVM内部处理或优化所需的内存(如 JITCompiler,Just-in-time Compiler,即时编译后的代码缓存)、每个类结构(如运行时常数池、字段和方法数据)以及方法和构造方法的代码。


Java的堆是一个运行时数据区,类的(对象从中分配空间。这些对象通过new、newarray、 anewarray和multianewarray等指令建立,它们不需要程序代码来显式的释放。


虚拟机必须为每个被装载的类型维护一个常量池。常量池就是该类型所用到常量的一个有序集合,包括直接常量(string,integer和 floating point常量)和对其他类型,字段和方法的符号引用。

那么intern()呢?

String对象的实例调用intern方法后,可以让JVM检查常量池,如果没有实例的value属性对应的字符串序列比如"aaa"(注意是检查字符串序列而不是检查实例本身),就将本实例放入常量池,如果有当前实例的value属性对应的字符串序列"aaa"在常量池中存在,则返回常量池中"aaa"对应的实例的引用(即常量池中第一次出现aaa的实例的引用)而不是当前实例的引用,即使当前实例的value也是"aaa"。


存在于.class文件中的常量池,在运行期被JVM装载,并且可以扩充。String的 intern()方法就是扩充常量池的 一个方法;当一个String实例str调用intern()方法时,Java 查找常量池中 是否有相同Unicode的字符串常量,如果有,则返回其的引用,如果没有,则在常 量池中增加一个Unicode等于str的字符串并返回它的引用;

看了上面的话,应该对上面那段代码有了一定的理解了。

案例解析:

场景一:
        String a = "aaa";
        System.out.println(a.intern() == a);

这句话运行返回的是true,是因为声明字符串aaa时,JVM会先到常量池里查找,如果有的话返回常量池里的这个实例的引用,否则的话创建一个新实例并置入常量池里。
而a.intern(),当string调用intern()时,JVM会去常量池中查找,如果没有实例的value属性对象的字符串序列是aaa,就会将本实例存入常量池中,否则会返回aaa对应的实例的引用(不是本实例的引用)。

那么显而易见,在常量池中aaa已经存在了,所以a.intern()返回的也是a实例的引用,两者相等,固然返回的是true。

场景二:

        String b = new String("aaa");
        System.out.println(b.intern() == b);

在执行String b = new String("aaa")的时候,首先走常量池的路线取到一个实例的引用,然后在堆上创建一个新的String实例,然后把实例引用赋值给b.

这里的new String("aaa");相当于创建了两个对象,一个是常量池中的实例b,一个是堆中的String实例。且b返回的是堆中的实例地址。

这里的b.intern()返回的则是常量池中的aaa实例引用。所以b.intern() == b返回的是false。


场景三:

        String c = new StringBuffer().append("aaa").toString();
        System.out.println(c.intern() == c);

 此处创建了两个对象,aaa 以及 StringBuffer()的 toString()得到的新对象,因之前aaa已存在于常量池中,所以常量池不会产生新的字符串,不过c是通过StringBuffer()的 toString()得到的新对象。

c.intern()返回的是之前创建aaa这个常量的引用,所以与c是两个不同的对象,即c.intern() == c为false。

所以c.intern() == c为false

场景四:

        String d = new StringBuffer("计算机").toString();
        System.out.println(d.intern() == d);
该场景与场景三相似。

场景五:

        String e = new StringBuffer("计算机").append("aaa").toString();
        System.out.println(e.intern() == e);
此处创建了三个对象,计算机、aaa、计算机aaa,因计算机、aaa在常量池中已存在,所以不会产生新的字符串,但是计算机aaaa在常量池中没有存在,所以计算机aaaa会被放入常量池中,e对象是StringBuffer()的toString()得到的字符串 计算机aaa。
e.intern()是去常量池检查字符串计算机aaa是否存在,并返回字符串计算机aaa第一次出现的引用,即对象e。
所以e.intern() == e为true。


欢迎大家一起讨论,如有不对,请指证!

参考博客1:http://ask.csdn.net/questions/648152

参考博客2:http://ask.csdn.net/questions/648163

参考博客3:http://www.cnblogs.com/ITtangtang/p/3976820.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值