java中String的intern、StringBuilder和new String

1.String s1 = “abc”和String s2 = new String(“abc”)的区别

  • 在执行String s1 = “abc”的时候,首先会检查常量池中有没有abc这个字符串,如果有则直接返回该字符串的引用,否则就在常量池中创建一个abc的字符串并且返回该引用。

  • 在执行String s2 = new String("abc")的时候,我们先来看看new String()的源码

String temp = "abc"; // 在常量池中
String str = new String(temp); // 在堆上

这个时候会创建两个对象,一个在常量池中,一个在上。

2.String.intern()

  1. StringTest1
public class StringTest1 {

    public static void main(String[] args) {
        String s1 = "String";
        String s2 = new String("String");
        String s3 = s2.intern();

        System.out.println(s1 == s2);        // false  !1
        System.out.println(s1 == s3);        // true   !2
    }
}

!1: 因为str1指向的是字符串中的常量,str2是在堆中生成的对象,所以str1==str2返回false。

!2: str2调用intern方法,会将str2中值(“string”)复制到常量池中,但是常量池中已经存在该字符串(即str1指向的字符串),
所以直接返回该字符串的引用,因此str1==str2返回true

  1. StringTest2
public class StringTest2 {
   public static void main(String[] args) {
       String baseStr = "baseStr";
       final String finalBaseStr = "baseStr";

       String s1 = "baseStr01";
       String s2 = "baseStr" + "01";
       String s3 = baseStr + "01";
       String s4 = finalBaseStr + "01";
       String s5 = new String("baseStr01").intern();

       System.out.println(s1 == s2);    // true      !1
       System.out.println(s1 == s3);    // false     !2
       System.out.println(s1 == s4);    // true      !3
       System.out.println(s1 == s5);    // true      !4
   }
}

!1 str1==str2 肯定会返回true,因为str1和str2都指向常量池中的同一引用地址。所以其实在JAVA 1.6之后,常量字符串的“+”操作,编译阶段直接会合成为一个字符串。

!2 因为str3实际上是stringBuilder.append()生成的结果,所以与str1不相等,结果返回false。

!3 因为str1和str4指向的都是常量池中的第三项,所以str1==str4返回true。这里我们还能发现一个现象,对于final字段,编译期直接进行了常量替换,而对于非final字段则是在运行期进行赋值处理的

!4 因为str5和str1都指向的都是常量池中的同一个字符串,所以str1==str5返回true。

  1. InternTest
public class InternTest1 {
    public static void main(String[] args) {
      intern1();
      intern2();
    }


    public static void intern1() {
        String s1 = new String("str") + new String("01");
        s1.intern();
        String s2 = "str01";
        System.out.println(s1 == s2);        // !1
    }

    public static void intern2() {
        String s1 = "str01";
        String s2 = new String("str") + new String("01");
        s1.intern();
        System.out.println(s1 == s2);      // !2
    }
}

在 JDK 1.6 和 JDK 1.7中的运行结果不同。我们先看一下运行结果,然后再来解释其原因:
在jdk1.6中false,false
在jdk1.7及以后:true,false

根据对代码段一的分析,应该可以很简单得出 JDK 1.6 的结果,因为 str2 和 str1本来就是指向不同的位置,理应返回false。

比较奇怪的问题在于JDK 1.7后,对于第一种情况返回true,但是调换了一下位置返回的结果就变成了false。这个原因主要是从JDK 1.7后,

HotSpot 将常量池从永久代移到了元空间,正因为如此,JDK 1.7 后的intern方法在实现上发生了比较大的改变,

JDK 1.7后,intern方法还是会先去查询常量池中是否有已经存在,如果存在,则返回常量池中的引用,这一点与之前没有区别,区别在于,

如果在常量池找不到对应的字符串,则不会再将字符串拷贝到常量池,而只是在常量池中生成一个对原字符串的引用。所以:

结果 !1:在第一种情况下,因为常量池中没有“str01”这个字符串,所以会在常量池中生成一个对堆中的“str01”的引用,

而在进行字面量赋值的时候,常量池中已经存在,所以直接返回该引用即可,因此str1和str2都指向堆中的字符串,返回true。

结果 !2:调换位置以后,因为在进行字面量赋值(String str1 = “str01″)的时候,常量池中不存在,所以str1指向的常量池中的位置,

