Java基础疑点解析 String(一系列难点)

本文深入解析Java中的String类,包括其不变性设计、安全与性能考量、String常量池的工作原理以及intern方法的作用。此外,还探讨了String与StringBuffer、StringBuilder的区别,强调在不同场景下的最佳实践。
摘要由CSDN通过智能技术生成

Java基础疑点解析 String(一系列难点)
1、String底层源码分析

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence,
               Constable, ConstantDesc {
               
    @Stable
    private final byte[] value;

    private final byte coder;

    private int hash; // Default to 0

    private boolean hashIsZero; // Default to false;

    @java.io.Serial
    private static final long serialVersionUID = -6849794470754667710L;

    static final boolean COMPACT_STRINGS;

    static {
        COMPACT_STRINGS = true;
    }

    @java.io.Serial
    private static final ObjectStreamField[] serialPersistentFields =
        new ObjectStreamField[0];
        
    public String() {
        this.value = "".value;
        this.coder = "".coder;
    }
    
    @HotSpotIntrinsicCandidate
    public String(String original) {
        this.value = original.value;
        this.coder = original.coder;
        this.hash = original.hash;
    }

上面是String底层源码,可以看出:
1、String的不变性指的是类值一旦被初始化,就不能被改变。我们从源码出发,可以看到String类由final修饰,即类不能被继承,String中的方法不能被继承重写。
2、String类实现了Serializable,Comparable,CharSequence 接口
3.string 源码中包含一个 不可变(final修饰的)的byte数组来存放字符串数据 private final byte value[];, 说明String它是一个不可变字符串,底层是一个char(byte)类型的数组。

2、String被设计成不可变的主要目的
String被设计成不可变的主要目的是为了安全和高效。
第一,安全方面。String中有些方法比较底层,如果被继承并重写方法会有隐患;同时不可变对象一定是线程安全的,可以在线程之间共享,无需同步。
第二,设计者希望用户用到的String就是JDK中的String,不希望被继承和重写,确保String可控。
第三,性能方面。基于不变性,编译器和JVM可以对String进行优化,提高性能。
String是不可变的,其hashcode值也是固定不变的。String通过hash来缓存当前字符串的hashcode值,保证只需要计算一次。源码如下:

private int hash; // Default to 0
public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        char val[] = value;
        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}

方法中首先对hash属性值做了判断,如果计算过hashCode,结果会被缓存到hash属性,不需要重复计算。
**3、String常量池、intern方法

// StringTable [ "a", "b" ,"ab" ]  hashtable 结构,不能扩容
public class StringTest {
        // 常量池中的信息,都会被加载到运行时常量池中, 这时 a b ab 都是常量池中的符号,还没有变为 java 字符串对象
        // ldc #7 会把 a 符号变为 "a" 字符串对象
        // ldc #9 会把 b 符号变为 "b" 字符串对象
        // ldc #11 会把 ab 符号变为 "ab" 字符串对象
        public static void main(String[] args) {
            String s1 = "a"; // 懒惰的
            String s2 = "b";
            String s3 = "ab";
            String s4 = s1 + s2; //底层是 new StringBuilder().append("a").append("b").toString()  new String("ab")

            System.out.println(s3 == s4);//flase
            System.out.println(s3.equals(s4));//true
            
         
    }
}

输出的是 false,true,因为s3被分配到字符串常量池中,而s4是最后new String(“ab”),它是被分配到堆内存中。两者尽管数据一样,但内存地址不一样,是不同的对象。所以为false。而equals方法比较的是两个字符串“是不是包含相同的数据”,所以是true。

// StringTable [ "a", "b" ,"ab" ]  hashtable 结构,不能扩容
public class StringTest {
        public static void main(String[] args) {
            String s1 = "a"; // 懒惰的
            String s2 = "b";
            String s3 = "ab";
            String s4 = s1 + s2; // new StringBuilder().append("a").append("b").toString()  new String("ab")
            String s5="a"+"b";

            System.out.println(s3 == s4);//false
            System.out.println(s3 == s5);//true
    }
}

输出的是false,true,因为这是两个常量”a”、”b”进行拼接,所以javac在编译期间的优化,结果已经在编译期确定为“ab”,它会到串池中直接查询”ab”,所以s3和s5是同一个对象,而s4是两个变量进行拼接,所以它最后是在堆内存进行创建对象。

// StringTable [ "a", "b" ,"ab" ]  hashtable 结构,不能扩容
public class StringTest {
        public static void main(String[] args) {
            String s1 = "a"; // 懒惰的
            String s2 = "b";
            String s3 = "ab";
            String s4 = s1 + s2; // new StringBuilder().append("a").append("b").toString()  new String("ab")
            String s5="a"+"b";
            //System.out.println(s3 == s4);
            //System.out.println(s3 == s5);
            String s = s4.intern();
            System.out.println(s3==s);//true
            System.out.println(s3==s4);//false
        }
}
// StringTable [ "a", "b" ,"ab" ]  hashtable 结构,不能扩容
public class StringTest {
        public static void main(String[] args) {
            String s1 = "a"; // 懒惰的
            String s2 = "b";
            String s4 = s1 + s2; // new StringBuilder().append("a").append("b").toString()  new String("ab")
            //System.out.println(s3 == s4);
            //System.out.println(s3 == s5);
            String s = s4.intern();
            String s3 = "ab";
            System.out.println(s3==s);//true
            System.out.println(s3==s4);//true
        }
}

使用intern()方法,jdk1.8以后,会将将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池,无论放没放入,都会把串池中的对象返回。
总结:StringTable 特性
常量池中的字符串仅是符号,第一次用到时才变为对象,利用串池的机制,来避免重复创建字符串对象
字符串变量拼接的原理是 StringBuilder (1.8)
字符串常量拼接的原理是编译期优化
可以使用 intern 方法,主动将串池中还没有的字符串对象放入串池
1.8 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串池中的对象返回
1.6 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有会把此对象复制一份,放入串池, 会把串池中的对象返回

3、String,StringBuffer,StringBuilder区别
String 跟其他两个类的区别是
String是final类型,每次声明的都是不可变的对象,
所以每次操作都会产生新的String对象,然后将指针指向新的String对象。
StringBuffer,StringBuilder都是在原有对象上进行操作

所以,如果需要经常改变字符串内容,则建议采用这两者。
StringBuffer vs StringBuilder
前者是线程安全的,后者是线程不安全的。
线程不安全性能更高,所以在开发中,优先采用StringBuilder.
StringBuilder > StringBuffer > String

参考资料:
https://zhuanlan.zhihu.com/p/58171343
黑马程序员JVM笔记

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值