vbs未结束的字符串常量怎么解决_一文弄懂String常量池,String常见面试题,以及intern()方法...

String做为Java开发中常用的类,弄懂它是非常有必要的,但是往往很多工作了几年从业人员,也并没有特别熟悉过,所以楼主总结一下String的常量池,以及intern()方法等。技术无止境,当然本文也有不足之处,欢迎大家在评论区指正。

前言

本次代码使用 jdk 1.8版本,并且以下代码示例除了第一个写了main()方法,并且所有的示例分别独立运行 ,其余为了简洁做了缺省main()。在创建字符串分析的同时,都默认省略了栈中的句柄指向分析。进入正题之时,先科普几个知识点

  • String源码里面标注为final修饰的类,是一个不可改变的对象,那平时用到字符串A+字符串B怎么改变了呢,其实这里有涉及到String的常量池,首先常量池存放在方法区。
  • 在jdk1.6时,方法区是存放在永久代(java堆的一部分,例如新生代,老年代)而在jdk1.7以后将字符串常量池移动到了的堆内存中
  • 在jdk1.8时,HotspotVM正式宣告了移除永久代,取而代之的是元数据区,元数据区存放在内存里面(存放一些加载class的信息),但是常量池还是和jdk1.7存放位置一样还是存放在堆中。

先看一波常见面试题:

首先看一道常见的面试题,问输出的是什么?

public static void main(String[] args){

String s1 = new String("123");

String s2 = "123";

System.out.println(s1 == s2);

}

基本上大家都能知道是false,但是再这么深究一次,问 String s1 = new String("123") 创建了几个对象,String s2 = "123" 创建 了几个对象,那如果题目稍微改变一下成下面这样,那输出的又是什么?

String s1 = new String("123").intern();

String s2 = "123";

System.out.println(s1 == s2); // true

// 如果这样再改一下

String s1 = new String("123");

s1.intern();

String s2 = "123";

System.out.println(s1 == s2); // false

如果对输出结果不是很明白的,本文都会一一解答并且进行拓展。

0b356fe0eb6bc4e1171a087ac4986d31.png

创建字符串分析:

首先要分析String,一定要知道String几种常见的创建字符串的方式,以及每一种不同的方式常量池和堆分别是什么储存情况。

1.直接写双引号常量来创建

判断这个常量是否存在于常量池,

  如果存在,则直接返回地址值(只不过地址值分为两种情况,1是堆中的引用,2是本身常量池的地址)

    如果是引用,返回引用地址指向的堆空间对象地址值

    如果是常量,则直接返回常量池常量的地址值,

  如果不存在,

    在常量池中创建该常量,并返回此常量的地址值

String s = "123";

//true,因为s已经在常量池里面了,s.intern()返回的也是常量池的地址,两者地址一样为true

System.out.println(s == s.intern());

2. new String创建字符串

与上面第一种方式相比,第一种方式效率高,下图解决了本文中的最开始出的部分面试题。

首先在堆上创建对象(无论堆上是否存在相同字面量的对象),

  然后判断常量池上是否存在字符串的字面量,

   如果不存在

    在常量池上创建常量(并将常量地址值返回)

   如果存在

    不做任何操作

String s = new String("123");

/*

严格来说首先肯定会在堆中创建一个123的对象,然后再去判断常量池中是否存在123的对象,

如果不存在,则在常量池中创建一个123的常量(与堆中的123不是一个对象),

如果存在,则不做任何操作,解决了本文第一个面试题有问到创建几个对象的问题。

因为常量池中是有123的对象的,s指向的是堆内存中的地址值,s.intern()返回是常量池中的123的常量池地址,所以输出false

*/

System.out.println(s == s.intern());

3.两个双引号的字符串相加

判断这两个常量、相加后的常量在常量池上是否存在

  如果不存在

   则在常量池上创建相应的常量(并将常量地址值返回)

  如果存在,则直接返回地址值(只不过地址值分为两种情况,1是堆中的引用,2是本身常量池的地址)

    如果是引用,返回引用地址指向的堆空间对象地址值,

    如果是常量,则直接返回常量池常量的地址值,

String s1 = new String("123").intern();

String s2 = "1"+"23";

/*

* 首先第一句话 String s1 = new String("123") 以上分析过创建了两个对象(一个堆中,一个常量池 中)此时s1指向堆中

* 当s1调用.intern()方法之后,发现常量池中已经有了字面量是123的常量,则直接把常量池的地址返回给s1

* 在执行s2等于123时候,去常量池查看,同上常量池已经存在了,则此时s2不创建对象,直接拿常量池123的地址值使用

* 所以此时s1 和 s2 都代表是常量池的地址值,则输出为true

*/

System.out.println(s1 == s2);

如果这里看不懂 intern()方法时,可以快速滑动到文章尾部,先看intern()方法的分析。

4.两个new String()的字符串相加

首先会创建这两个对象(堆中)以及相加后的对象(堆中)

 然后判断常量池中是否存在这两个对象的字面量常量

  如果存在

   不做任何操作

  如果不存在

   则在常量池上创建对应常量

String s1 = new String("1")+new String("23");

/*

* 首先堆中会有 1 ,23 ,以及相加之后的123 这三个对象。如果 1,23 这两个对象在常量池中没有相等的字面量

* 那么还会在常量池中创建2个对象 最大创建了5个对象。最小创建了3个对象都在堆中。

*/

s1.intern();

String s2 = "123";

System.out.println( s1 == s2);// true

