找回密码
 立即注册
首页 业界区 业界 2.Java SDK源码分析系列笔记-String系列

2.Java SDK源码分析系列笔记-String系列

盒礁泅 2025-9-24 15:08:17
目录

  • 1. String

    • 1.1. 是什么
    • 1.2. 使用
    • 1.3. 源码分析

      • 1.3.1. 类的定义
      • 1.3.2. 构造方法

        • 1.3.2.1. 解释new String("test1") != new String("test1")

      • 1.3.3. 常量池

        • 1.3.3.1. 解释"test2"=="test2"

      • 1.3.4. equals方法
      • 1.3.5. toCharArray方法
      • 1.3.6. toString
      • 1.3.7. valueOf

        • 1.3.7.1. 解释String.valueOf("test3") == String.valueOf("test3")

      • 1.3.8. intern方法

        • 1.3.8.1. 解释new String("test4").intern() == "test4"

      • 1.3.9. subString

    • 1.4. 常见问题

      • 1.4.1. toString和valueOf的区别
      • 1.4.2. String的不可变性
      • 1.4.3. 线程安全
      • 1.4.4. String对+的重载
      • 1.4.5. replaceFirst、replaceAll、replace区别
      • 1.4.6. String s = new String("abc")创建了几个字符串对象


  • 2. StringBuilder

    • 2.1. 是什么
    • 2.2. 如何使用
    • 2.3. 原理分析

      • 2.3.1. 构造函数
      • 2.3.2. append方法
      • 2.3.3. toString
      • 2.3.4. subString


  • 3. StringBuffer

    • 3.1. 是什么
    • 3.2. 如何使用
    • 3.3. 原理分析

      • 3.3.1. 构造函数
      • 3.3.2. append方法
      • 3.3.3. toString
      • 3.3.4. subString


  • 4. StringBuilder vs StringBuffer vs String
  • 5. 参考链接

1. String

1.1. 是什么

不可变、线程安全的字符串
1.2. 使用
  1. public class StringTest
  2. {
  3.     public static void main(String[] args)
  4.     {
  5.         String val = new String("test1");
  6.         String val1 = new String("test1");
  7.         System.out.println(val == val1);//false。上面的代码会在堆中两块不同的地方创建字符串
  8.         String val2 = "test2";
  9.         String val3 = "test2";
  10.         System.out.println(val2 == val3);//true。上面的代码在编译期间已经确定,那么会把"test2"保存在常量池(不是堆中)
  11.         String val4 = "te" + "st2";
  12.         System.out.println(val2 == val4);//true。虽然val4是通过+拼接的,但是这个也是可以在编译期确定的,所以使用的仍是常量池中的字符串
  13.         String val5 = String.valueOf("test3");
  14.         String val6 = String.valueOf("test3");
  15.         System.out.println(val5 == val6);//true。"test3"在编译期间已经确定,放入常量池中。String.valueOf返回的是常量池中的字符串
  16.         String aa = new String("1111");
  17.         String bb = new String("1111");
  18.         String val9 = String.valueOf(aa);
  19.         String val10 = String.valueOf(bb);
  20.         System.out.println(val9 == val10);//false。两个"1111"分别在堆中创建,String.valueOf返回的是堆中不同的对象
  21.         String val7 = new String("test4");
  22.         String val8 = "test4";
  23.         String val7Intern = val7.intern();
  24.         System.out.println(val8 == val7);//false。val7在堆中,val8在常量池中,自然不相等
  25.         System.out.println(val8 == val7Intern);//true。intern方法的作用是在运行时往常量池中增加字符串,如果常量池池中已有,那么把常量池中的对象返回
  26.         System.out.println(val8 == val7);//false。再试验一次说明intern方法不是把堆中的地址塞到常量池中
  27.     }
  28. }
复制代码
1.3. 源码分析

