String字符串常量池

1.String的不可变性

String str = new String("abc");

在内存中开辟了一块空间之后,该空间赋值"abc",该空间中的值即为"abc",无法改变,除非经过GC后,内存重新分配

从源码中分析,String底层是被final修饰的char数组,从jdk9之后,是被final修饰的byte数组,是不可变量

(从char改为byte大概原因是大部分String数据是字母或者拉丁文,只占一个byte,而一个char是两个byte,浪费资源空间而一个汉字占用两个byte,可以通过设置编码UTF8指定)

例1:

String s1 ="abc";

String s2 ="abc";

log.info(s1 == s2);//true

s1 ="def";

log.info(s1 == s2);//false

log.info(s1);//def

log.info(s2);//abc/**

* String s1 = "abc";

* 以字面量的方式赋值,"abc"存储在堆空间中的字符串常量池中, * 而字符串常量池中不允许相同的字符串出现,此"abc"的地址为0x001

* String s2 = "abc";

* 常量池中已经存在"abc",所以将0x001给变量s2,此时s1和s2指向同一个地址

* s1 = "def";

* "abc"并未被修改为"def",而是在字符串常量池中开辟新的空间赋值"def",地址为0x002,

* 然后将此地址给变量s1,此时s1指向的是地址0x002,而s2指向的地址是0x001

*/

例2:

/**

* String str = "aaa";

在内存中开辟空间,赋值"aaa",地址为0x001

public void change(String str) {

str = "bbb";

}

然后调用changge()方法,changge()入栈

str = "bbb";

在空间中开辟新的空间,赋值为"bbb",地址为0x002,将地址给局部变量str,方法出栈

main()方法中的实例变量str指向的是0x001地址,change()方法中的局部变量str指向的是0x002的地址

log.info(demo.str);打印的是实例变量str指向的地址0x001,aaa

*/

2.String的内存分配

jdk6以及以前,String对象存储在永久代中

jdk7以及以后,String对象存储在java堆中

3.字符串的拼接操作

1.常量与常量的拼接结果在常量池,原理是编译器优化

2.常量池中不会存在相同内容的常量

3.只要其中还有一个是变量,结果就在堆中(而不是在常量池中),变量拼接的原理是StringBuilder

4.如果拼接的对象调用intern()方法,则主动将常量池中还没有的字符串对象放入池中,并返回此对象地址

  例1:

        String s1 = "a"+"b"+"c"; //编译器优化后等同于abc,地址为0x001

        String s2 = "abc"; //字面量赋值,abc是在常量池中,将此地址0x001给了s2

        log.info("{}",s1==s2); //true

        log.info("{}",s1.equals(s2)); //true

  查看字节码文件

例2:

String s1 = "javaEE";

        String s2 = "hadoop";

        String s3 = "javaEEhadoop";

        String s4 = "javaEE" + "hadoop"; //编译器优化

        //如果拼接符号前后有了变量,则相当于在堆中new String(),具体的内容为拼接的结果:javaEEhadoop

        //所以,s5,s6,s7是在堆空间中是三个不同的对象

        String s5 = s1 + "hadoop";

        String s6 = "javaEE" + s2;

        String s7 = s1 + s2;

        log.info(s3 == s4); //true

        log.info( s3 == s5); //false

        log.info( s3 == s6); //false

        log.info(s3 == s7); //false

        log.info( s5 == s6); //false

        log.info(s5 == s7); //false

        log.info(s6 == s7); //false

        //intern():判断字符串常量池中是否存在javaEEhadoop,如果存在,则返回常量池中javaEEhadoop的地址;

        //如果不存在,就在字符串常量池中加载一份javaEEhadoop,并返回这个对象的地址

        String s8 = s6.intern();

        log.info("{}", s4 == s8); //true

例3:

        String s1 = "a";

        String s2 = "b";

        String s3 = "ab";

        String s4 = s1 + s2;

        log.info("{}",s3 == s4); //false

        对该代码的字节码文件进行反编译后

        ldc #16 <a> //这个a就是字符串a

        astore_1 //将这个字符串a存储在局部变量表的1位置

        ldc #17 <b> //这个b就是字符串b

        astore_2 //将这个字符串b存储在局部变量表的2位置

        ldc #18 <ab> //这个ab就是字符串ab

        astore_3 //将这个字符串ab存储在局部变量表的3位置

        new #11 <java/lang/StringBuilder>

        dup

        invokespecial #12 <java/lang/StringBuilder.<init>>

        aload_1

        invokevirtual #13 <java/lang/StringBuilder.append>

        aload_2

        invokevirtual #13 <java/lang/StringBuilder.append>

        invokevirtual #14 <java/lang/StringBuilder.toString>

        astore 4

        //在执行s1 + s2的时候,是在堆空间中new了一个StringBuilder,然后从局部变量表的1位置获取字符串a,

        执行append()方法将字符串a追加到StringBuilder中,然后再从局部变量表的2位置获取字符串b,调用append()方法

        将字符串b追加到StringBuilder中,最后调用toString()方法(约等于new String()),所以s1+s2的值是在堆内存中

        重新开辟了一块空间的值,而s3是在堆中的字符串常量池中,并不是同一个对象

