Java String类相关

String类简述

String类是不可变类,由final关键字修饰,内部由final数组char[]保存字符。对于String类的方法,查询类方法不会导致char[]数组内容变化。其他诸如subString,replace的方法均需要原字符串内容变化,又由于String类不可变,当调用这些方法的时候实际上返回的是新建的String对象

String类内部由char[]数组维护,String类中大部分方法都是对数组的操作,诸如subString、replace之类均使用了System类中的静态方法:arrayCopy。

String intern()方法

Java常量池

关于Java常量池:分为两类,一是Class类常量池,二是运行时常量池。

运行时常量池可视为Java系统级别提供的缓存机制。
用的比较多的是基本数据类型的包装类常量池和String类常量池。

包装类中Byte,Short,Character,Integer,Long,Boolean均提供了常量池支持,两种浮点型数据类型没有提供常量池。其中Byte,Short,Character,Integer,Long仅在[-128,127]范围内提供了常量池。

    public static void main(String[] args) {

        Integer i = new Integer(10);
        Integer j = new Integer(10);
        System.out.println(i == j);//false,引用比较,不同的对象

        Integer i1 = 127;
        Integer j1 = 127;
        System.out.println(i1 == j1);//true,指向常量池中的同一个对象

        Integer i2 = 128;
        Integer j2 = 128;
        System.out.println(i2 == j2);//false,超过常量池范围,新建对象,不同的对象

        int i3 = 128;
        int j3 = 128;
        System.out.println(i3 == j3);//true,值比较
    }
String与常量池

String常量池使用的两种方式:

  • 字面量,该String对象将会被直接放入常量池。
  • String.intern()方法。调用intern方法后,String对象放入常量池。如果常量池中已经存在,则返回该常量的引用,如果不存在则放入常量池并返回引用。

String常量池本质上是一个散列表,但是该表不能自动扩容,当散列表中的String常量过多时,会引起大量hash collision,影响散列表的查询性能。hotspot虚拟机可以通过-XX:StringTableSize参数设定散列表的大小,我的eclipse默认大小为60013。

jdk6中字符串常量池在Perm Space中,大量使用字符串常量池容易内存溢出。jdk7移到了Heap中,在放入常量池时不再重新创建新的对象,而是直接将常量的引用指向原对象。

经典示例如下,我们基于jdk7来解析:

public static void main(String[] args) {
    String s = new String("1");//生成两个对象,一个普通对象s,一个字符串常量池内的对象"1"
    s.intern();//s放入常量池时发现已经有“1”了
    String s2 = "1";//s2指向常量池内的“1”
    System.out.println(s == s2);//false  s指向普通对象,s2指向常量池内对象

    String s3 = new String("1") + new String("1");//生成4个对象,一个常量池“1”,一个普通对象s3(值为11),
    //两个匿名普通对象
    s3.intern();//s3放入常量池成功
    String s4 = "11";//s4指向常量池“11”,常量池中“11”由s3放入,由于常量池在heap中,
    //“11”直接指向了s3,而不是重新在常量池中新建一个“11”对象,则s4即指向了s3对象
    System.out.println(s3 == s4);//true s3和s4指向同一个对象“11”
}
public static void main(String[] args) {
    String s = new String("1");//创建两个对象,一个普通对象s,一个常量池对象“1”
    String s2 = "1";//s2指向常量池对象“1”
    s.intern();//s放入的时候发现常量池已经存在“1”
    System.out.println(s == s2);// false s和s2指向不同的对象

    String s3 = new String("1") + new String("1");//创建四个对象:一个普通对象s3(值为11),
    //一个常量池对象“1”,两个匿名普通对象
    String s4 = "11";//创建常量池对象“11”,s4指向它
    s3.intern();//s3放入发现已存在
    System.out.println(s3 == s4);//false s3 和s4指向不同的对象
}

在大量创建String对象时,如果每次创建String对象后并调用intern方法,并将intern方法返回的引用作为该String对象的引用,则有相同内容的String对象将会在gc时被大量回收,可以节约内存和提高效率。

StringBuilder

StringBuilder和StringBuffer的区别在于StringBuilder是非线程安全的,而StringBuffer是线程安全的。除此之外两者在功能上是相同的,均继承于抽象类AbstractStringBuilder,该类主要定义了针对字符串的几类操作:append、insert、delete、query等。
AbstractStringBuilder对象内部亦由char[]数组维护字符串,所以各种操作底层都是在操作数组。这里要注意的是由于数组定义后长度不可变,当append和insert操作时需要先检查容量是否足够,不够时需要新建数组扩容后再进行arrayCopy。

https://tech.meituan.com/in_depth_understanding_string_intern.html

猜你喜欢

转载自blog.csdn.net/john_lw/article/details/79567022