Java SDK源码剖析之String篇(系列二)

2个月前发布 gsjqwyl
26 0 0

Java SDK源码中String相关剖析(第二篇)

目录

  • 1. String
    • 1.1. 是什么
      一种具备不可变性与线程安全性的字符串数据类型
    • 1.2. 应用场景
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方法并非将堆中的地址存入常量池
    }
}
* 1.3. 源码细节解析
  * 1.3.1. 类的定义情况
//final修饰表示不能被继承 
public final class String
    //可比较、可序列化
    implements java.io.Serializable, Comparable<String>, CharSequence 
{
    /** 用于存储字符的数据 */
    //底层由char数组实现,final修饰表明引用不可更改,但这不意味着char数组里的值不能修改
    //那为何String还是不可变的呢?因为String未提供修改内部属性char value[]的方法,所以自然不可变
    private final char value[];

    /** 缓存字符串的哈希码 */
    private int hash; // 默认值为0
}
  文字表述:String类采用final修饰,使其无法被继承。其内部依靠char数组来存储字符,该char数组被final修饰,这意味着引用不能被改变。并且String类没有对外公开修改char数组内容的方法,所以String具有不可变性。
  * 1.3.2. 构造函数相关
//无参构造方法
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 < 0) {
        throw new StringIndexOutOfBoundsException(offset);
    }
    if (count <= 0) {
        if (count < 0) {
            throw new StringIndexOutOfBoundsException(count);
        }
        if (offset <= value.length) {
            this.value = "".value;
            return;
        }
    }
    // Note: offset or count might be near 1>>>1.
    if (offset > value.length  count) {
        throw new StringIndexOutOfBoundsException(offset + count);
    }

    //复制char数组
    this.value = Arrays.copyOfRange(value, offset, offset+count);
}
  * 1.3.2.1. 解释new String("test1")与new String("test1")为何不相等
 String val = new String("test1");
String val1 = new String("test1");
System.out.println(val == val1);//false。上面的代码会在堆中两块不同的地方创建字符串
  文字表述:查看字节码可知,调用NEW字节码会在堆中创建字符串,所以new String("test1")和new String("test1")会在堆中生成不同的对象,导致两者不相等。
  * 1.3.3. 常量池相关
    常量池英文名为constant pool,指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。它包含类、方法、接口等中的常量以及字符串常量等
  * 1.3.3.1. 说明"test2"=="test2"的原因
String val2 = "test2";
String val3 = "test2";
System.out.println(val2 == val3);//true。上面的代码在编译期间已经确定,那么会把"test2"保存在常量池(不是堆中)

String val4 = "te" + "st2";
System.out.println(val2 == val4);//true。虽然val4是通过+拼接的,但是这个也是可以在编译期确定的,所以使用的仍是常量池中的字符串
  文字表述:查看字节码,上述三行代码都调用了LDC字节码,该字节码表示在常量池中加载字符串,"test2"在编译器会存入.class文件中,所以三者相等。
  * 1.3.4. equals方法剖析
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[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}
  * 1.3.5. toCharArray方法解读
public char[] toCharArray() {
    // Cannot use Arrays.copyOf because of class initialization order issues
    //创建一个新的char数组
    char result[] = new char[value.length];
    //调用arraycopy函数把value的值复制到新的char数组返回(防止外界改变char数组的值)
    System.arraycopy(value, 0, result, 0, value.length);
    return result;
}
  * 1.3.6. toString方法分析
public String toString() {
    //直接返回自己
    return this;
}
  * 1.3.7. valueOf方法
public static String valueOf(Object obj) {
    //为null的话返回“null”,否则调用obj的toString
    return (obj == null) ? "null" : obj.toString();
}
  * 1.3.7.1. 解释String.valueOf("test3")与String.valueOf("test3")相等的缘由
String val5 = String.valueOf("test3");
String val6 = String.valueOf("test3");
System.out.println(val5 == val6);//true。"test3"在编译期间已经确定,放入常量池中。String.valueOf返回的是常量池中的字符串
  文字表述:对于String val5 = String.valueOf("test3")这类代码,编译器会将其当作String val5 = "test3"来处理,把"test3"放入常量池中,然后调用String.valueOf方法返回常量池中的"test3"字符串,所以两者相等。再看示例,String aa = new String("1111")在堆中创建字符串"1111",String val9 = String.valueOf(aa)返回的是堆中的字符串,所以两者不等。
  * 1.3.8. intern方法
