Java 基礎 String 的詳解

寫在前面

String 算是 Java 源碼中先要學習的,今天就從源碼的角度來重新認識一下

1.存儲結構

看主流的 JDK 版本 1.8 ,String 內部實際存儲結構爲 char 數組,源碼如下:

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];

    /** Cache the hash code for the string */
    private int hash; // Default to 0
    //其他內容......

2.常用方法

2.1.構造方法

其中 StringBuffer 和 StringBuilder 爲參數的構造函數用的比較少,但也要知道

	/**
     * String 爲參數的構造方法
     * @param  original
     *        A {@code String}
     */
    public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }

    /**
     * char[] 爲參數構造方法
     * @param  value
     *         The initial value of the string
     */
    public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);
    }
    
	/**
     * StringBuffer 爲參數的構造方法
     * @param  buffer
     *         A {@code StringBuffer}
     */
    public String(StringBuffer buffer) {
        synchronized(buffer) {
            this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
        }
    }

    /**
     * StringBuilder 爲參數的構造方法
     * @param   builder
     *          A {@code StringBuilder}
     * @since  1.5
     */
    public String(StringBuilder builder) {
        this.value = Arrays.copyOf(builder.getValue(), builder.length());
    }

2.2.equals()

String 中 equals() 是比較兩個字符串的值是否相等,== 纔是比較字符串的引用是否相等,equals() 重寫了父類 Object 方法,傳參也爲 Object 類型,方法中會通過 instanceof 判斷,是 String 類型才進行下一步。

這裏提一下,Object 父類中 equals() 和 == 對於引用類型的作用是一樣的。

	/**
	* @param  anObject
     *         The object to compare this {@code String} against
     *
     * @return  {@code true} if the given object represents a {@code String}
     *          equivalent to this string, {@code false} otherwise
     *
     * @see  #compareTo(String)
     * @see  #equalsIgnoreCase(String)
     */
    public boolean equals(Object anObject) {
        if (this == anObject) {// 對象引用相同直接返回 true
            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;
                while (n-- != 0) {
                    if (v1[i] != v2[i])//轉化爲字符數組,對比每個字符,有一個不相同就是 fasle
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }

有一個和 equels 相似的方法 equalsIgnoreCase(String),忽略大小寫對比,傳參爲 String 類型

2.3.compareTo()

compareTo() 和 equels() 處理方式類似,都是字符對比,不同的是,equels() 比較兩字符串相同返回 true,不相同返回 false;compareTo() 比較兩字符串相同返回 0,不相同返回 其他 int 類型數值。並且 compareTo() 只能接收 String 類型。

還有一個和 compareTo() 比較類似的方法 compareToIgnoreCase(),用於忽略大小寫後比較兩個字符串。

	/**
     * @param   anotherString   the {@code String} to be compared.
     * @return  the value {@code 0} if the argument string is equal to
     *          this string; a value less than {@code 0} if this string
     *          is lexicographically less than the string argument; and a
     *          value greater than {@code 0} if this string is
     *          lexicographically greater than the string argument.
     */
    public int compareTo(String anotherString) {
        int len1 = value.length;
        int len2 = anotherString.value.length;
        int lim = Math.min(len1, len2);
        char v1[] = value;
        char v2[] = anotherString.value;

        int k = 0;
        while (k < lim) {
            char c1 = v1[k];
            char c2 = v2[k];
            if (c1 != c2) {
                return c1 - c2;
            }
            k++;
        }
        return len1 - len2;
    }

2.4.其他重要方法

  • indexOf():查詢字符串首次出現的下標位置
  • lastIndexOf():查詢字符串最後出現的下標位置
  • contains():查詢字符串中是否包含另一個字符串
  • toLowerCase():把字符串全部轉換成小寫
  • toUpperCase():把字符串全部轉換成大寫
  • length():查詢字符串的長度
  • trim():去掉字符串首尾空格
  • replace():替換字符串中的某些字符
  • split():把字符串分割並返回字符串數組
  • join():把字符串數組轉爲字符串

3.常遇問題

3.1.String 和 StringBuilder、StringBuffer 的區別

String 是不可變的,在字符串拼接的時候使用 String 會很耗性能,因此有了 StringBuilder 和 StringBuffer,它們有 2 方法 append 和 insert 可以實現字符串拼接,唯一不同的是 StringBuffer 使用 synchronized 來保證線程安全

//StringBuffer 截取片段,具體可以看 StringBuffer 類源碼

	@Override
    public synchronized StringBuffer append(Object obj) {
        toStringCache = null;
        super.append(String.valueOf(obj));
        return this;
    }

    @Override
    public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }
    //其他......


    /**
     * @throws StringIndexOutOfBoundsException {@inheritDoc}
     */
    @Override
    public synchronized StringBuffer insert(int offset, Object obj) {
        toStringCache = null;
        super.insert(offset, String.valueOf(obj));
        return this;
    }

    /**
     * @throws StringIndexOutOfBoundsException {@inheritDoc}
     */
    @Override
    public synchronized StringBuffer insert(int offset, String str) {
        toStringCache = null;
        super.insert(offset, str);
        return this;
    }
   //其他......
//StringBuilder截取片段,具體可以看 StringBuilder類源碼

 	@Override
    public StringBuilder append(Object obj) {
        return append(String.valueOf(obj));
    }

    @Override
    public StringBuilder append(String str) {
        super.append(str);
        return this;
    }
	//其他......


	/**
     * @throws StringIndexOutOfBoundsException {@inheritDoc}
     */
    @Override
    public StringBuilder insert(int offset, Object obj) {
            super.insert(offset, obj);
            return this;
    }

    /**
     * @throws StringIndexOutOfBoundsException {@inheritDoc}
     */
    @Override
    public StringBuilder insert(int offset, String str) {
        super.insert(offset, str);
        return this;
    }
    //其他......

StringBuffer 保證線程安全,所以性能不是很高,JDK 1.5 就有了 StringBuilder

3.2.String 爲什麼用 final 修飾

使用 final 修飾的第一個好處是安全;第二個好處是高效,例如

String s1 = "java";
String s2 = "java";

只有字符串是不可變時,我們才能實現字符串常量池,字符串常量池可以爲我們緩存字符串,提高程序的運行效率,如下圖所示:
在這裏插入圖片描述

3.3.JVM 中存儲

String 常用的 2 種創建方式,有 String a1 = “java” 和 String a2 = new Strring(“java”),但他們在內存中的存放方式不同,JDK1.8 中創建啊變量 a1,會先從常量池中找字符串 “java”,如果有直接返回,如果沒有則先在常量池中創建該字符串再返回,而變量 a2會直接在堆內存上創建,a2 調用方法 intern() 會把字符串保存到常量池,例如

String a1 = "java";
String a2 = new Strring("java");
String a3 = "a2.intern();

System.out.println(a1 == a2); // false
System.out.println(a1 == a3); // true

JVM 存儲位置如圖
在這裏插入圖片描述
PS:JDK 1.7 之後把永生代換成的元空間,把字符串常量池從方法區移到了 Java 堆上。

結束…

如果有哪些不對的地方煩請指認,先行感謝

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章