汐汐诺诺仪仪 發表於 2025-6-28 13:26:00

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

<p></p><div class="toc"><div class="toc-container-header">目录</div><ul><li>1. String<ul><li>1.1. 是什么</li><li>1.2. 使用</li><li>1.3. 源码分析<ul><li>1.3.1. 类的定义</li><li>1.3.2. 构造方法<ul><li>1.3.2.1. 解释new String("test1") != new String("test1")</li></ul></li><li>1.3.3. 常量池<ul><li>1.3.3.1. 解释"test2"=="test2"</li></ul></li><li>1.3.4. equals方法</li><li>1.3.5. toCharArray方法</li><li>1.3.6. toString</li><li>1.3.7. valueOf<ul><li>1.3.7.1. 解释String.valueOf("test3") == String.valueOf("test3")</li></ul></li><li>1.3.8. intern方法<ul><li>1.3.8.1. 解释new String("test4").intern() == "test4"</li></ul></li><li>1.3.9. subString</li></ul></li><li>1.4. 常见问题<ul><li>1.4.1. toString和valueOf的区别</li><li>1.4.2. String的不可变性</li><li>1.4.3. 线程安全</li><li>1.4.4. String对+的重载</li><li>1.4.5. replaceFirst、replaceAll、replace区别</li><li>1.4.6. String s = new String("abc")创建了几个字符串对象</li></ul></li></ul></li><li>2. StringBuilder<ul><li>2.1. 是什么</li><li>2.2. 如何使用</li><li>2.3. 原理分析<ul><li>2.3.1. 构造函数</li><li>2.3.2. append方法</li><li>2.3.3. toString</li><li>2.3.4. subString</li></ul></li></ul></li><li>3. StringBuffer<ul><li>3.1. 是什么</li><li>3.2. 如何使用</li><li>3.3. 原理分析<ul><li>3.3.1. 构造函数</li><li>3.3.2. append方法</li><li>3.3.3. toString</li><li>3.3.4. subString</li></ul></li></ul></li><li>4. StringBuilder vs StringBuffer vs String</li><li>5. 参考链接</li></ul></div><p></p>
<h1 id="1-string">1. String</h1>
<h2 id="11-是什么">1.1. 是什么</h2>
<p>不可变、线程安全的字符串</p>
<h2 id="12-使用">1.2. 使用</h2>
<pre><code class="language-java">public class StringTest
{
    public static void main(String[] args)
    {
      String val = new String("test1");
      String val1 = new String("test1");
      System.out.println(val == val1);//false。上面的代码会在堆中两块不同的地方创建字符串

      String val2 = "test2";
      String val3 = "test2";
      System.out.println(val2 == val3);//true。上面的代码在编译期间已经确定,那么会把"test2"保存在常量池(不是堆中)

      String val4 = "te" + "st2";
      System.out.println(val2 == val4);//true。虽然val4是通过+拼接的,但是这个也是可以在编译期确定的,所以使用的仍是常量池中的字符串

      String val5 = String.valueOf("test3");
      String val6 = String.valueOf("test3");
      System.out.println(val5 == val6);//true。"test3"在编译期间已经确定,放入常量池中。String.valueOf返回的是常量池中的字符串


      String aa = new String("1111");
      String bb = new String("1111");
      String val9 = String.valueOf(aa);
      String val10 = String.valueOf(bb);
      System.out.println(val9 == val10);//false。两个"1111"分别在堆中创建,String.valueOf返回的是堆中不同的对象

      String val7 = new String("test4");
      String val8 = "test4";
      String val7Intern = val7.intern();
      System.out.println(val8 == val7);//false。val7在堆中,val8在常量池中,自然不相等
      System.out.println(val8 == val7Intern);//true。intern方法的作用是在运行时往常量池中增加字符串,如果常量池池中已有,那么把常量池中的对象返回
      System.out.println(val8 == val7);//false。再试验一次说明intern方法不是把堆中的地址塞到常量池中
    }
}
</code></pre>
<h2 id="13-源码分析">1.3. 源码分析</h2>
<h3 id="131-类的定义">1.3.1. 类的定义</h3>
<pre><code class="language-java">//final表示不能被继承
public final class String
        //可比较,可序列化
    implements java.io.Serializable, Comparable&lt;String&gt;, CharSequence
{
    /** The value is used for character storage. */
    //底层是通过char数组实现的,final表示引用不能修改,但并不表示char数组里的值不能修改
    //那为什么String还是不可变的呢?因为String并没有提供修改value数组值的方法,所以自然就不可变
    private final char value[];

    /** Cache the hash code for the string */
    private int hash; // Default to 0
}
</code></pre>
<p>String是不可变的</p>
<ul>
<li>类使用final修饰</li>
<li>内部属性char value[]使用final修饰,说明引用不能改变</li>
<li>且内部没有对外提供修改内部属性char value[]的方法</li>
</ul>
<h3 id="132-构造方法">1.3.2. 构造方法</h3>
<pre><code class="language-java">//无参构造方法
public String() {
        //会创建一个空串
    this.value = "".value;
}

