String 类

2019-06-01  本文已影响0人  gczxbb

一、不可变

String 是不可变类,不可变的意思是 String 类型变量初始化后,其引用指向内存内容不能改变,变量引用可以指向其他内存。

String str = "abc";
str = "def";
字符串引用改变地址

定义一个 String 变量 str,引用指向内存字符串 abc。
变量赋值时,新开辟内存 def 字符串,str 引用指向新对象,原内存内容 abc 不变。

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    private final char value[];
    ...
}

String 类是一个字符串数组的封装类(内部一个 char[] 数组)。数组类型 final private,引用 value 值不可变,外部无法访问。

String 对象内存

因此,String 对象本质是指向一个 char 数组内存的引用,设计成 final 不可变类型,一旦 String 对象创建,value 值初始化,指向一块字符数组内存,char[] 引用不可变,String 对象即不可改变。

二、replace() 方法

替换字符串中的某个字符,String 类 replace() 方法,不直接更改 char[] 引用指向内存,而是开辟一块新内存。

public String replace(char oldChar, char newChar) {
    if (oldChar != newChar) {
        int len = value.length;
        int i = -1;
        char[] val = value; /* avoid getfield opcode */

        while (++i < len) {
            if (val[i] == oldChar) {
                break;
            }
        }
        if (i < len) {
            char buf[] = new char[len];
            for (int j = 0; j < i; j++) {
                buf[j] = val[j];
            }
            while (i < len) {
                char c = val[i];
                buf[i] = (c == oldChar) ? newChar : c;
                i++;
            }
            return new String(buf, true);
        }
    }
    return this;
}

创建新 char[] 数组,分配内存,长度和老数组一致,复制,替换,new 一个新 String 类对象,在构造方法,新 char[] 数组赋值 String 类内部 value,返回新 String 引用。

三、常量池

class 文件常量池,在文件中,编译器编译生成字面量和符号引用,未载入内存,字面量是文本字符串,(如 String str = "abc" 中的 abc)。
符号引用是类/接口全限定名,(如 java/lang/String ),变量名称( str ),方法名称和描述符(参数和返回值)。
类加载内存后,class 文件常量池(字面量和符号引用),进入方法区运行时常量池,该常量池区全局共享。
字面量(字符串常量池), jdk1.7 后不再方法区,移到堆中,符号引用如方法名、类全限定名仍然在方法区。

public class StrClass {
    public String a = "hello2";   
    public String a1 = "hello2";   
    public String a2 = "hello"+2;   
}

定义一个 String 变量 a,编译后 hello2 是文本字符串,在 class 文件常量池,编译阶段确定 a 的值。
两个字符串字面值,编译时会进行优化(拼接),解析成一个,所以 a2 在编译期由编译器直接解析为 hello2。
反编译 javap -verbose StrClass.class 命令,查看 class 文件常量池。

Constant pool

编译时会检查常量池是否已存在 hello2 字符串,只有一个 #2,String,对应 #22,即 hello2。

class code

类加载内存时

(运行时)在对象初始化阶段,初始化 Code,在常量池中 ldc 获取第 #2 项( hello2 字符串对象),putfield 将引用入栈,分别是 a(#3),a1(#4) 和 a2(#5),它们指向常量池中相同的对象 hello2,(a==a1==a2)。

此过程会查找字符串常量池是否存在 hello2,若不存在,在堆创建 char[] 数组,创建 String 对象关联 char[] 数组,保存到字符串常量池,最后将a指向这个对象。

public class StrClass {
    public String a = "hello2";  
 
    public String b = "hello"; 
    public String a3 = b + 2;   

    public final String c = "hello"; 
    public String a4 = c + 2; 
}

编译阶段,不能确定 a3 的值,定义 final 变量 c,字节码替换掉 a4 中的 c 变量,场景和 a2 一致。

class code

(运行时)对象变量初始化,new 一个 StringBuilder 对象,a3 引用指向 toString() 方法在堆内存 new 的 String 对象。a==a4,指向字符串常量池,a3 指向堆内存 new 的 String 对象。

public class StrClass {
    public String a = "hello2";     
    public String a5 = new String("hello2"); 
}

类加载时,在常量池创建对象 hello2,变量 a5,运行时堆内存 new一个 String 对象,字符串 hello2 已经在常量池,#2项,a 引用指向字符串常量池,a5 引用指向堆内存新对象,(a!=a5)。

new 关键字创建字符串对象, 在堆内存创建。
查找常量池是否存在,若没有,创建一个字符串对象,先放入常量池,然后再堆中创建对象,返回堆中的地址,因此,如果常量池中原来没有,会产生两个对象,否则,产生一个对象(堆中)。

public class StrClass { 
    public String a6 = new String("hello2"); 
}

class 文件常量池,hello2 文本字符。

Constant pool

类加载内存时,在字符串常量池创建一个 hello2 字符串对象。

初始化Code

对象初始化时,new 指令,在堆中再次创建一个对象,变量 a6 引用指向它。

public class StrClass { 
    public String a7 = new String("hello")+new String("2");
}

class 文件常量池只有 hello 和 2 字符串,没有 hello2 字符串,当类加载时,在字符串常量池不存在 hello2 对象。
初始化时,new 指令在堆创建两个 String 对象( hello和2 ),通过 StringBuffer 类 append() 方法,toString() 方法在堆内存中 new 一个 String 对象 (hello2),a7 引用指向它。

四、StringBuffer 和 StringBuilder

前一节的变量 a3=b+2 赋值时,class 字节码中定义了一个 StringBuilder 类,调用两次 append() 方法,依次添加 b 和 2 ,即 hello 和 2,一次 toString() 方法,堆内存创建对象。
StringBuffer 和 StringBuilder 区别是线程安全。

StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("hello");
stringBuffer.append(2);

StringBuffer 通过 char[] 数组保存数据,每一个 append() 方法的新增数据在 char[] 数组保存,支持不同类型,boolean 类型保存4或5个字符 (true/false),字符串将每个字符保存,StringBuffer 类可以对字符串进行修改,进行字符串拼接时,不会产生新对象,直接对 char[] 数组进行操作更改。

public synchronized StringBuffer append(String str) {
    super.append(str);
    return this;
}

几乎所有的字符操作方法都 synchronized 同步,该类线程安全。

@Override
public synchronized String toString() {
    if (toStringCache == null) {
        toStringCache = Arrays.copyOfRange(value, 0, count);
    }
    return new String(toStringCache, true);
}

toString() 方法,创建一个 String 对象,关联 char[] 数组。


任重而道远

上一篇下一篇

猜你喜欢

热点阅读