java字符串之理论重点

1、简单概述

一直以来,对String这个字符串类都是心存疑惑的,也不知道如何来进行解决。最近花点时间看了下别人的博客来研究一下,觉得挺有意思的,所以将此来记录下来。

String将会牵扯到堆和常量池,字符串常量池在JDK8中是存在于堆中的,是属于堆的一部分。

引入:

String str = "计算机";

"computer"也是一个字符串对象,只不过上面创建的这个会存在于字符串常量池中,返回来的是在字符串在常量池中的地址。

String str = "计算机";
String str2 = "计算机";

这段代码表示的是都在字符串常量池中来创建"计算机"对象,但是由于第一行中已经在字符串常量池中创建好了"计算机"对象,所以第二行代码将不会再次进行创建,而是直接指向字符串常量池的中"计算机"的地址。因为常量池是用来节省内存资源开销的,将一些经常使用的常量放在里面来方便来使用。

测试这段代码:

        String str = "计算机";
        String str2 = "计算机";
        System.out.println(str==str2); // true

因为二者指向的是在常量池中得同一个地址,所以返回为true

上面两种创建字符串的方式也是官方推荐的,但是和使用new关键字创建出来的字符串有很大的不同的使用

String str = new String("计算机");

首先看到了因为出现了字符串常量"计算机",那么首先做的事情就是检查字符串常量池中有没有"计算机",如果没有,那么添加进去;如果有,那么就不添加;

然后第二步,因为是new,所以会在堆中创建出来"计算机"对象

再看一段代码:

String str = new String("计算机");
String str2 = new String("计算机");

执行第一行代码的时候,先去字符串常量池中检查是否存在"计算机"对象,如果存在,那么不创建;如果不存在,那么就在字符串常量池中创建该对象;

然后执行new的步骤,会在堆内存中来创建出"计算机"对象,str引用指向新开辟的内存空间地址;

然后执行第二行代码,先去字符串常量池中检测是否存在"计算机"对象,发现已经存在了,那么就不再进行创建;

然后执行new的步骤,会在堆内存中创建出来"计算机"对象,str2引用指向新开辟的内存空间地址

最终的图如下所示:

所以当在检测的时候

        String str = new String("计算机");
        String str2 = new String("计算机");
        System.out.println(str==str2); // false

2、intern()方法

intern()方法也是面试中的经常问到的地方,所以在这个地方也会来做一个说明。

jdk1.8中是这样描述intern()方法的:当intern()方法被调用的时候,如果字符串常量池中已经存在这个字符串对象了,就返回常量池中该字符串对象的地址;如果字符串常量池中不存在,就在常量池中创建一个指向该对象堆中实例的引用,并返回这个引用地址。

这里想要表达的意思就是说:

if(字符串常量池中存在){
    return 常量池中地址;
}
if(常量池中不存在){
    return 创建指向堆中字符串的地址的引用;
}

这里也用图来进行说明下:

        String str1 = new String("计算机")+new String("软件");
        String str2 = str1.intern();

使用上述的代码来验证一下:

        String str1 = new String("计算机")+new String("软件");
        String str2 = str1.intern();
        System.out.println(str1 == str2); // true

但是在这里,我需要说明一下上面的第一行代码:

        String str1 = new String("计算机")+new String("软件");

这里在常量池中存在的字符串只有"计算机"和"软件",不会存在"计算机软件",从上面的图中可以看到。

验证一下:

如果说:String str1 = new String("计算机")+new String("软件");这段代码执行完成之后,存在了"计算机软件",那么将直接会返回在字符串常量池中的地址,这个时候如果和堆中得地址来进行比较,那么返回的应该是false,但是这里返回的是true,可以验证。

而执行

        String str2 = str1.intern();

这段代码后,就会在字符串常量池中来创建"计算机软件"对象,因为字符串常量池中是没有的,但是堆中有,所以创建了一个指向堆中的常量的对象,这个对象也是"计算机软件",但是这个对象实际指向的却是堆中的地址:

用一个例子说明下:

String s3 = new String("1") + new String("1");
s3.intern();
String s4 = "11";

第一步中:首先检查字符串常量池中是否存在着"1"对象,如果不存在,那么在字符串常量池中来进行创建;如果存在了,那么不需要来进行创建;最终会在堆中创建两个"1"对象,因为有两个new。

然后在堆中会存在"11"对象,其实这个和上面的一样,两个new出来的对象进行拼接的时候,到底是在堆中产生了新的对象还是在字符串常量池中,我觉得可以来进行证明:

        String s3 = new String("1") + new String("1");
        String s4 = "11";
        System.out.println(s3 == s4); // false

证明了第一行中产生的对象是在堆中的,直接new的话就会在常量池中。

那么再接着说上面的问题:

String s3 = new String("1") + new String("1");
s3.intern();
String s4 = "11";

因为第一行中产生的"11"是在堆内存中的,如果第二行执行了,如果常量池中有,那么就直接返回其地址了;如果没有,那么就直接返回在堆内存中的地址;很明显,字符串常量池中是没有"11"的,只存在于堆中,那么执行完成之后,会在字符串常量池创建指向堆内存的引用,然后将这个引用保存在字符串常量池中。

然后走到第三行的时候,会去检查"11"是否存在于常量池中,检查到了有一个引用存在于常量池中,但是实际指向的却是堆中的,所以s4也指向了堆中的地址。这里底层应该是做了某种优化方式。

看看最终的结果:

        String s3 = new String("1") + new String("1");
        s3.intern();
        String s4 = "11";
        System.out.println(s3==s4); // true

这里还应该注意到一个问题,就是顺序的问题:

        String s4 = "11";
        String s3 = new String("1") + new String("1");
        s3.intern();
        System.out.println(s3==s4); // false

第一行:首先直接在字符串常量池中创建一个对象"11",

第二行:首先在堆中创建"1",然后在堆中创建"11";

第三行:因为字符串常量池中已经存在了"11"对象,所以将常量池中得字符串对象"11"地址进行返回;

第四行:堆中地址和常量池中地址进行比较,返回FALSE

面试题

String s1 = "1";
String s2 = new String("1");

这段代码中创建了几个对象?

第一步:检查字符串常量池中是否存在"1",如果不存在,那么创建"1"对象;如果存在,就不会对象"1"对象;

第二步:检查字符串常量池中是否存在"1",存在,则不会在字符串常量池中来创建,直接new一个"1"在字符串常量池中;

所以这段代码可能创建了1个对象,也可能创建了2个对象。

因为new肯定是会在堆中来创建对象的,至于是否需要在字符串常量池中创建,取决于字符串常量池中是否存在了"1"对象;

3、总结

字符串对象一定位于堆中,到底是字符串常量池还是堆中得除了常量池的其他位置,需要按照具体情况来定。

注意的两种情况:

String str1 = "aaa";
String str2 = new String("aaa");

在结合intern()方法的使用分析即可。

有一点需要来补充说明下,字符串常量池中也缓存了一些默认的字符串。比如"java",但是到底缓存了多少个,那我们就不得而知了。可以验证下:

        String s1 = new String("ja")+new String("va");
        String s2 = s1.intern();
        System.out.println(s1==s2); // false

但是要是改变了其中的一点,那么结果将会变化:

        String s1 = new String("ja")+new String("va1");
        String s2 = s1.intern();
        System.out.println(s1==s2); // true

因为java中得字符串常量池中缓存了"java"这个字符串,只不过我们无法看到而已。

参考文档:https://blog.csdn.net/huangxin388/article/details/110918064?utm_medium=distribute.pc_relevant.none-task-blog-2~default~BlogCommendFromMachineLearnPai2~default-1.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2~default~BlogCommendFromMachineLearnPai2~default-1.control

posted @ 2021-09-04 13:43  写的代码很烂  阅读(45)  评论(0编辑  收藏  举报