//使用String构造
public String(String original) {
        //直接把引用指向同一个字符数组?因为String内部的char数组是不可以改变的,所以可以共享
    this.value = original.value;
    this.hash = original.hash;
}

//使用char数组构造
public String(char value[]) {
        //外部传递过来的char数组可能被改变,所有需要复制数组
    this.value = Arrays.copyOf(value, value.length);
}

//使用StringBuffer构造
public String(StringBuffer buffer) {
        //线程安全的StringBUffer需要加锁并且复制数组
    synchronized(buffer) {
      this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
    }
}
//使用StringBuilde构造
public String(StringBuilder builder) {
        //复制数组
    this.value = Arrays.copyOf(builder.getValue(), builder.length());
}

//使用char数组带下标的构造
public String(char value[], int offset, int count) {
    if (offset &lt; 0) {
      throw new StringIndexOutOfBoundsException(offset);
    }
    if (count &lt;= 0) {
      if (count &lt; 0) {
            throw new StringIndexOutOfBoundsException(count);
      }
      if (offset &lt;= value.length) {
            this.value = "".value;
            return;
      }
    }
    // Note: offset or count might be near 1&gt;&gt;&gt;1.
    if (offset &gt; value.lengthcount) {
      throw new StringIndexOutOfBoundsException(offset + count);
    }

    //复制char数组
    this.value = Arrays.copyOfRange(value, offset, offset+count);
}
</code></pre>
<h4 id="1321-解释new-stringtest1--new-stringtest1">1.3.2.1. 解释new String("test1") != new String("test1")</h4>
<pre><code class="language-java"> String val = new String("test1");
String val1 = new String("test1");
System.out.println(val == val1);//false。上面的代码会在堆中两块不同的地方创建字符串
</code></pre>
<p>我们查看字节码,结果如下:<br>
<img src="https://raw.githubusercontent.com/TDoct/images/master/img/20200119171523.png"><br>
调用的字节码时NEW,会在堆中创建字符串,所以两者不同</p>
<h3 id="133-常量池">1.3.3. 常量池</h3>
<p>英文名叫constant pool,指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。它包括了关于类、方法、接口等中的常量,也包括字符串常量</p>
<h4 id="1331-解释test2test2">1.3.3.1. 解释"test2"=="test2"</h4>
<pre><code class="language-java">String val2 = "test2";
String val3 = "test2";
System.out.println(val2 == val3);//true。上面的代码在编译期间已经确定,那么会把"test2"保存在常量池(不是堆中)

String val4 = "te" + "st2";
System.out.println(val2 == val4);//true。虽然val4是通过+拼接的,但是这个也是可以在编译期确定的,所以使用的仍是常量池中的字符串
</code></pre>
<p>我们查看字节码,结果如下:<br>
<img src="https://raw.githubusercontent.com/TDoct/images/master/img/20200119172725.png"><br>
可以看出上面三行都调用了<code>LDC</code>字节码,他表示在常量池中加载字符串,而<code>"test2"</code>这个字符串在编译器会存入.class文件中,因此三者相等</p>
<h3 id="134-equals方法">1.3.4. equals方法</h3>
<pre><code class="language-java">public boolean equals(Object anObject) {
        //首先比较引用是否相等
    if (this == anObject) {
      return true;
    }
    //如果是个字符串
    if (anObject instanceof String) {
      String anotherString = (String)anObject;
      int n = value.length;
            //字符数组长度相等
      if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
              //从后往前比较value是否相等
            while (n != 0) {
                if (v1 != v2)
                  return false;
                i++;
            }
            return true;
      }
    }
    return false;
}

</code></pre>
<h3 id="135-tochararray方法">1.3.5. toCharArray方法</h3>
<pre><code class="language-java">public char[] toCharArray() {
    // Cannot use Arrays.copyOf because of class initialization order issues
    //创建一个新的char数组
    char result[] = new char;
    //调用arraycopy函数把value的值复制到新的char数组返回(防止外界改变char数组的值)
    System.arraycopy(value, 0, result, 0, value.length);
    return result;
}