1.3.1. 类的定义
  1. //final表示不能被继承
  2. public final class String
  3.         //可比较,可序列化
  4.     implements java.io.Serializable, Comparable<String>, CharSequence
  5. {
  6.     /** The value is used for character storage. */
  7.     //底层是通过char数组实现的,final表示引用不能修改,但并不表示char数组里的值不能修改
  8.     //那为什么String还是不可变的呢?因为String并没有提供修改value数组值的方法,所以自然就不可变
  9.     private final char value[];
  10.     /** Cache the hash code for the string */
  11.     private int hash; // Default to 0
  12. }
复制代码
String是不可变的

  • 类使用final修饰
  • 内部属性char value[]使用final修饰,说明引用不能改变
  • 且内部没有对外提供修改内部属性char value[]的方法
1.3.2. 构造方法
  1. //无参构造方法
  2. public String() {
  3.         //会创建一个空串
  4.     this.value = "".value;
  5. }
  6. //使用String构造
  7. public String(String original) {
  8.         //直接把引用指向同一个字符数组?因为String内部的char数组是不可以改变的,所以可以共享
  9.     this.value = original.value;
  10.     this.hash = original.hash;
  11. }
  12. //使用char数组构造
  13. public String(char value[]) {
  14.         //外部传递过来的char数组可能被改变,所有需要复制数组
  15.     this.value = Arrays.copyOf(value, value.length);
  16. }
  17. //使用StringBuffer构造
  18. public String(StringBuffer buffer) {
  19.         //线程安全的StringBUffer需要加锁并且复制数组
  20.     synchronized(buffer) {
  21.         this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
  22.     }
  23. }
  24. //使用StringBuilde构造
  25. public String(StringBuilder builder) {
  26.         //复制数组
  27.     this.value = Arrays.copyOf(builder.getValue(), builder.length());
  28. }
  29. //使用char数组带下标的构造
  30. public String(char value[], int offset, int count) {
  31.     if (offset < 0) {
  32.         throw new StringIndexOutOfBoundsException(offset);
  33.     }
  34.     if (count <= 0) {
  35.         if (count < 0) {
  36.             throw new StringIndexOutOfBoundsException(count);
  37.         }
  38.         if (offset <= value.length) {
  39.             this.value = "".value;
  40.             return;
  41.         }
  42.     }
  43.     // Note: offset or count might be near 1>>>1.
  44.     if (offset > value.length  count) {
  45.         throw new StringIndexOutOfBoundsException(offset + count);
  46.     }
  47.     //复制char数组
  48.     this.value = Arrays.copyOfRange(value, offset, offset+count);
  49. }
复制代码
1.3.2.1. 解释new String("test1") != new String("test1")
  1. String val = new String("test1");
  2. String val1 = new String("test1");
  3. System.out.println(val == val1);//false。上面的代码会在堆中两块不同的地方创建字符串
复制代码
我们查看字节码,结果如下:
1.png

调用的字节码时NEW,会在堆中创建字符串,所以两者不同
1.3.3. 常量池

英文名叫constant pool,指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。它包括了关于类、方法、接口等中的常量,也包括字符串常量
1.3.3.1. 解释"test2"=="test2"
  1. String val2 = "test2";
  2. String val3 = "test2";
  3. System.out.println(val2 == val3);//true。上面的代码在编译期间已经确定,那么会把"test2"保存在常量池(不是堆中)
  4. String val4 = "te" + "st2";
  5. System.out.println(val2 == val4);//true。虽然val4是通过+拼接的,但是这个也是可以在编译期确定的,所以使用的仍是常量池中的字符串
复制代码
我们查看字节码,结果如下:
2.png

可以看出上面三行都调用了LDC字节码,他表示在常量池中加载字符串,而"test2"这个字符串在编译器会存入.class文件中,因此三者相等
1.3.4. equals方法
  1. public boolean equals(Object anObject) {
  2.         //首先比较引用是否相等
  3.     if (this == anObject) {
  4.         return true;
  5.     }
  6.     //如果是个字符串
  7.     if (anObject instanceof String) {
  8.         String anotherString = (String)anObject;
  9.         int n = value.length;
  10.             //字符数组长度相等
  11.         if (n == anotherString.value.length) {
  12.             char v1[] = value;
  13.             char v2[] = anotherString.value;
  14.             int i = 0;
  15.                 //从后往前比较value是否相等
  16.             while (n != 0) {
  17.                 if (v1[i] != v2[i])
  18.                     return false;
  19.                 i++;
  20.             }
  21.             return true;
  22.         }
  23.     }
  24.     return false;
  25. }
