淺層次瞭解Java中的String

最近以爲本身學的東西經常會忘記,因而就想寫一下筆記。總結一下String相關的一些內容,若是有什麼理解有誤的話歡迎你們指出。java

目錄

  • Unicode 編碼
  • String屬性
  • 構造方法
  • 經常使用函數
  • 一些常被問到的問題

Unicode

&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp計算機誕生的時候,老美爲了存儲他們的英文單詞和一些符號,與計算機有一套約定,整數45表示百分號%,整數50表明數字2,51表示數字3,這些約定成爲ASCII,他跟計算機約定好了從整數0-127所對應的字符。即1個字節就能夠保存下來了。正則表達式

&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp後來計算機被世界普遍應用,因此各個國家都須要與計算機作好約定,好比咱們大中國也須要跟計算機約定好,可是128個字符明顯就容不下咱們中國五千年來的文化積累呀,因而咱們和計算機說好,中文要佔3個字節,好比0xB6A001表示中文字「丁」,這種數值與中文的約定成爲GBK,那日本也須要與計算機作約定,韓國也須要。世界上那麼多種語言,計算機在識別字符的時候還要先看看是什麼國家跟他作了約定,很是麻煩。數組

&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp因而Unicode對這些編碼進行了大一統,用6個字節表示全部的字符,範圍從00x10FFFF,其中經常使用的字符放在00xFFFF的位置,每個字符對應的數字叫作碼點,安全

&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp後來人們發現,其實經常使用的字符用00xFFFF就能夠表示了,後面那些字符用的不多,不必每次都弄3個字節來存儲,因而咱們制定了可變長的UTF-8和·UTF16,在存儲過程當中,若是字符在00xFFFF之間就用2個字節,恰好一個char來存儲,若是大於0xFFFF的就要用4個字節存儲,恰好2個char。在java中就是以這種方式存儲字符的。app

屬性:

/** The value is used for character storage. */
private final char value[];

/** Cache the hash code for the string */
private int hash; // Default to 0

/** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID = -6849794470754667710L;
  • char數組 被final修飾
  • hash
  • serialVersionUID 這個與序列化有關,之後再總結

構造方法

  • 默認構造函數函數

    public String() {
           this.value = "".value;
       }
  • 傳入String性能

    public String(String original) {
           this.value = original.value;
           this.hash = original.hash;
       }
  • 傳入char數組學習

    public String(char value[]) {
           this.value = Arrays.copyOf(value, value.length);
       }
  • 傳入byte數組,須要傳入字符集優化

    public String(byte bytes[], String charsetName)
               throws UnsupportedEncodingException {
           this(bytes, 0, bytes.length, charsetName);
       }
    平時咱們的轉碼就須要依靠這個構造方法啦
  • 傳入StringBufferui

    public String(StringBuffer buffer) {
           synchronized(buffer) {
               this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
           }
       }
    這裏上了鎖,因此是線程安全滴
  • 傳入StringBuilder

    public String(StringBuilder builder) {
               this.value = Arrays.copyOf(builder.getValue(), builder.length());
       }
    關於 StringBuilderStringBuffer等等會講
  • 還有不少就不數啦

經常使用方法

equals

據說這個經常會被問到,原本equals方法是Object類裏面的,String把他重寫了一下,用來判斷兩字符串是否相等

public boolean equals(Object anObject) {
        //判斷是否是同一個引用
        if (this == anObject) {
            return true;
        }
        //判斷是否爲String類
        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])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }

寫的很精煉,裏面有些地方值得我去學習的,看到instanceof我忽然想起反射裏面的一個小知識點

equalsIgnoreCase

忽略大小寫比較兩字符串是否相等

public boolean equalsIgnoreCase(String anotherString) {
        return (this == anotherString) ? true
                : (anotherString != null)
                && (anotherString.value.length == value.length)
                && regionMatches(true, 0, anotherString, 0, value.length);
    }

這裏調用了一個叫regionMatches的方法,我也把他沾上來

public boolean regionMatches(boolean ignoreCase, int toffset,
            String other, int ooffset, int len) {
        char ta[] = value;
        int to = toffset;
        char pa[] = other.value;
        int po = ooffset;
        // Note: toffset, ooffset, or len might be near -1>>>1.
        if ((ooffset < 0) || (toffset < 0)
                || (toffset > (long)value.length - len)
                || (ooffset > (long)other.value.length - len)) {
            return false;
        }
        while (len-- > 0) {
            char c1 = ta[to++];
            char c2 = pa[po++];
            if (c1 == c2) {
                continue;
            }
            if (ignoreCase) {
                // If characters don't match but case may be ignored,
                // try converting both characters to uppercase.
                // If the results match, then the comparison scan should
                // continue.
                char u1 = Character.toUpperCase(c1);
                char u2 = Character.toUpperCase(c2);
                if (u1 == u2) {
                    continue;
                }
                // Unfortunately, conversion to uppercase does not work properly
                // for the Georgian alphabet, which has strange rules about case
                // conversion.  So we need to make one last check before
                // exiting.
                if (Character.toLowerCase(u1) == Character.toLowerCase(u2)) {
                    continue;
                }
            }
            return false;
        }
        return true;
    }

他在比較的時候還先轉大寫再轉小寫,爲何要這麼作你們看他的註釋把我其實沒看懂。

我想了一下,String不是帶有一個toUppaerCase的方法嘛?若是讓我去寫這個方法我可能就直接這樣寫了

s2.equals(s1.toUppaerCase());

不過看了看源碼,我這樣寫Java會對字符串遍歷兩次,效率會比較低吧。

而後我又看了一下toUpperCase()方法裏面的代碼

public String toUpperCase(Locale locale) {
        if (locale == null) {
            throw new NullPointerException();
        }

        int firstLower;
        final int len = value.length;

        /* Now check if there are any characters that need to be changed. */
        scan: {
            for (firstLower = 0 ; firstLower < len; ) {
                
                int c = (int)value[firstLower];
                int srcCount;
                if ((c >= Character.MIN_HIGH_SURROGATE)
                        && (c <= Character.MAX_HIGH_SURROGATE)) {
                    c = codePointAt(firstLower);
                    srcCount = Character.charCount(c);
                } else {
                    srcCount = 1;
                }
                int upperCaseChar = Character.toUpperCaseEx(c);
                if ((upperCaseChar == Character.ERROR)
                        || (c != upperCaseChar)) {
                    break scan;
                }
                firstLower += srcCount;
            }
            return this;
        }