</code></pre>
<h3 id="136-tostring">1.3.6. toString</h3>
<pre><code class="language-java">public String toString() {
        //直接返回自己
    return this;
}
</code></pre>
<h3 id="137-valueof">1.3.7. valueOf</h3>
<pre><code class="language-java">public static String valueOf(Object obj) {
        //为null的话返回“null”,否则调用obj的toString
    return (obj == null) ? "null" : obj.toString();
}
</code></pre>
<h4 id="1371-解释stringvalueoftest3--stringvalueoftest3">1.3.7.1. 解释String.valueOf("test3") == String.valueOf("test3")</h4>
<pre><code class="language-java">String val5 = String.valueOf("test3");
String val6 = String.valueOf("test3");
System.out.println(val5 == val6);//true。"test3"在编译期间已经确定,放入常量池中。String.valueOf返回的是常量池中的字符串
</code></pre>
<p><img src="https://raw.githubusercontent.com/TDoct/images/master/img/20200122153151.png"><br>
对于<code>String val5 = String.valueOf("test3")</code>这种代码,编译器首先会把他当作<code>String val5 = "test3"</code>处理,把<code>"test3"</code>放入常量池中,然后调用<code>String.valueOf</code>方法返回常量池中的<code>"test3"</code>字符串,所以两者相等。</p>
<ul>
<li>再看一个例子</li>
</ul>
<pre><code class="language-java">String aa = new String("1111");
String bb = new String("1111");
String val9 = String.valueOf(aa);
String val10 = String.valueOf(bb);
System.out.println(val9 == val10);//false。两个"1111"分别在堆中创建,String.valueOf返回的是堆中不同的对象
</code></pre>
<p><img src="https://raw.githubusercontent.com/TDoct/images/master/img/20200122154245.png"><br>
<code>String aa = new String("1111")</code>这种先在堆中创建字符串<code>"1111"</code>,然后<code>String val9 = String.valueOf(aa)</code>返回的是堆中的字符串,所以两者不等</p>
<h3 id="138-intern方法">1.3.8. intern方法</h3>
<pre><code class="language-java">//运行时往常量池增加字符串
//调用intern方法的时候,如果常量池中已经存在一个字符串与这个字符串相等,那么返回常量池的中字符串。
//没有的话会在常量池中创建这个字符串,然后才返回。
public native String intern();

</code></pre>
<h4 id="1381-解释new-stringtest4intern--test4">1.3.8.1. 解释new String("test4").intern() == "test4"</h4>
<pre><code class="language-java">String val7 = new String("test4");
String val8 = "test4";
String val7Intern = val7.intern();
System.out.println(val8 == val7);//false。val7在堆中,val8在常量池中,自然不相等
System.out.println(val8 == val7Intern);//true。intern方法的作用是在运行时往常量池中增加字符串,如果常量池池中已有,那么把常量池中的对象返回
System.out.println(val8 == val7);//false。再试验一次说明intern方法不是把堆中的地址塞到常量池中
</code></pre>
<p><img src="https://raw.githubusercontent.com/TDoct/images/master/img/20200122155314.png"><br>
<code>String val7 = new String("test4")</code>是堆中的字符串<code>"test4"</code>,<code>String val8 = "test4"</code>是常量池中的<code>"test4"</code>,<code>String val7Intern = val7.intern()</code>intern首先检查常量池中是否有<code>"test4"</code>,发现有直接返回</p>
<h3 id="139-substring">1.3.9. subString</h3>
<pre><code class="language-java">String substring(int beginIndex, int endIndex) {
        //下标越界判断
    if (beginIndex &lt; 0) {
      throw new StringIndexOutOfBoundsException(beginIndex);
    }
    if (endIndex &gt; value.length) {
      throw new StringIndexOutOfBoundsException(endIndex);
    }
    int subLen = endIndexbeginIndex;
    if (subLen &lt; 0) {
      throw new StringIndexOutOfBoundsException(subLen);
    }
    //返回自己或者调用使用char数组带下标的构造函数
    return ((beginIndex == 0) &amp;&amp; (endIndex == value.length)) ? this
            : new String(value, beginIndex, subLen);
}
</code></pre>
<h2 id="14-常见问题">1.4. 常见问题</h2>
<h3 id="141-tostring和valueof的区别">1.4.1. toString和valueOf的区别</h3>
<pre><code class="language-java">String aa = null;
//System.out.println(aa.toString());//抛出异常
System.out.println(String.valueOf(aa));//null
</code></pre>
<p>前者没有做为空判断,后者做了。</p>
<h3 id="142-string的不可变性">1.4.2. String的不可变性</h3>
<p>String这个类是由final修饰的,意味着不能被继承<br>
String内部通过char数组实现,而这个数组是用final修饰的。意味着一旦赋值就不能改变引用,而且String也没有提供修改字符数组内容的方法<br>
用下面的例子解释:</p>
<pre><code class="language-java">String a = "aaa";
a = "bbb";//这里的可变String类型的引用改变了,但是原有的值没有变化