复制代码
1.3.5. toCharArray方法
  1. public char[] toCharArray() {
  2.     // Cannot use Arrays.copyOf because of class initialization order issues
  3.     //创建一个新的char数组
  4.     char result[] = new char[value.length];
  5.     //调用arraycopy函数把value的值复制到新的char数组返回(防止外界改变char数组的值)
  6.     System.arraycopy(value, 0, result, 0, value.length);
  7.     return result;
  8. }
复制代码
1.3.6. toString
  1. public String toString() {
  2.         //直接返回自己
  3.     return this;
  4. }
复制代码
1.3.7. valueOf
  1. public static String valueOf(Object obj) {
  2.         //为null的话返回“null”,否则调用obj的toString
  3.     return (obj == null) ? "null" : obj.toString();
  4. }
复制代码
1.3.7.1. 解释String.valueOf("test3") == String.valueOf("test3")
  1. String val5 = String.valueOf("test3");
  2. String val6 = String.valueOf("test3");
  3. System.out.println(val5 == val6);//true。"test3"在编译期间已经确定,放入常量池中。String.valueOf返回的是常量池中的字符串
复制代码
3.png

对于String val5 = String.valueOf("test3")这种代码,编译器首先会把他当作String val5 = "test3"处理,把"test3"放入常量池中,然后调用String.valueOf方法返回常量池中的"test3"字符串,所以两者相等。

  • 再看一个例子
  1. String aa = new String("1111");
  2. String bb = new String("1111");
  3. String val9 = String.valueOf(aa);
  4. String val10 = String.valueOf(bb);
  5. System.out.println(val9 == val10);//false。两个"1111"分别在堆中创建,String.valueOf返回的是堆中不同的对象
复制代码
4.png

String aa = new String("1111")这种先在堆中创建字符串"1111",然后String val9 = String.valueOf(aa)返回的是堆中的字符串,所以两者不等
1.3.8. intern方法
  1. //运行时往常量池增加字符串
  2. //调用intern方法的时候,如果常量池中已经存在一个字符串与这个字符串相等,那么返回常量池的中字符串。
  3. //没有的话会在常量池中创建这个字符串,然后才返回。
  4. public native String intern();
复制代码
1.3.8.1. 解释new String("test4").intern() == "test4"
  1. String val7 = new String("test4");
  2. String val8 = "test4";
  3. String val7Intern = val7.intern();
  4. System.out.println(val8 == val7);//false。val7在堆中,val8在常量池中,自然不相等
  5. System.out.println(val8 == val7Intern);//true。intern方法的作用是在运行时往常量池中增加字符串,如果常量池池中已有,那么把常量池中的对象返回
  6. System.out.println(val8 == val7);//false。再试验一次说明intern方法不是把堆中的地址塞到常量池中
复制代码
5.png

String val7 = new String("test4")是堆中的字符串"test4",String val8 = "test4"是常量池中的"test4",String val7Intern = val7.intern()intern首先检查常量池中是否有"test4",发现有直接返回
1.3.9. subString
  1. String substring(int beginIndex, int endIndex) {
  2.         //下标越界判断
  3.     if (beginIndex < 0) {
  4.         throw new StringIndexOutOfBoundsException(beginIndex);
  5.     }
  6.     if (endIndex > value.length) {
  7.         throw new StringIndexOutOfBoundsException(endIndex);
  8.     }
  9.     int subLen = endIndex  beginIndex;
  10.     if (subLen < 0) {
  11.         throw new StringIndexOutOfBoundsException(subLen);
  12.     }
  13.     //返回自己或者调用使用char数组带下标的构造函数
  14.     return ((beginIndex == 0) && (endIndex == value.length)) ? this
  15.             : new String(value, beginIndex, subLen);
  16. }
