java基础:String — 字符串常量池与intern(二)

其他更多java基础文章:
java基础学习(目录)


学习资料:
String类API中文
深入解析String#intern
Java 中new String("字面量") 中 "字面量" 是何时进入字符串常量池的?
new一个String对象的时候,如果常量池没有相应的字面量真的会去它那里创建一个吗?我表示怀疑。

通过上一篇的学习,我们已经了解了String源码的方法,这一章,我们就通过Stirng.intern()方法来延伸,讲一下String的其他方面。

字符串字面量

字符串字面量是在 Java™语言规范的3.10.5. String 字面量中定义的
关于字面量通俗点解释就是,使用双引号""创建的字符串,在堆中创建了对象后其引用插入到字符串常量池中(jdk1.7后),可以全局使用,遇到相同内容的字面量,就不需要再次创建。举个例子:

//这就是创建了一个aaa字符串字面量
String a = "aaa";
//简单来说,这就是创建了一个Stirng对象和一个aaa字符串字面量,后面会详细讨论
String a = new String("aaa")

字符串常量池

java中常量池的概念主要有三个:全局字符串常量池class文件常量池运行时常量池。我们现在所说的就是全局字符串常量池,在下文中可能会简称常量池。对这个想弄明白的同学可以看这篇Java中几种常量池的区分

字符串常量池里面存的到底是对象,还是引用呢?我查了很多资料,最后根据自己的测试和查到的各种说法,认为在jdk1.7后字符串常量池中存的是引用。在new一个String对象的时候,如果常量池没有相应的字面量真的会去它那里创建一个吗?我表示怀疑。问题中,R大的回答解答了我:

至于说:
之前一直有个结论就是:当创建一个string对象的时候,去字符串常量池看是否有相应的字面量,如果没有就创建一个。
这个说法从来都不正确。
对象在堆里。常量池存引用。