//这种看似修改的方法实际上返回的是一个新的String对象
String c= a.subString(1,2);
</code></pre>
<p><img src="https://raw.githubusercontent.com/TDoct/images/master/img/20200216233033.png"></p>
<h3 id="143-线程安全">1.4.3. 线程安全</h3>
<p>因为不可变所以线程安全</p>
<pre><code class="language-java">public class TestString
{
    public static void main(String[] args) throws InterruptedException
    {
      String string = "0";
      TestThread testThread = new TestThread(string);//因为不可变,所以传递进去无论做了什么操作都不影响
      testThread.start();
      testThread.join();

      System.out.println(string);//0
    }
}

class TestThread extends Thread
{
    private String string;
    public TestThread(String string)
    {
      this.string = string;
    }

    @Override
    public void run()
    {
      this.string += "test";
      System.out.println(Thread.currentThread().getName() + ":" + this.string);
    }
}
</code></pre>
<h3 id="144-string对的重载">1.4.4. String对+的重载</h3>
<p>实际上使用的StringBuilder,并且调用append方法,最后调用toString方法</p>
<ul>
<li>
<p>普通+<br>
<img src="https://raw.githubusercontent.com/TDoct/images/master/img/20200103111852.png"></p>
</li>
<li>
<p>循环+<br>
<img src="https://raw.githubusercontent.com/TDoct/images/master/img/20200103111903.png"></p>
</li>
</ul>
<h3 id="145-replacefirstreplaceallreplace区别">1.4.5. replaceFirst、replaceAll、replace区别</h3>
<ul>
<li><code>String replaceFirst(String regex, String replacement)</code><br>
基于正则的替换,替换第一个</li>
<li><code>String replaceAll(String regex, String replacement)</code><br>
基于正则的替换,替换全部</li>
<li><code>String replace(Char Sequencetarget, Char Sequencereplacement)</code><br>
普通的比较替换,替换全部</li>
</ul>
<h3 id="146-string-s--new-stringabc创建了几个字符串对象">1.4.6. String s = new String("abc")创建了几个字符串对象</h3>
<ul>
<li>当加载类时,"abc"被创建并驻留在了字符创常量池中(如果先前加载中没有创建驻留过)。</li>
<li>当执行此句时,因为"abc"对应的String实例已经存在于字符串常量池中,所以JVM会将此实例复制到会在堆(heap)中并返回引用地址</li>
</ul>
<h1 id="2-stringbuilder">2. StringBuilder</h1>
<h2 id="21-是什么">2.1. 是什么</h2>
<p>线程安全的、可变字符串<br>
其实就是在StringBuilder的基础上加了synchronized关键字</p>
<h2 id="22-如何使用">2.2. 如何使用</h2>
<pre><code class="language-java">public class TestStringBuilder
{
    public static void main(String[] args) throws InterruptedException
    {
      StringBuffer stringBuffer = new StringBuffer();

      Thread thread1 = new Thread(() -&gt; {
            for (int i = 0; i &lt; 5000; i++)
            {
                stringBuffer.append("aaaa");
            }
      });
      Thread thread2 = new Thread(() -&gt; {
            for (int i = 0; i &lt; 5000; i++)
            {
                stringBuffer.append("aaaa");
            }
      });

      thread1.start();
      thread2.start();

      thread1.join();
      thread2.join();


      System.out.println(stringBuffer.toString());
      System.out.println(stringBuffer.length() == 5000 * 2 * 4);//true

    }
}

</code></pre>
<h2 id="23-原理分析">2.3. 原理分析</h2>
<h3 id="231-构造函数">2.3.1. 构造函数</h3>
<pre><code class="language-java"> public final class StringBuffer//一样是final的
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence
{
        public StringBuffer() {
                //跟StringBuilder一样调用AbstractStringBuilder的构造方法
                super(16);//默认容量16个
        }

}

abstract class AbstractStringBuilder implements Appendable, CharSequence {
   
    char[] value;
    int count;
   