复制代码
1.4. 常见问题

1.4.1. toString和valueOf的区别
  1. String aa = null;
  2. //System.out.println(aa.toString());//抛出异常
  3. System.out.println(String.valueOf(aa));//null
复制代码
前者没有做为空判断,后者做了。
1.4.2. String的不可变性

String这个类是由final修饰的,意味着不能被继承
String内部通过char数组实现,而这个数组是用final修饰的。意味着一旦赋值就不能改变引用,而且String也没有提供修改字符数组内容的方法
用下面的例子解释:
  1. String a = "aaa";
  2. a = "bbb";//这里的可变String类型的引用改变了,但是原有的值没有变化
  3. //这种看似修改的方法实际上返回的是一个新的String对象
  4. String c= a.subString(1,2);
复制代码
6.png

1.4.3. 线程安全

因为不可变所以线程安全
  1. public class TestString
  2. {
  3.     public static void main(String[] args) throws InterruptedException
  4.     {
  5.         String string = "0";
  6.         TestThread testThread = new TestThread(string);//因为不可变,所以传递进去无论做了什么操作都不影响
  7.         testThread.start();
  8.         testThread.join();
  9.         System.out.println(string);//0
  10.     }
  11. }
  12. class TestThread extends Thread
  13. {
  14.     private String string;
  15.     public TestThread(String string)
  16.     {
  17.         this.string = string;
  18.     }
  19.     @Override
  20.     public void run()
  21.     {
  22.         this.string += "test";
  23.         System.out.println(Thread.currentThread().getName() + ":" + this.string);
  24.     }
  25. }
复制代码
1.4.4. String对+的重载

实际上使用的StringBuilder,并且调用append方法,最后调用toString方法

  • 普通+
    7.png

  • 循环+
    8.png

1.4.5. replaceFirst、replaceAll、replace区别


  • String replaceFirst(String regex, String replacement)
    基于正则的替换,替换第一个
  • String replaceAll(String regex, String replacement)
    基于正则的替换,替换全部
  • String replace(Char Sequencetarget, Char Sequencereplacement)
    普通的比较替换,替换全部
1.4.6. String s = new String("abc")创建了几个字符串对象


  • 当加载类时,"abc"被创建并驻留在了字符创常量池中(如果先前加载中没有创建驻留过)。
  • 当执行此句时,因为"abc"对应的String实例已经存在于字符串常量池中,所以JVM会将此实例复制到会在堆(heap)中并返回引用地址
2. StringBuilder

2.1. 是什么

线程安全的、可变字符串
其实就是在StringBuilder的基础上加了synchronized关键字
2.2. 如何使用
  1. public class TestStringBuilder
  2. {
  3.     public static void main(String[] args) throws InterruptedException
  4.     {
  5.         StringBuffer stringBuffer = new StringBuffer();
  6.         Thread thread1 = new Thread(() -> {
  7.             for (int i = 0; i < 5000; i++)
  8.             {
  9.                 stringBuffer.append("aaaa");
  10.             }
  11.         });
  12.         Thread thread2 = new Thread(() -> {
  13.             for (int i = 0; i < 5000; i++)
  14.             {
  15.                 stringBuffer.append("aaaa");
  16.             }
  17.         });
  18.         thread1.start();
  19.         thread2.start();
  20.         thread1.join();
  21.         thread2.join();
  22.         System.out.println(stringBuffer.toString());
  23.         System.out.println(stringBuffer.length() == 5000 * 2 * 4);//true
  24.     }
  25. }
复制代码
2.3. 原理分析

