Java基础

初探String

2018-07-20  本文已影响3人  黄金矿工00七

平时只知道使用String,却不知道String有这么多奥秘,今天就来好好探讨一下。

String概述

我们从源码下手

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];

首先看到String是一个final的类,甚至所有的变量都是final修饰,final有什么用呢?

不可变性
image.png
 /**
     * Allocates a new {@code String} so that it represents the sequence of
     * characters currently contained in the character array argument. The
     * contents of the character array are copied; subsequent modification of
     * the character array does not affect the newly created string.
     *
     * @param  value
     *         The initial value of the string
     */
    public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);
    }

以上所说的是String成为了一个immutable类,这也是我们以后设计不可变类的一些方式


public class Test {


  public static void main(String[] args) {
    HashSet<String> strings = new HashSet<>();
    String s = "1";
    strings.add(s);
    String s1 = s;
    s1 += "123";
    System.out.println(strings);
    HashSet<StringBuilder> stringBuilders = new HashSet<>();
    StringBuilder sb = new StringBuilder("1");
    stringBuilders.add(sb);
    StringBuilder sb1 = sb;
    sb1.append("123");
    System.out.println(stringBuilders);
  }
}
image.png
使用可变类的时候键值被修改,这是很大的问题
第二个例子:多并发的场景下,使用可变类需要额外的同步并且很容易出问题,但是不可变类就不存在这些问题(为啥?不可变类的状态只有一种,并且由构造函数来控制,所以是无法修改的,也就不存在线程安全问题)
String常量池
在上一篇关于Java内存区域管理的文章中大概的说了一下方法区,在Java8中,String常量池移入了堆中 image.png

大概是这个样子,详细的我就不再画了。
常量池是干啥的?
很明显,用来缓存String对象的呗,复用。
String常量可能会在两种时机进入常量池:

使用 ” ” 双引号创建 : String s1 = “first”;
使用字符串连接符拼接 : String s2=”se”+”cond”;
使用字符串加引用拼接 : String s12=”first”+s2;
使用new String(“”)创建 : String s3 = new String(“three”);
使用new String(“”)拼接 : String s4 = new String(“fo”)+”ur”;
使用new String(“”)拼接 : String s5 = new String(“fo”)+new String(“ur”);
image.png
我们看到所有的字面量被放进了常量池,这里要注意的是下面这个例子:
 String result = "hello" + "world" ;
常量池中是helloworld
String a="hello";
String b="world";
String result =a+b;
常量池中是hello和world
final String a="hello";
final String b="world";
String result =a+b;
常量池中是hello和world 以及helloworld
public static void main(String[] args) {
    String s = new String("1");
    s.intern();
    String s2 = "1";
    System.out.println(s == s2);

    String s3 = new String("1") + new String("1");
    s3.intern();
    String s4 = "11";
    System.out.println(s3 == s4);
    //这段代码更加直观
    //  String s3 = new String("1") + new String("1");
    //  System.out.println(s3 == s3.intern(););
}

这里我用的jdk8做的测试 结果为false和true


image.png

String s = new String("1"); 第一句代码,生成了2个对象。常量池中的“1” 和 JAVA Heap 中的字符串对象。s.intern(); 这一句是 s 对象去常量池中寻找后发现 “1” 已经在常量池里了。接下来String s2 = "1"; 这句代码是生成一个 s2的引用指向常量池中的“1”对象。 结果就是 s 和 s2 的引用地址明显不同。
String s3 = new String("1") + new String("1");,这句代码中现在生成了2最终个对象,是字符串常量池中的“1” 和 JAVA Heap 中的 s3引用指向的对象。中间还有2个匿名的new String("1")我们不去讨论它们。此时s3引用对象内容是"11",但此时常量池中是没有 “11”对象的。
接下来s3.intern();这一句代码,是将 s3中的“11”字符串放入 String 常量池中,因为此时常量池中不存在“11”字符串,关键点是 jdk76以后常量池不在 Perm 区域了,这块做了调整。常量池中不需要再存储一份对象了,可以直接存储堆中的引用。这份引用指向 s3 引用的对象。 也就是说引用地址是相同的。

public static void main(String[] args) {
    //intern下调
    String s = new String("1");
    String s2 = "1";
    s.intern();
    System.out.println(s == s2);

    String s3 = new String("1") + new String("1");
    String s4 = "11";
    s3.intern();
    System.out.println(s3 == s4);
     
}
image.png

运行结果为false,false,为啥呢,String s = new String("1"); 第一句代码,生成了2个对象。常量池中的“1” 和 JAVA Heap 中的字符串对象。。接下来String s2 = "1";这一句是 s2 对象去常量池中寻找后发现 “1” 已经在常量池里了 ,所以s2引用的是常量池的对象,s.intern这句代码已经没有作用了,因为常量池中已经存在1了。 结果就是 s 和 s2 的引用地址明显不同
String s3 = new String("1") + new String("1");,这句代码中现在生成了2最终个对象,是字符串常量池中的“1” 和 JAVA Heap 中的 s3引用指向的对象。中间还有2个匿名的new String("1")我们不去讨论它们。此时s3引用对象内容是"11",但此时常量池中是没有 “11”对象的。然后 String s4 = "11";在常量池中生成了11对象,所以s4这时候是指向常量池中的对象的,s3.intern也没有任何影响,因为常量池中已经存在11对象了

最后总结一下就是:String#intern 方法时,如果常量池中存在对象,则返回常量池中的对象;否则,如果存在堆中的对象,会直接保存对象的引用,而不会重新创建对象。(常量池中既有对象也有引用)

要注意的是,String的String Pool是一个固定大小的Hashtable,默认值大小长度是60013,如果放进String Pool的String非常多,就会造成Hash冲突严重,从而导致链表会很长,而链表长了后直接会造成的影响就是当调用String.intern时性能会大幅下降(因为要一个一个找)。
可以使用下面命令查看你的StringTable大小
-XX:+PrintStringTableStatistic...

上一篇下一篇

猜你喜欢

热点阅读