        AbstractStringBuilder(int capacity) {
                value = new char;
        }
}
</code></pre>
<h3 id="232-append方法">2.3.2. append方法</h3>
<pre><code class="language-java">//加了synchronized修饰
public synchronized StringBuffer append(String str) {
    toStringCache = null;
    super.append(str);
    return this;
}

</code></pre>
<h3 id="233-tostring">2.3.3. toString</h3>
<pre><code class="language-java">//加了synchronized修饰
public synchronized String toString() {
    if (toStringCache == null) {
      toStringCache = Arrays.copyOfRange(value, 0, count);
    }
    return new String(toStringCache, true);
}
</code></pre>
<h3 id="234-substring">2.3.4. subString</h3>
<pre><code class="language-java">public synchronized String substring(int start, int end) {
        return super.substring(start, end);
}
</code></pre>
<h1 id="3-stringbuffer">3. StringBuffer</h1>
<h2 id="31-是什么">3.1. 是什么</h2>
<p>线程安全的、可变字符串<br>
其实就是在StringBuilder的基础上加了synchronized关键字</p>
<h2 id="32-如何使用">3.2. 如何使用</h2>
<pre><code class="language-java">public class TestStringBuilder
{
    public static void main(String[] args) throws InterruptedException
    {
      StringBuffer stringBuffer = new StringBuffer();

      Thread thread1 = new Thread(() -&gt; {
            for (int i = 0; i &lt; 5000; i++)
            {
                stringBuffer.append("aaaa");
            }
      });
      Thread thread2 = new Thread(() -&gt; {
            for (int i = 0; i &lt; 5000; i++)
            {
                stringBuffer.append("aaaa");
            }
      });

      thread1.start();
      thread2.start();

      thread1.join();
      thread2.join();


      System.out.println(stringBuffer.toString());
      System.out.println(stringBuffer.length() == 5000 * 2 * 4);//true

    }
}

</code></pre>
<h2 id="33-原理分析">3.3. 原理分析</h2>
<h3 id="331-构造函数">3.3.1. 构造函数</h3>
<pre><code class="language-java"> public final class StringBuffer//一样是final的
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence
{
        public StringBuffer() {
                //跟StringBuilder一样调用AbstractStringBuilder的构造方法
                super(16);//默认容量16个
        }

}

abstract class AbstractStringBuilder implements Appendable, CharSequence {
   
    char[] value;
    int count;
   
        AbstractStringBuilder(int capacity) {
                value = new char;
        }
}
</code></pre>
<h3 id="332-append方法">3.3.2. append方法</h3>
<pre><code class="language-java">//加了synchronized修饰
public synchronized StringBuffer append(String str) {
    toStringCache = null;
    super.append(str);
    return this;
}

</code></pre>
<h3 id="333-tostring">3.3.3. toString</h3>
<pre><code class="language-java">//加了synchronized修饰
public synchronized String toString() {
    if (toStringCache == null) {
      toStringCache = Arrays.copyOfRange(value, 0, count);
    }
    return new String(toStringCache, true);
}
</code></pre>
<h3 id="334-substring">3.3.4. subString</h3>
<pre><code class="language-java">public synchronized String substring(int start, int end) {
        return super.substring(start, end);
}
</code></pre>
<h1 id="4-stringbuilder-vs-stringbuffer-vs-string">4. StringBuilder vs StringBuffer vs String</h1>
<table>
<thead>
<tr>
<th></th>
<th>String</th>
<th>StringBuffer</th>
<th>StringBuilder</th>
</tr>
</thead>
<tbody>
<tr>
<td>是否线程安全</td>
<td>√</td>
<td>√</td>
<td>×</td>
</tr>
<tr>
<td>是否可变</td>
<td>×</td>
<td>√</td>
<td>√</td>
</tr>
</tbody>
</table>
<h1 id="5-参考链接">5. 参考链接</h1>
<ul>
<li>Java 源码分析 — String 的设计 - 掘金</li>
<li>String源码分析 - 掘金</li>
<li>Java中String对"+"的"重载" - 掘金</li>
<li>java 中为什么说,String是线程安全的?为什么说StringBuilder是线程不安全的?分别举例证明。 - OSCHINA</li>
<li>为什么String被设计为不可变?是否真的不可变? - Jessica程序猿 - 博客园</li>
<li>Java提高篇——理解String 及 String.intern() 在实际中的应用 - 萌小Q - 博客园</li>
</ul><br><br>
来源:https://www.cnblogs.com/ThinkerQAQ/p/18953468
頁: [1]
查看完整版本: 2.Java SDK源码分析系列笔记-String系列