2.3.1. 构造函数
  1. public final class StringBuffer//一样是final的
  2.     extends AbstractStringBuilder
  3.     implements java.io.Serializable, CharSequence
  4. {
  5.         public StringBuffer() {
  6.                 //跟StringBuilder一样调用AbstractStringBuilder的构造方法
  7.                 super(16);//默认容量16个
  8.         }
  9. }
  10. abstract class AbstractStringBuilder implements Appendable, CharSequence {
  11.    
  12.     char[] value;
  13.     int count;
  14.    
  15.         AbstractStringBuilder(int capacity) {
  16.                 value = new char[capacity];
  17.         }
  18. }
复制代码
2.3.2. append方法
  1. //加了synchronized修饰
  2. public synchronized StringBuffer append(String str) {
  3.     toStringCache = null;
  4.     super.append(str);
  5.     return this;
  6. }
复制代码
2.3.3. toString
  1. //加了synchronized修饰
  2. public synchronized String toString() {
  3.     if (toStringCache == null) {
  4.         toStringCache = Arrays.copyOfRange(value, 0, count);
  5.     }
  6.     return new String(toStringCache, true);
  7. }
复制代码
2.3.4. subString
  1. public synchronized String substring(int start, int end) {
  2.         return super.substring(start, end);
  3. }
复制代码
3. StringBuffer

3.1. 是什么

线程安全的、可变字符串
其实就是在StringBuilder的基础上加了synchronized关键字
3.2. 如何使用
  1. public class TestStringBuilder
  2. {
  3.     public static void main(String[] args) throws InterruptedException
  4.     {
  5.         StringBuffer stringBuffer = new StringBuffer();
  6.         Thread thread1 = new Thread(() -> {
  7.             for (int i = 0; i < 5000; i++)
  8.             {
  9.                 stringBuffer.append("aaaa");
  10.             }
  11.         });
  12.         Thread thread2 = new Thread(() -> {
  13.             for (int i = 0; i < 5000; i++)
  14.             {
  15.                 stringBuffer.append("aaaa");
  16.             }
  17.         });
  18.         thread1.start();
  19.         thread2.start();
  20.         thread1.join();
  21.         thread2.join();
  22.         System.out.println(stringBuffer.toString());
  23.         System.out.println(stringBuffer.length() == 5000 * 2 * 4);//true
  24.     }
  25. }
复制代码
3.3. 原理分析

3.3.1. 构造函数
  1. public final class StringBuffer//一样是final的
  2.     extends AbstractStringBuilder
  3.     implements java.io.Serializable, CharSequence
  4. {
  5.         public StringBuffer() {
  6.                 //跟StringBuilder一样调用AbstractStringBuilder的构造方法
  7.                 super(16);//默认容量16个
  8.         }
  9. }
  10. abstract class AbstractStringBuilder implements Appendable, CharSequence {
  11.    
  12.     char[] value;
  13.     int count;
  14.    
  15.         AbstractStringBuilder(int capacity) {
  16.                 value = new char[capacity];
  17.         }
  18. }
复制代码
3.3.2. append方法
  1. //加了synchronized修饰
  2. public synchronized StringBuffer append(String str) {
  3.     toStringCache = null;
  4.     super.append(str);
  5.     return this;
  6. }
复制代码
3.3.3. toString
  1. //加了synchronized修饰
  2. public synchronized String toString() {
  3.     if (toStringCache == null) {
  4.         toStringCache = Arrays.copyOfRange(value, 0, count);
  5.     }
  6.     return new String(toStringCache, true);
  7. }
复制代码
3.3.4. subString
  1. public synchronized String substring(int start, int end) {
  2.         return super.substring(start, end);
  3. }
复制代码
4. StringBuilder vs StringBuffer vs String

StringStringBufferStringBuilder是否线程安全√√×是否可变×√√5. 参考链接


  • Java 源码分析 — String 的设计 - 掘金
  • String源码分析 - 掘金
  • Java中String对"+"的"重载" - 掘金
  • java 中为什么说,String是线程安全的?为什么说StringBuilder是线程不安全的?分别举例证明。 - OSCHINA
  • 为什么String被设计为不可变?是否真的不可变? - Jessica程序猿 - 博客园
  • Java提高篇——理解String 及 String.intern() 在实际中的应用 - 萌小Q - 博客园

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册