String基本特性

String : 字符串,使用一对""引起来表示。

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

String 实现了Serializeable接口:表示字符串支持序列化的。实现了Comparable 接口:表示String可以比较大小

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

String存储结构变更

String使用byte[]加上编码标记,节约了内存空间

StringBuffer 和StringBuilder 都也有相应的改动。

String的基本特性

String代表不可变的字符序列,即不可变性

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

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

String的String Pool 是一个固定大小的Hashtable,默认值大小长度是1009,如果放进String Pool 的String非常多,就会造成Hash冲突,从而导致链表会很长,而链表长了后会造成的影响就是当调用String.intern时性能会大幅下降。

使用-XX:StringTableSize 可设置StringTable的长度

在jdk6中StringTable是固定的,就是1009 的长度,所以如果常量池中的字符串过多就会导致效率下降地很快。StringTableSize设置没有要求

在jdk7中,StringTable的长度默认值是60012 ,jdk8开始,1009是可以设置的最小值。

String 的内存分配

Java6及之前,字符串常量池存放在永久代。

Java7 中Oracle的工程师对字符串池的逻辑做了很大的变化,即将字符串常量池的位置调整到Java堆内。

  • 所有的字符串都保存在堆(Heap)中,就像其他普通对象一样,这样可以让你在进行调优应用时仅需调整堆大小就可以了。
  • 字符串常量池概念原本使用得比较多,但是这个改动使得我们有足够的理由让我们重新考虑在java7 中使用String.intern()。

Java8 元空间,字符串常量在堆。

String的基本操作

Java语言规范里要求完全相同的字符串字面量,应该包含同样的Unicode字符序列(包含同一份码点序列的常量),并且必须是指向同一个String类实例。

字符串拼接操作

  • 常量与常量的拼接结果是在常量池,原理是编译期优化
  • 常量池中不会存在相同内容的常量
  • 只要其中有一个是变量,结果就在堆中。变量拼接的原理是StringBuilder
  • 如果拼接的结果调用intern()方法,则主动将常量池中还没有的字符串对象放入池中,并返回对象地址。
@Test
public void test3(){
    String s1 = "a";
    String s2 = "b";
    String s3 = "ab";
    /*
    如下的s1 + s2 的执行细节:(变量s是我临时定义的)
    ① StringBuilder s = new StringBuilder();
    ② s.append("a")
    ③ s.append("b")
    ④ s.toString()  --> 约等于 new String("ab")

    补充:在jdk5.0之后使用的是StringBuilder,在jdk5.0之前使用的是StringBuffer
     */
/*
1. 字符串拼接操作不一定使用的是StringBuilder!
   如果拼接符号左右两边都是字符串常量或常量引用,则仍然使用编译期优化,即非StringBuilder的方式。
2. 针对于final修饰类、方法、基本数据类型、引用数据类型的量的结构时,能使用上final的时候建议使用上。
 */
/**
 * 如何保证变量s指向的是字符串常量池中的数据呢?
 * 有两种方式:
 * 方式一: String s = "shkstart";//字面量定义的方式
 * 方式二: 调用intern()
 *         String s = new String("shkstart").intern();
 *         String s = new StringBuilder("shkstart").toString().intern();
 *
 */
public class StringIntern {
    public static void main(String[] args) {

        String s = new String("1");
        s.intern();//调用此方法之前,字符串常量池中已经存在了"1"
        String s2 = "1";
        System.out.println(s == s2);//jdk6:false   jdk7/8:false
        String s3 = new String("1") + new String("1");//s3变量记录的地址为:new String("11")
        //执行完上一行代码以后,字符串常量池中,是否存在"11"呢?答案:不存在!!
        s3.intern();//在字符串常量池中生成"11"。如何理解:jdk6:创建了一个新的对象"11",也就有新的地址。
        //         jdk7:此时常量中并没有创建"11",而是创建一个指向堆空间中new String("11")的地址
        String s4 = "11";//s4变量记录的地址:使用的是上一行代码代码执行时,在常量池中生成的"11"的地址
        System.out.println(s3 == s4);//jdk6:false  jdk7/8:true
    }
}
public class StringIntern1 {
    public static void main(String[] args) {
        //StringIntern.java中练习的拓展:
        String s3 = new String("1") + new String("1");//new String("11")
        //执行完上一行代码以后,字符串常量池中,是否存在"11"呢?答案:不存在!!
        String s4 = "11";//在字符串常量池中生成对象"11"
        String s5 = s3.intern();
        System.out.println(s3 == s4);//false
        System.out.println(s5 == s4);//true
    }
}

总结String 的intern()的使用

Jdk1.6中,将这个字符串对象尝试放入串池

  • 如果串池中有,则并不会放入,返回已有的串池中的对象的对象地址
  • 如果没有,会把此对象赋值一份,放入串池,并返回串池中的对象地址

jdk1.7起,将这个字符串对象地址尝试放入串池

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

参考资料:宋红康JVM教程


林慫慫
11 声望0 粉丝

引用和评论

0 条评论