而str2指向的是堆中的对象,再进行intern方法时,对str1和str2已经没有影响了,所以返回false。

3. StringBuilder

public class RuntimeConstantPoolOOM {
    public static void main(String[]args) {
        String str1=new StringBuilder("计算机").append("软件").toString();
        System.out.println(str1.intern()==str1);        // true
        String str2=new StringBuilder("ja").append("va").toString();
        System.out.println(str2.intern()==str2);        // false
    }
 
}

JDK 1.7(以及部分其他虚拟机,例如JRockit)的intern()实现不会再复制实例,只是在常量池中记录首次出现的实例引用,因此intern()返回的引用和由StringBuilder创建的那个字符串实例是同一个。对str2比较返回false是因为“java”这个字符串在执StringBuilder.toString()之前已经出现过,字符串常量池中已经有它的引用了,不符合“首次出现”的原则,而“计算机软件”这个字符串则是首次出现的,因此返回true。

java虚拟机会自动调用System类

/* register the natives via the static initializer.
 *
 * VM will invoke the initializeSystemClass method to complete
 * the initialization for this class separated from clinit.
 * Note that to use properties set by the VM, see the constraints
 * described in the initializeSystemClass method.
 */
在System类中的注释可以知道,调用了initializeSystemClass方法,在此方法中调用了Version对象的init静态方法
sun.misc.Version.init();
因此sun.misc.Version类会在JDK类库的初始化过程中被加载并初始化。
查看Version类定义的私有静态字符串常量如下:
private static final String launcher_name = "java";
private static final String java_version = "1.7.0_51";
private static final String java_runtime_name = "Java(TM) SE Runtime Environment";
private static final String java_runtime_version = "1.7.0_51-b13";
在初始化Version类时,对其静态常量字段根据指定的常量值做默认初始化,所以"java"被加载到了字符串常量池中,修改上面代码使字符串值为上面常量中的任意一个都会返回false。
String str2=new StringBuilder("1.7.0").append("_51").toString();
System.out.println(str2.intern()==str2);

这个问题解决了,然后我又发现了另外一个问题。除了这些在虚拟机加载时就初始化的常量,定义其他的字符串常量,比如“nihao”.

先运行这个代码
String str3 = new StringBuilder("ni").append("hao").toString();
System.out.println(str3==str3.intern());
通过上面的解释,运行结果为true.
在运行这个代码
String str3 = new StringBuilder("nihao").toString();
System.out.println(str3==str3.intern());
其结果是什么?应该还是true吧,毕竟通过上一个运行结果可以知道"nihao"这个字符串常量没有被预先加载到常量池中。
但是运行结果却是false.

分析:

先运行这个代码

String str3 = new StringBuilder("ni").append("hao").toString();
System.out.println(str3==str3.intern());

上面的代码等价于下面的代码

String a = "ni";
String b = "hao";
String str3 = new StringBuilder(a).append(b).toString();
System.out.println(str3==str3.intern());

很容易分析出:
“nihao” 最先创建在堆中 str3.intern()然后缓存在字符串常连池中 运行结果为true.

String str3 = new StringBuilder("nihao").toString();
System.out.println(str3==str3.intern());

可以写成下面的形式

String a = "nihao";
String str3 = new StringBuilder(a).toString();
System.out.println(str3==str3.intern());

很容易分析出:
“nihao” 最先创建在常量池中, 运行结果为false.

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

推荐阅读更多精彩内容

  • 从网上复制的,看别人的比较全面,自己搬过来,方便以后查找。原链接:https://www.cnblogs.com/...
    lxtyp阅读 1,345评论 0 9
  • String 的声明 由 JDK 中关于String的声明可以知道: 不同字符串可能共享同一个底层char数组,例...
    CodeKing2017阅读 1,630评论 1 2
  • 执行顺序:willBeginDraggingDidScrollwillEndDraggingDidEndDragg...
    ClarkWang_001阅读 406评论 0 1
  • 翔子 那年冬天遇见了她 把我的心悄悄融化 大大的眼睛,长长的秀发 我遇见过很多个她 只有她在心底发芽 慢慢...
    人人余羊习习阅读 208评论 0 0
  • 停下来,与自己对话: 这一生中,你最讨厌的人就是你最感谢的人;你最受不了的人就是你成长和学习最大的人。 运动860...
    彩之堂主阅读 187评论 0 0