//运行时往常量池增加字符串
//调用intern方法的时候,如果常量池中已经存在一个字符串与这个字符串相等,那么返回常量池的中字符串。
//没有的话会在常量池中创建这个字符串,然后才返回。
public native String intern();
  * 1.3.8.1. 阐释new String("test4").intern() == "test4"的原理
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方法不是把堆中的地址塞到常量池中
  文字表述:String val7 = new String("test4")是堆中的字符串"test4",String val8 = "test4"是常量池中的"test4",String val7Intern = val7.intern()时,intern首先检查常量池中是否有"test4",发现有则直接返回。
  * 1.3.9. subString方法解析
String substring(int beginIndex, int endIndex) {
    //下标越界判断
    if (beginIndex < 0) {
        throw new StringIndexOutOfBoundsException(beginIndex);
    }
    if (endIndex > value.length) {
        throw new StringIndexOutOfBoundsException(endIndex);
    }
    int subLen = endIndex  beginIndex;
    if (subLen < 0) {
        throw new StringIndexOutOfBoundsException(subLen);
    }
    //返回自己或者调用使用char数组带下标的构造函数
    return ((beginIndex == 0) && (endIndex == value.length)) ? this
            : new String(value, beginIndex, subLen);
}
* 1.4. 常见问题汇总
  * 1.4.1. toString和valueOf的差异之处
String aa = null;
//System.out.println(aa.toString());//抛出异常
System.out.println(String.valueOf(aa));//null
  文字表述:前者没有进行空值判断,后者进行了空值判断。
  * 1.4.2. String不可变性的缘由
  String这个类由final修饰,意味着不能被继承;String内部通过char数组实现,而该数组用final修饰,意味着一旦赋值就不能改变引用,而且String没有提供修改字符数组内容的方法。用示例解释:String a = "aaa"; a = "bbb";这里只是String类型的引用改变了,原有值未变化;String c = a.subString(1,2);这种看似修改的方法实际上返回的是一个新的String对象。
  * 1.4.3. 线程安全性探讨
  因为不可变所以线程安全。
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);
    }
}
  * 1.4.4. String对+运算符的重载情况
  实际上使用的StringBuilder,并且调用append方法,最后调用toString方法。包括普通+和循环+的情况。
  * 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会将此实例复制到堆中并返回引用地址。

2. StringBuilder

* 2.1. 基本概念
线程安全的、可变字符串,其实就是在StringBuilder的基础上加了synchronized关键字。
* 2.2. 使用方式
public class TestStringBuilder
{
    public static void main(String[] args) throws InterruptedException
    {
        StringBuffer stringBuffer = new StringBuffer();

        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 5000; i++)
            {
                stringBuffer.append("aaaa");
            }
        });
        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 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

    }
}
* 2.3. 原理深入分析
  * 2.3.1. 构造函数细节
 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[capacity];
    }
}
  * 2.3.2. append方法解读
//加了synchronized修饰
public synchronized StringBuffer append(String str) {
    toStringCache = null;
    super.append(str);
    return this;
}
  * 2.3.3. toString方法分析
//加了synchronized修饰
public synchronized String toString() {
    if (toStringCache == null) {
        toStringCache = Arrays.copyOfRange(value, 0, count);
    }
    return new String(toStringCache, true);
}
  * 2.3.4. subString方法解析
public synchronized String substring(int start, int end) {
    return super.substring(start, end);
}

3. StringBuffer

* 3.1. 基本概述
线程安全的、可变字符串,其实就是在StringBuilder的基础上加了synchronized关键字。
* 3.2. 使用途径
public class TestStringBuilder
{
    public static void main(String[] args) throws InterruptedException
    {
        StringBuffer stringBuffer = new StringBuffer();

        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 5000; i++)
            {
                stringBuffer.append("aaaa");
            }
        });
        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 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

    }
}
* 3.3. 原理剖析
  * 3.3.1. 构造函数详情

“`java
public final class StringBuffer//一样是

© 版权声明

相关文章

没有相关内容!

暂无评论

none
暂无评论...