当前位置: 代码迷 >> 综合 >> String的特性及字符串常量池(intern()的使用、字符串的拼接详解)
  详细解决方案

String的特性及字符串常量池(intern()的使用、字符串的拼接详解)

热度:22   发布时间:2024-03-09 20:47:31.0

文章目录

  • 12. String的特性
    • 12.1 String的基本特性
      • 12.1.1 StringTable
    • 12.2 String的内存分配
    • 12.3 字符串的拼接
    • 12.4 intern()的使用
    • 12.5 new String()

12. String的特性

12.1 String的基本特性

  • String声明为final的,不可被继承

    • String s1 = "ILOVEYOU";//字面量的定义方式
    • String s2 = new String("ILOVEYOU");
  • String实现了serializable接口:表示字符串是支持序列化的(跨进程传输数据)。实现了Comparable接口:表示string可以比较大小

  • String在jdk8及以前内部定义了final char[] value用于存储字符串数据。idk9时改为byte[]

  • String:代表不可变的字符序列。不可变性

    • 当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的value进行赋值。
    • 当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
    • 当调用string的replace ()方法修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
  • 通过字面量的方式(区别于new)给一个字符串赋值,此时的字符串值声明在字符串常量池中。

字符串常量池中是不会存储相同内容的字符串的

12.1.1 StringTable

  • string的string Pool是一个固定大小的Hashtable,默认值大小长度是1009。如果放进string Pool的string非常多,就会造成Hash冲突严重,从而导致链表会很长,而链表长了后直接会造成的影响就是当调用string.intern时性能会大幅下降。
  • 使用-XX:stringTablesize=?可设置stringTable的长度
  • 在jdk6中stringTable是固定的,就是1009的长度,所以如果吊量池中的字符串过多就会导致效率下降很快。StringTablesize设置没有要求。
  • 在jdk7中,stringTable的长度默认值是60013。
  • jdk8开始,1009是StringTable可设置的最小值。

12.2 String的内存分配

  • jdk6及以前字符串常量池存放在永久代中。jdk7将字符串常量池的位置调整到java堆中。jdk8字符串常量池在堆中。

12.3 字符串的拼接

  • 常量与常量的拼接结果在常量池,原理是编译期优化

    String s1 = "a" +"b" +"c";//等同于“abc”,在编译期间就赋值为“abc”
    String s2 = "abc";
    System.out.println(s1 == s2);//true
    System.out.println(s1.equals(s2));//true
    
  • 常量池中不会存在相同内容的常量。

  • 只要其中有一个是变量,结果就在堆中。变量拼接的原理是stringBuilder

  • 如果拼接的结果调用intern()方法,则主动将常量池中还没有的字符串对象放入池中,并返回此对象地址。

String s1 = "javaEE";
String s2 = "hadoop";String s3 = "javaEEhadoop";
String s4 = "javaEE" + "hadoop";//编译器优化
//如果拼接符号的前后出现了变量,则相当于在推空间中new String()
String s5 = s1 + "hadoop";
String s6 = "javaEE" + s2;
//jdk5.0之后,执行细节:首先new StringBuilder,然后分别append(s1)、append(s2),最后toString()生成一个string对象.
String s7 = s1 + s2;System.out.println(s3 == s4);//true
System.out.println(s3 == s5);//false
System.out.println(s3 == s6);//false
System.out.println(s3 == s7);//false
System.out.println(s5 == s6);//false
System.out.println(s5 == s7);//false
System.out.println(s6 == s7);//false
//进行校验,如果s6的值存在于字符串常量池中,则将该地址返回给s8,如果没有,则在常量池中创建字符串。
String s8 = s6.intern();
System.out.println(s3 == s8);//true----------------------------------------------------------------------------------------
//字符串拼接不一定使用StringBuilder,也有可能是编译器优化
final String s1 = "a";//常量
final String s2 = "b";//常量
String s3 = "ab";
String s4 =  s1 + s2;
System.out.println(s3 == s4);//true-----------------------------------------------------------------------------------------
当多次进行字符串拼接的时候,StringBuilder的append()的效率远高于+操作。
StringBuilder的append的方式自始至终只创建了一个StringBuilder对象。而String的拼接创建了多个StringBuilder和String的对象,内存占用比较大,如果GC,也会花费时间。
优化空间:如果基本确定字符串的长度,新建对象的时候就可以指定(底层数组)容量,避免后续多次新建StringBuilder。即StringBuilder sb = new StringBuilder(capacity);

12.4 intern()的使用

  • 如果不是用双引号声明的string对象,可以使用string提供的intern方法: intern方法会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串放入常量池中。
    比如:String myInfo =new String ("I love you" ).intern();
  • 确保:字符串常量池中是不会存储相同内容的字符串的

通过以下方式保证变量s指向的是字符串常量池中的数据

  1. String s = "I Love com";//字面量的定义方式
  2. String s = ......intern();//调用intern()方法

12.5 new String()

new String("ab");会创建几个对象?两个,通过字节码指令ldc

:一个对象是new关键字在堆中创建,一个对象是字符串常量池中的

String str = new String("a") + new String("b");会创建几个对象?

  • 对象1:new StringBuilder()

  • 对象2:new String(“a”);

  • 对象3:字符串常量池中的"a"

  • 对象4:new String(“b”)

  • 对象5:字符串常量池中的"b"

  • 深入刨析:StringBuilder的toString();

    对象6:toString()会new String(“ab”);

    在字符串常量池中没有"ab"

String s1 = new String("1");//s指向队中的"1",此时常量池中也有"1"
s.intern();
String s2 = "1";//字面量定义"1",指向常量池中已有的"1"
System.out.println(s1 == s2);//jdk6:false jdk7/8:falseString s3 = new String("1") + new String("1");//s3指向的是堆中的new String("11"),此时常量池中没有"11"
s3.intern();//在字符串常量池中开辟空间,存放的是上述堆中new String("11")的地址
String s4 = "11";//s4指向常量池中的"11",实际指向堆中的"11"的地址
System.out.println(s3 == s4);//jdk6:false jdk7/8:true
  • 总结string的intern()的使用:
    jdk1.6中,将这个字符串对象尝试放入串池。

    • 如果串池中有,则并不会放入。返回已有的串池中的对象的地址

    • 如果没有,会把此对象复制一份,放入串池,并返回串池中的对象地址

    Jdk1.7起,将这个字符串对象尝试放入串池。

    • 如果串池中有,则并不会放入。返回已有的串池中的对象的地址
    • 如果没有,则会把对象的引用地址复制一份,放入串池,并返回串池中的引用地址

例题:

String s = new String("a") + new String("b");
String s2 = s.intern();
System.out.println(s2 == "ab");
System.out.println(s == "ab");解释为什么在jdk7/8中都是true?:
String s = new String("a") + new String("b");//
String s2 = s.intern();//字符串常量池中存放的是堆中new String("ab")的地址
System.out.println(s2 == "ab");//jdk6:true jdk7/8:true --> 与"ab"相比时,发现堆中有指向堆中的"ab",所以等号左右都指向堆中的"ab"
System.out.println(s == "ab");//jdk6:false jdk7/8:true
  相关解决方案