String详解

String不可变

  1. String被声明为final,不可被继承,就不能通过子类继承去更改String类
  2. 内部使用final型的byte数组value保存字符串内容,意味着数组的地址空间不可改变,value不能再引用其他数组
  3. String内部没有提供改变value数组内容的方法

不可变的好处

  1. 线程安全
    String不可变,可以在多个线程中安全地使用
  2. 可以缓存hash值
    String的hash值经常被使用,比如在hashmap中作为key,经常要计算key的hash值来计算索引位置。String不可变使得hash值也不可变,hash值只要计算一次即可,不用多次计算
  3. 字符串常量池缓存
    String不可变,使得部分字符串字面量可以作为缓存在字符串常量池中,相同的字符串字面量就不用重复创建,节省内存。因为String是不可变的,所有字符串字面量才可以缓存在字符串常量池中
  4. 安全性
    String经常被用做参数,只有String不可变才能保证参数不可变。例如在String在作为数据库连接参数(用户名、密码等),如果String可变,那么在数据库连接过程中就可能因为字符串参数内容改变而发生不可预期的错误

String常量池

  • String常量池保存着一些被用做缓存的String实例
  • 加入String常量池的两种方法:String字面量在编译期就会被创建并加入到String常量池中;运行期使用String 的 intern() 方法把该String实例加入到String常量池中,并返回它的引用(前提是常量池中不存在该String实例的缓存)。
  • 当要生成一个String实例时,如果该String实例是字面量,则会先在String常量池中检查有没有实例和要生成的String字面量内容相同(使用equals方法为true):如果没有,则创建一个String实例,并放在String常量池中当做缓存供其他String实例使用;如果有,则不创建新的String实例,直接把String常量池中的String实例的引用返回给它就行,因为String字面量在编程中经常会被重复多次使用,这样可以防止创建过多的String实例。

看下面两个例子就能明白:

        String b = "b";
        String s1 = new String("aaa"+b);//这里实际上生成三个对象,编译期生成:一个"aaa"放在常量池中,运行期生成:一个"aaab"放在堆中,由+拼接产生,作为s1的过渡,这里编译器没有优化,另一个"aaab"也是放在堆中,由new产生,s1指向的String实例也是这个"aaab"
        String s3 = s1.intern();//String实例"aaab"并不存在于String常量池中,使用intern方法就会把s1的对象加入到常量池中,并返回s1的引用,因此s1和s3的地址相同,为同一个对象
        System.out.println(s1==s3);//true
        String s1 = new String("aaa"+"b"); //编译器优化直接生成"aaab"String实例,并在编译期把该字面量加入常量池中。这里实际上生成两个对象,一个在常量池中,一个在堆中
        String s3 = s1.intern();//常量池中已经存在编译期加入的"aaab"字面量,因此intern方法并不会把s1加入到常量池中,而是返回常量池中的"aaab"字面量的引用,而s1和常量池中的字面量"aaab"是两个对象,所以下面为false
        System.out.println(s1==s3);//false

关于编译期优化,使用+连接两个String实例,细细品读下面代码:

        String rea = "ab";//编译期生成String实例加入常量池
        String b = "b";
        final String d = "b";//String常量,在编译期如果存在对d的引用,则直接把常量d替换成d的引用,也称为解析阶段
        String test1 = "a"+"b";//编译期优化,代码变为String test1 = "ab",因为之前rea已经被加入常量池中,所以这里不再生成新的String实例,而是直接把rea的引用返回给test1
        String test2 = "a"+b;//+两边存在String变量,编译期无法确定test2的值,因此无法优化,而是生成新的String实例
        String test3 = "a"+d;//+右边的d因为是常量,编译期会直接把d替换成d引用的对象"b",这样+两边又是字面量了,按test1处理
        System.out.println(test1==test1.intern());//true
        System.out.println(test1==rea); //true
        System.out.println(test2==test2.intern());//false
        System.out.println(test2.intern()==rea);//true
        System.out.println(test3==test3.intern());//true
        System.out.println(test3.intern()==rea);//true

在 Java 7 之前,String常量池被放在运行时常量池中,它属于永久代。而在 Java 7,String常量池被移到堆中。这是因为永久代的空间有限,在大量使用字符串的场景下会导致 OutOfMemoryError 错误。

String, StringBuffer and StringBuilder区别

  1. 可变性
    String 不可变
    StringBuffer 和 StringBuilder 可变
  2. 线程安全
    String 不可变,因此是线程安全的
    StringBuilder 不是线程安全的
    StringBuffer 是线程安全的,内部使用 synchronized 进行同步
    StringBuilder 和StringBuffer 都继承了AbstractStringBuilder,本质上差不多,但是StringBuilder 因为少了synchronize同步因而速度更快,而StringBuffer 因为要做同步处理,加锁有开销,所以更慢。

你可能感兴趣的:(java基础)