一文说透String

89 阅读4分钟

一.首先说说String的不可变性

String被设计为不可变的 就保证了线程安全 因为不可变意味着是不可修改的 不可修改在多线程环境下就是安全的 不存在共享变量被多个线程修改的风险

String的不可变体现在以下几个方面:

1.首先String是final修饰的 String类本身是不可被继承和重写的

2.String存储字符串值的数组是final的 意味着一旦创建就不可能修改该数组引用指向的地址

且该变量被声明为private的 并且未暴露修改该变量的方法 这就意味着这个数组存储内容是不可能被外部改变的

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

3.String的变量被修改的时候 会创建新的String 而不会修改原本的那个对象

4.要明白之所以能在字符串常量池中复用字符串 这也正是因为String的不可变性才造就了这一特性 极大提升性能

二.String的intern方法

1.String.intern

调用这个方法有两种情况:

如果字符串已经在常量池中存在 则返回该字符串的引用地址

如果字符串并不存在则在池中创建一个 并返回指向该字符串的引用

以下是jdk源码关于这个方法的权威注释:

  * When the intern method is invoked, if the pool already contains a
     * string equal to this {@code String} object as determined by
     * the {@link #equals(Object)} method, then the string from the pool is
     * returned. Otherwise, this {@code String} object is added to the
     * pool and a reference to this {@code String} object is returned.
     * <p>
     * It follows that for any two strings {@code s} and {@code t},
     * {@code s.intern() == t.intern()} is {@code true}
     * if and only if {@code s.equals(t)} is {@code true}.
     * <p>
     * All literal strings and string-valued constant expressions are
     * interned. String literals are defined in section 3.10.5 of the
     * <cite>The Java&trade; Language Specification</cite>.
     *
     * @return  a string that has the same contents as this string, but is
     *          guaranteed to be from a pool of unique strings.
     */
    public native String intern();

看看一个小测试:

// 在堆中创建字符串对象”Java“
// 将字符串对象”Java“的引用保存在字符串常量池中
String s1 = "Java";
// 直接返回字符串常量池中字符串对象”Java“对应的引用
String s2 = s1.intern();
// 会在堆中在单独创建一个字符串对象
String s3 = new String("Java");
// 直接返回字符串常量池中字符串对象”Java“对应的引用
String s4 = s3.intern();
// s1 和 s2 指向的是堆中的同一个对象
System.out.println(s1 == s2); // true
// s3 和 s4 指向的是堆中不同的对象
System.out.println(s3 == s4); // false
// s1 和 s4 指向的是堆中的同一个对象
System.out.println(s1 == s4); //true

三.String用法推荐

如果用到很多字符串拼接 则不推荐使用+来连接多个字符串 本质上底层字节码还需要循环创建多个SringBuilder

String str1 = "he";
String str2 = "llo";
String str3 = "world";
String str4 = str1 + str2 + str3;

字节码:

1.单线程使用StringBuilder

2.多线程使用StringBuffer来保证线程安全

四.Java9之后改了底层存储

从char[]数组改为了byte[]数组实现

原因是byte占一个字节 char占两个字节

节省了一半内存

新版的String支持两种编码格式 Latin-1 和UTF-16

如果是前者就占用一个字节 后者占用两个字节

五.常量折叠

对于编译器已经确定值的字符串 会被当做常量放入字符串常量池

并且字符串常量的拼接结果在编译器就被保存在字符串常量池 这得益于编译器优化

在编译过程中,Javac 编译器(下文中统称为编译器)会进行一个叫做 常量折叠(Constant Folding) 的代码优化

常量折叠会把常量表达式的值求出来作为常量嵌在最终生成的代码中,这是 Javac 编译器会对源代码做的极少量优化措施之一(代码优化几乎都在即时编译器中进行)。

对于 String str3 = "str" + "ing"; 编译器会给你优化成 String str3 = "string";

并不是所有的常量都会进行折叠,只有编译器在程序编译期就可以确定值的常量才可以:

1.基本数据类型和字符串常量

2.被final修饰的字符串变量

3.字符串通过 “+”拼接得到的字符串、基本数据类型之间算数运算(加减乘除)、基本数据类型的位运算(<<、>>、>>> )