这个字符串常量池的位置也是随着jdk版本的不同而位置不同。在jdk6中,常量池的位置在永久代(方法区)中,此时常量池中存储的是对象。在jdk7中,常量池的位置在堆中,此时,常量池存储的就是引用了。在jdk8中,永久代(方法区)被元空间取代了。这里就引出了一个很常见很经典的问题,看下面这段代码。

    @Test
    public void test(){
        String s = new String("2");
        s.intern();
        String s2 = "2";
        System.out.println(s == s2);


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

jdk6
false
false

jdk7
false
true

这段代码在jdk6中输出是false false,但是在jdk7中输出的是false true。我们通过图来一行行解释。

JDK1.6代码图

JDK1.6
String s = new String("2");创建了两个对象,一个在堆中的StringObject对象,一个是在常量池中的“2”对象。
s.intern();在常量池中寻找与s变量内容相同的对象,发现已经存在内容相同对象“2”,返回对象2的地址。
String s2 = "2";使用字面量创建,在常量池寻找是否有相同内容的对象,发现有,返回对象"2"的地址。
System.out.println(s == s2);从上面可以分析出,s变量和s2变量地址指向的是不同的对象,所以返回false

String s3 = new String("3") + new String("3");创建了两个对象,一个在堆中的StringObject对象,一个是在常量池中的“3”对象。中间还有2个匿名的new String("3")我们不去讨论它们。
s3.intern();在常量池中寻找与s3变量内容相同的对象,没有发现“33”对象,在常量池中创建“33”对象,返回“33”对象的地址。
String s4 = "33";使用字面量创建,在常量池寻找是否有相同内容的对象,发现有,返回对象"33"的地址。
System.out.println(s3 == s4);从上面可以分析出,s3变量和s4变量地址指向的是不同的对象,所以返回false

JDK1.7代码图

JDK1.7
String s = new String("2");创建了两个对象,一个在堆中的StringObject对象,一个是在堆中的“2”对象,并在常量池中保存“2”对象的引用地址。
s.intern();在常量池中寻找与s变量内容相同的对象,发现已经存在内容相同对象“2”,返回对象“2”的引用地址。
String s2 = "2";使用字面量创建,在常量池寻找是否有相同内容的对象,发现有,返回对象“2”的引用地址。
System.out.println(s == s2);从上面可以分析出,s变量和s2变量地址指向的是不同的对象,所以返回false

String s3 = new String("3") + new String("3");创建了两个对象,一个在堆中的StringObject对象,一个是在堆中的“3”对象,并在常量池中保存“3”对象的引用地址。中间还有2个匿名的new String("3")我们不去讨论它们。
s3.intern();在常量池中寻找与s3变量内容相同的对象,没有发现“33”对象,将s3对应的StringObject对象的地址保存到常量池中,返回StringObject对象的地址。
String s4 = "33";使用字面量创建,在常量池寻找是否有相同内容的对象,发现有,返回其地址,也就是StringObject对象的引用地址。
System.out.println(s3 == s4);从上面可以分析出,s3变量和s4变量地址指向的是相同的对象,所以返回true。

再来一段变种代码
通过上面的逐句分析,应该都了解了为什么两个版本的jdk返回值会不一样了。那我们稍稍改变一下上面代码中的语句顺序,将intern方法与字面量赋值语句调换顺序:

        String s = new String("2");
        String s2 = "2";
        s.intern();
        System.out.println(s == s2);

        String s3 = new String("3") + new String("3");
        String s4 = "33";
        s3.intern();
        System.out.println(s3 == s4);

答案是多少呢,大家可以稍微思考一下再往下看:

jdk6
false
false

jdk7
false
false

原理很简单,因为在调用intern方法前,先使用了字面量赋值语句,所以在常量池中都存在了与变量相同内容的对象(jdk1.6)或对象的引用(jdk1.7+),此时再调用intern方法,就会发现常量池里的对象地址和变量的地址不是指向同一个对象,自然就false了。对于这段不懂的同学可以评论,我看需不需要再画一次结构图和逐句解释。

字面量是何时进入常量池

通过上面两段代码,我们发现调用intern方法和字面量赋值的顺序是很重要的。我们将上面两段代码都通过javap命令查看其字节码,发现在class类常量池中都有“33”。这说明在运行时,class常量池里的常量并不会直接全部加入到全局常量池中,那这是在什么时候加入的呢?我搜到了下面大神的回答
new String(“字面量”) 中 “字面量” 是何时进入字符串常量池的?

简单来说:

  • HotSpot VM的实现来说,加载类的时候,那些字符串字面量会进入到当前类的运行时常量池,不会进入全局的字符串常量池 ;

  • 在字面量赋值的时候,会翻译成字节码ldc指令,ldc指令触发lazy resolution动作

    • 到当前类的运行时常量池(runtime constant pool,HotSpot VM里是ConstantPool + ConstantPoolCache)去查找该index对应的项
    • 如果该项尚未resolve则resolve之,并返回resolve后的内容。
    • 在遇到String类型常量时,resolve的过程如果发现StringTable已经有了内容匹配的java.lang.String的引用,则直接返回这个引用;
    • 如果StringTable里尚未有内容匹配的String实例的引用,则会在Java堆里创建一个对应内容的String对象,然后在StringTable记录下这个引用,并返回这个引用出去。

String“+”符号的实现

在我们使用中经常会用到+符号来拼接字符串,但是这个+符号在String中的实现还是有讲究的。如果是相加含有String对象,则底部是使用StringBuilder实现的拼接的

String str1 ="str1";
String str2 ="str2";
String str3 = str1 + str2;

如果相加的参数只有字面量或者常量或基础类型变量,则会直接编译为拼接后的字符串。

String str1 =1+"str2"+"str3";

这里有个小细节
如果使用字面量拼接的话,java常量池里是不会保存拼接的参数的,而是直接编译成拼接后的字符串保存,我们看看这段代码:

        String str1 = new String("aa"+"bb");
        //String str3 = "aa";
        String str2 = new StringBuilder("a").append("a").toString();
        System.out.println(str2==str2.intern());

这段代码的输出是true。可以得知,在str1变量的创建中,虽然我们用了字面量“aa”,但是我们常量池里并没有aa,所以str2==str.intern()才会返回true。如果我们去掉str3的注释,重新运行,就会输出false

个人疑问

我在学习的过程中,遇到了一个疑问,怎么都查不到是为什么,大家如果看到这里,可以顺手写一下这段代码,看是不是也会遇到这样的问题。

public static void main(String[] args){
        String s3 = new String("1") + new String("1");
        s3.intern();
        String s4 = "11";
        System.out.println(s3 == s4);
    }
   @Test
    public void test7(){
        String s3 = new String("1") + new String("1");
        s3.intern();
        String s4 = "11";
        System.out.println(s3 == s4);
    }

如上所示,分别在test环境和main方法里运行相同代码,此时main函数里返回true,test环境下却是返回false。按逻辑这里应该是返回true才对。但是我测试了将参数“1”改为“2“”或者“3”,两者返回的都是true。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,123评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,031评论 2 384
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,723评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,357评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,412评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,760评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,904评论 3 405
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,672评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,118评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,456评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,599评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,264评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,857评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,731评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,956评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,286评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,465评论 2 348

推荐阅读更多精彩内容