值得注意的是其中兩行代碼

c = codePointAt(firstLower);
srcCount = Character.charCount(c);

在Java中有兩個獲取下表字符的Unicode編碼,

  • charAt 獲取的是0-0xFFFFFF的碼點,3個字節,保存在int中
  • codePointAt 獲取的是0-0xFFFF的碼點,2個字節,保存在int中

你們順着代碼看,大概也能猜出來Character.charCount()實際上是計算這個字符究竟是佔2個字節的仍是3個字節的。

順便提一下,String的length方法和codePointCount方法也是同樣的道理

  • length 返回的是字符串的長度,若是有超出0xFFFF的字符會算兩
  • codePointCount 返回真正的字符個數,一個字符對應一個碼點

因此Java在大小寫轉換的時候還要考慮編碼問題,學就是了。

compareTo

也是比較兩個字符串,不過他與equals不一樣,equals傳入的是Object,返回的是布爾值,compareTo傳入的是字符串,返回的是整型

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;
    }

返回值表示第一個出現不一樣的字符的編碼差值。若是是相同的字符串則返回0。你們回憶一下equals方法,他第一步是判斷兩個變量是否爲同一個引用,這裏爲何不去判斷呢?解決這個問題咱們要想一下字符串在內存中是怎麼存儲的。

其餘函數

  • indexOf 可傳入字符、字符數組,從左向右對入參進行索引,找不到返回-1
  • lastIndexOf 從右向左對入參進行索引,找不到返回-1
  • contains 判斷是否包含子字符串,內部調用indexOf
  • toUpperCase 將字符串轉換成大寫
  • toLowerCase 將字符串轉換成小寫
  • trim 去除字符串先後的空格
  • replace 替換字符串,可傳入正則表達式,內部真正作替換的是replaceAll
  • join 鏈接字符串,能夠傳入StringStringBuffer
注意 : 字符串是不可變的,操做完必定要拿個新的字符串存於下返回值,例如· trim函數,他會新建一個char數組存放結果
public String trim() {
    int len = value.length;
    int st = 0;
    char[] val = value;    /* avoid getfield opcode */

    while ((st < len) && (val[st] <= ' ')) {
        st++;
    }
    while ((st < len) && (val[len - 1] <= ' ')) {
        len--;
    }
    return ((st > 0) || (len < value.length)) ? substring(st, len) : this;
}