例4:

        final String s1 = "a";

        final String s2 = "b";

        String s3 = "ab";

        String s4 = s1 + s2;

        log.info("{}",s3 == s4); //true

        对该代码的字节码进行反编译后

ldc #16 <a>

astore_1

ldc #17 <b>

astore_2

ldc #18 <ab>

astore_3

ldc #18 <ab>

astore 4

//可以看到被final修饰后,s1,s2其实就是常量的引用,而不是变量,s4 = s1+s2可以认为是s4 = "a"+"b"

结论:字符串拼接操作不一定使用的是StringBuilder

如果拼接符号左右两边都是字符串常量或者常量引用的话,则仍然使用编译器优化,即非StringBuilder的方式

针对final修饰类,方法,基本数据类型,引用数据类型的变量时,能用final修饰的尽量用final修饰

4. String str = new String("abc")创建了几个对象?

两个,看字节码

1.new关键字在堆中分配空间

2.ldc指令从字符串常量池中加载"abc",如果没有就在常量池中创建一个

String str = new String("a")+new String("b")

然后查看StringBuilder.class的字节码文件,找到toString()方法

toString方法中,第一行在堆中new了一块空间,并没有在字符串常量池中创建"ab"对象

所以,上面的操作一共创建了六个对象

1.StringBuilder对象

2.String对象,值时"a"

3.字符串常量池中的"a"

4.String对象,值时"b"

5.字符串常量池中的"b"

6.toString方法中new对象,存放"ab"字符串

5.intern()的理解

总结String的intern()的使用

    jdk1.6中,将这个字符串对象尝试放入字符串常量池中

    如果池中有,则不会放入,返回已有的池的对象地址

    如果池中没有,会把此对象赋值一份,放入池中,并返回池中的对象地址

    jdk1.7中,将这个字符串尝试放入字符串常量池中

    如果池中有,则不会放入,返回已有的池的对象地址

    如果池中没有,会把对象的引用地址赋值一份,放入池中,并返回池中的引用地址

例1:

        String s = new String("1");

        s.intern();

        String s2 = "1";

        log.info("{}",s == s2); //false

        /**

        *  new String("1");在堆中new一个对象存放字符串1,在字符串常量池中开辟一块空间存放字符串1,此时s指向的是堆空间中的字符串1

        *  s.intern();执行intern()方法,但是此时字符串常量池中已经存在字符串1,所以s.intern()方法没有任何作用,s指向的还是堆空间中的字符串1

        *  String s2 = "1"; 通过字面量赋值,s2指向的是字符串常量池中的1

        *  所以s1,s2是指向不同的地址,结果为false

        *

        */

例2:

String s = new String("1") + new String("1");

        s.intern();

        String s2 = "11";

        log.info("{}",s==s2); //jdk6 false;jdk7/8 true

        /**

        * new String("1")  在堆中new一个对象存放字符串1,在字符串常量池中开辟一块空间存放字符串1

        * new String("1")  在堆中new一个对象存放字符串1,由于字符串常量池中已经存在字符串1,所以不再创建字符串1

        * s.intern() jdk6:此时s的值是指向堆空间中的字符串11,去永久代的字符串常量池中查询,没有字符串11,在字符串常量池中开辟空间存入字符串11

        *  jdk7:此时s的值是指向堆空间中的字符串11,去堆中的字符串常量池中查询,没有字符串11,但是因为字符串常量池也在堆空间中,为了节省内存

        * 字符串常量池中开辟的新空间的地址就是堆空间中的11的地址

        * String s2 = "11" 字面量赋值会直接在字符串常量池中寻找,有,就将地址给s2,没有,就在字符串常量池中创建,

        * 而此时,显然上一步已经在字符串常量池中有了字符串11

        */

例3:

String s = new String("1") + new String("1");

        String s2 = "11";

        s.intern();

        log.info("{}", s == s2); //false

        s = s.intern();

        log.info("{}", s == s2); //true

        /**

        * new String("1")  在堆中new一个对象存放字符串1,在字符串常量池中开辟一块空间存放字符串1

        * new String("1")  在堆中new一个对象存放字符串1,由于字符串常量池中已经存在字符串1,所以不再创建字符串1

        * String s2 = "11" 这个操作会在字符串常量池中开辟一个空间,存放字符串11

        * s.intern() 此时在字符串常量池中已经存在了字符串11,所以这行代码没有什么效果

            s指向的是堆中的对象,s2指向的是栈中的对象

s = s.intern(); 字符串常量池中已经存在"11",将常量池中的引用给变量s,s2指向的也是常量池中的"11"

        */

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

推荐阅读更多精彩内容