这个地方比较复杂 ,如果我把String s2 = "123" 代码放在s1.intern()前面先执行,其余代码不变,那么输出结果又为false,这里等会楼主会在分析 intern()方法的时候再重点分析一次。

String s2 = "123";

s1.intern();

System.out.println( s1 == s2);// false

5.双引号字符串常量与new String字符串相加

首先创建两个对象,一个是new String的对象(堆中),一个是相加后的对象(堆中)

 然后判断双引号字符串字面量和new String的字面量在常量池是否存在

  如果存在

   不做操作

  如果不存在

   则在常量池上创建对象的常量

String s1 = "1"+new String("23");

/*

*首先堆中会有 23 ,以及相加之后的123 这2个对象。如果23,1 这两个对象在常量池中没有相等的字面量

*那么还会在常量池中创建2个对象最大创建了4个对象(2个堆中,2个在常量池中)。最小创建了2个对象都堆中。

*/

String s2 = "123";

System.out.println( s1.intern() == s2);// true

6.双引号字符串常量与一个字符串变量相加

首先创建一个对象,是相加后的结果对象(存放堆中,不会找常量池)

 然后判断双引号字符串字面量在常量池是否存在

  如果存在

   不做操作

  如果不存在

   则在常量池上创建对象的常量

String s1 = "23";

/*

* 这里执行时,常量“1” 会首先到字符串常量池里面去找,如果没有就创建一个,并且加入字符串常量池。

* 得到的123结果对象,不会存入到常量池。这里特别注意和两个常量字符串相加不同 “1”+“23” 参考上面第三点

* 由于不会进入常量池,所以s2 和 s3 常量池地址值不同,所以输出为false

*/

String s2 = "1"+s1;

String s3 = "123";

System.out.println( s2 == s3.intern());

Q: 有人会问为什么两个常量字符串相加得到的对象就会入常量池(参考上面第3点),而加上一个变量就不会???

A: 这是由于Jvm优化机制决定的,Jvm会有编译时的优化,如果是两个常量,Jvm会认定这已经是不可变的,就会直接在编译 时和常量池进行判断比对等,但是如果是加上一个变量,说明最后运行得出的结果是可变的,Jvm无法在编译时就确定执 行之后的结果是多少,所以不会把该结果和常量池比对。

String.intern()方法分析:

在分析intern()方法时候,首先去官网查看api的相关解释

687406aa06003624b06b2cf61c6fede9.png

楼主大概翻译一下,意思就是:当调用这个方法时候,如果常量池包含了一个相等的常量,就把该 常量池的对象返回,否则,就把当前对象加入到常量池中并且返回当前对象的引用。楼主用更加白话的方式解释一下:

判断这个常量是否存在于常量池。

  如果存在,则直接返回地址值(只不过地址值分为两种情况,1是堆中的引用,2是本身常量池的地址)

    如果是引用,返回引用地址指向的堆空间对象地址值

    如果是常量,则直接返回常量池常量的地址值,

  如果不存在,

   将当前对象引用复制到常量池,并且返回的是当前对象的引用(这个和上面最开始的字符串创建分析有点不同)

实战分析问题:

基本上读者看到这里就可以尝试着去回过头文章一些示例代码,看看输出结果,这里分析一下上文存在的一个例子

public static void main(String[] args){

String s1 = new String("1")+new String("23");

s1.intern();

String s2 = "123";

System.out.println( s1 == s2);

}

分析: 1 首先看第一行是两个new String类型的字符串相加(详见上文第4点)可知道,这里创建了堆中有3个对象 一个是1, 一个是23,还有一个是结果 123,由于程序刚启动常量池也没有 1,23 所以会在常量池创建2个对象 (1 , 23)

2 当s1执行intern()方法之后,首先去常量池判断有没有123,此时发现没有,所以会把对象加入到常量池,并且返回 当前对象的引用(堆中的地址)

3 当创建s2时候(详见上文第1点),并且找到常量池中123,并且把常量池的地址值返回给s2

4 由于常量池的地址值就是s1调用intern()方法之后得到的堆中的引用,所以此时s1和s2的地址值一样,输出true。

public static void main(String[] args){

String s1 = new String("1")+new String("23");

String s2 = "123";

s1.intern();

System.out.println( s1 == s2);

}

如果把中间两行换一个位置,那输出就是false了,下面在分析一下不同点,上面分析过的不再赘述。

1.在执行到第二行的时候String s2 = "123"时,发现常量池没有123,所以会先创建一个常量

2.在当s1调用intern()方法时,会发现常量池已经有了123对象,就会直接把123的常量给返回出去,但是由于返回值并没有接 收,所以此时s1还是堆中地址,则输入false;如果代码换成 s1 = s1.intern();那s1就会重新指向常量池了,那输出就为true;

结尾:

由于本文都是在Jdk1.8版本(1.7由于已经把常量池放在堆中了和1.8结果应该一样)执行,如果有读者要探究1.6的相关问题,主要知道jdk1.6在创建String和1.8(1.7)的堆和常量池有一些不同实现,那相关问题就很清楚了,这里楼主没有涉及到1.6的相关问题,以免混淆读者,如果需要探究1.6以及相关问题,欢迎在评论区留言。

楼主很多时候都说了句柄指针的概念相对抽象,如果读者想知道两个对象是否是指向了相同的地址,可用 System.identityHashCode(Object x) 来验证。

最后本文也肯定有一些不足之处,欢迎大家在评论区留言,学无止境,大家加油。

13a68f01155b64d06e84dc00c371e07e.png

原文链接:https://blog.csdn.net/xzjayx/article/details/103020029

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值