一些常見問題

  • == equals有什麼區別
  • final修飾的好處
  • String、StringBufferStringBuilder
  • String的建立方式

1. == equals有什麼區別

==對於基本類型來講,是直接比較值的大小,而對於引用來講是比較地址是否相等,而String的·equals是比較兩個字符串的內容是否相同。

能夠發現Object也有equals方法,內部實際上是調用==

public boolean equals(Object obj) {
    return (this == obj);
}

2. 爲何Stringfinal修飾

兩個目的,安全高效

看下圖,如今有兩個引用指向字符串,咱們的字符串是存儲在常量池中的,若是字符串設計成可變的話,s1不當心對字符串的內容修改了,咱們用s2取值的時候發現他變了,會引發不堪設想的災難,關於常量池的內容晚點我也總結一下。

String不可變性

3. StringStringBufferStringBuilder的區別

SringBufferStringBuilder主要用於字符串的拼接。

字符串的拼接方法有不少,下面咱們來分析一下:

  • +:
    因爲String的不可變性,在循環體中對字符串拼接的時候每次都要建立一個對象

    String s = "";
    for(int i = 0; i < 100000; i++) {
       s = s + "test"
    }

    每次都須要在建立新的字符串變量,因此·+適用於常量字符串的拼接,編譯器會在編譯的時候幫咱們拼接好。

  • Stringjoin方法

    其實內部是使用了StringJoin方法實現的,StringJoiner內部實際上是使用StringBuilder的。

  • StringBufer

    咱們能夠看看他的append方法

    public synchronized StringBuffer append(CharSequence s) {
        toStringCache = null;
        super.append(s);
        return this;
    }

    加了同步鎖,因此是線程安全的,返回的是this,因此支持鏈式操做

    因爲加了同步鎖,性能相對沒有 StringBuilder
  • StringBuilder

    public StringBuilder append(String str) {
        super.append(str);
        return this;
    }

    沒有帶鎖,線程不安全,可是效率會皮較高,關於線程安全的知識我稍後也會總結

總結:所以,若是是常量或者一兩個變量之間的拼接咱們能夠直接使用+,若是是須要重複拼接的話,不考慮線程安全就是用StringBuilder,考慮則使用StringBuffer.

關於StringJoiner

有點像Python裏面的join,它能夠幫咱們在拼接的時候加入分隔符、前綴和後綴

構造函數

public StringJoiner(CharSequence delimiter,
                    CharSequence prefix,
                    CharSequence suffix) {
    Objects.requireNonNull(prefix, "The prefix must not be null");
    Objects.requireNonNull(delimiter, "The delimiter must not be null");
    Objects.requireNonNull(suffix, "The suffix must not be null");
    // make defensive copies of arguments
    this.prefix = prefix.toString();
    this.delimiter = delimiter.toString();
    this.suffix = suffix.toString();
    this.emptyValue = this.prefix + this.suffix;
}

能夠傳入分隔符、前綴和後綴

兩個StringJoiner可使用merge拼接

public StringJoiner merge(StringJoiner other) {
    Objects.requireNonNull(other);
    if (other.value != null) {
        final int length = other.value.length();
        // lock the length so that we can seize the data to be appended
        // before initiate copying to avoid interference, especially when
        // merge 'this'
        StringBuilder builder = prepareBuilder();
        builder.append(other.value, other.prefix.length(), length);
    }
    return this;
}

4. String的建立方式

String有兩種建立方式

  • String s1 = "Rhythm";

    s1在建立時,會在常量池中看看有沒有這個字符串,有就直接返回句柄(地址),沒有就建立在返回句柄(地址)

  • String s2 = new String("rHYTHM");

    直接在堆中建立,調用intern方法能夠將其存入常量池

JDB1.7後的版本中,編譯器會對字符串進行優化

String s1 = "AAA" + "BBB";
String s2 = "AAABBB";
System.out.println(s1 == s2);    //true

返回的是true,你們平時能夠稍加註意一下


第一次寫文章,寫得不咋地,你們見諒,下一篇打算寫一下JVM的內存模型,順便交代一下String是怎麼存儲的。

相關文章
相關標籤/搜索