Java字符串详解

一、字符串常量池

  • 字符串常量池,在 jdk1.7 版本后开始实现在堆内存中。
  • 与基本类型的包装类不一样,字符串常量池没有装箱拆箱等操作,也不会像包装类一样一开始就有数据,而是在程序运行的过程中,将一些字符串存入常量池中,以此来提高效率。
  • jdk1.7后中常量池可以存对象也可以存引用,其中1.6存的是对象。

下面通过一个例子来说明一下常量池

    public static void main(String[] args) {
        String s1 = "123";
        String s2 = new String("123");
        String s3 = "123";
        System.out.println(s1==s2);    //false
        System.out.println(s1==s3);    //true
        System.out.println(s2==s3);    //false
    }

nZSZqJ.gif

  • 程序中出现的字符串常量,无论是在用作参数(s2通过构造函数以 “123” 为参数创建对象)还是用来赋值(s1直接赋值字符串常量 “123” ),都会经历一个步骤:先在常量池中创建该对象。比如 s1 就会直接在常量池创建对象.
  • 通过 new 来创建字符串,不会通过常量池,而是直接在常量池外另外创建一个对象。(动画有错,s2 不会指向常量池)
  • 如果字符串常量直接用来赋值,就会去常量池中查找是否已经存在该字符串,如果有,就直接返回那个字符串对象或者引用,如果没有,就在常量池中新建一个对象。

我们尝试一下理解下面的代码:

    public static void main(String[] args) {
        String s1 = "123";
        String s2 = new String(s1 +"456");
        System.out.println(s2.intern() == s2);          //true
        String s3 = new String(s1 + "456");
        System.out.println(s3.intern() == s3);          //false
    }
  • 创建 s2 后,常量池中存在的字符串对象有 “123”,“456”,需要注意的是,此时 s2 所指向的 “123456” 不存在于常量池中。
  • intern()函数的作用是返回常量池中所存储的、等于这个字符串的对象的引用,如果这个字符串不存在于常量池中,就会将这个对象的引用存到常量池中 ,所以执行 intern() 后,常量池将 s2 的对象的引用存了进去,并返回了出来,所以他们的地址相同。
  • 第二个判断中,因为常量池中已经存在了 “123456” 这个字符串的引用,也就是 s2 ,因此返回的是 s2 的地址,因此是false

此处放个坑:

    public static void main(String[] args) {
        String s2 = new String("hello");
        s2.intern();
        String s1 = "hello";
        System.out.println(s1 == s2);            //false
        String s3 = new String("hello") + new String("world");
        s3.intern();
        String s4 = "helloworld";
        StringBuilder stringBuilder = new StringBuilder();
        System.out.println(s3 == s4);                //true
    }

我们都知道 intern() 的作用了,那为什么上面两个输出是不一样的结果呢?

我们之前提到 new 的一个过程,也就是说, s2 会创建两个 “hello” 对象,先根据字符串常量 “123” 创建一个在常量池中,然后再 new 一个在常量池外的堆内存中,注意这是两个对象,不是常量池外的对象往常量池中存入地址。因此 s2.intern() 没有任何作用, s1 指向的是 s2 所 “不经意” 创建出来的对象,因此 s1 不等于s2。

而对于 s3 来说,他这样通过连接创建,常量池中没有存在 “helloworld” ,因此它通过 s3.intern() 将自己的引用存入常量池中,当 s4 直接赋值字符串常量 “123456” 时,就会直接给 s4 赋值常量池中所存的 s3 的地址,因此 s3 等于s4。

 
 

二、字符串连接的底层操作

字符串对象的获取我们已经知道了原理,接下来研究一下拼接的操作:

	public static void main(String[] args) {
        String s1 = "123" + "123";
        String s2 = s1 + "123";
        String s3 = s1 + s2;
    }

我们用 javap -c 来反编译一下对应的 .class 文件,可以得到以下内容:
nke8JA.png

s1 在编译器中会自动优化为

String s1 = "123123";

因此 s1 创建完后,常量池中只有“123123”,不存在 “123” 。

s2 我们可以通过反编译看到,会先新建一个StringBuilder对象,赋值为 “” ,然后再调用 append() 方法,将 s1 拼接,然后再新建一个 “123” 的对象,要注意 “123” 会注册到常量池中,然后再调用 append() 方法拼接,最后通过 StringBuilder 的 toString() 方法返回一个 String 对象,我们来看一下 toString 的源码

@Override
    public String toString() {
        // Create a copy, don't share the array
        return new String(value, 0, count);
    }

因此执行完 s2 后,常量池中存在的是 “123123” ,“123”,因为在拼接完所有字符串后,是以 StringBuilder 的值直接 new 一个 String 对象,没有出现字符串常量,因此不会将 s2 注册到常量池中,我们可以验证一下:

	public static void main(String[] args) {
        String s1 = "123" + "123";
        String s2 = s1 + "123";
        String s3 = "123123123";
        System.out.println(s3.intern() == s2);   //false
    }

如果 s2 注册进了常量池,那么应该为true才对,为false说明没有注册到常量池中。
s3 与 s2 的原理一致,就不多说了。

三、String、StringBuilder、StringBuffer的区别

  • 这三者底层都是字符数组实现的。
  • 通过查看源码,可以发现 StringBuffer 和 StringBuilder 的区别就是 StringBuffer 的方法上基本都加上了synchronized关键字,这使得 StringBuffer 是线程安全的。
  • String中的大部分方法都会返回 new 出来的一个新的 String 对象,而 StringBuilder 会直接返回它本身,因此 String 类型的一些操作的效率远不如 StringBuilder 类型快,尤其是对字符串进行大量的拼接修改操作时。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值