Skip to content

值得您信賴的旅遊品牌 | 團體旅遊、自由行的專家‎

機場接送

Menu
  • 首頁
  • 旅遊天地
  • 裝潢設計
  • 環保清潔
  • 發燒車訊
Menu

死啃了String源碼之後

Posted on 2021-01-292021-01-29 by admin

Java源碼之String

說在前面:

為什麼看源碼: 最好的學習的方式就是模仿,接下來才是創造。而源碼就是我們最好的模仿對象,因為寫源碼的人都不是一般的人,所以用心學習源碼,也就可能變成牛逼的人。其次,看源碼,是一項修練內功的重要方式,書看百遍其意自現,源碼也是一樣,前提是你不要懼怕源碼,要用心的看,看不懂了,不要懷疑自己的智商,回過頭來多看幾遍,我就是這樣做的,一遍有一遍的感受,等你那天看源碼不由的驚嘆一聲,這個代碼寫得太好了,恭喜你,你已經離優秀不遠了。最後,看源碼,能培養我們的編程思維,當然這個層次有點高了,需要時間積累,畢竟我離這個境界也有點遠。

今天就來談談java的string的源碼實現,後續我也會寫javaSe的源碼系列,歡迎圍觀和交流。

1.繼承關係

繼承三個接口的說明:

Comparable接口:

實現對象之間比較的接口,它的核心方法只有一個:

public int compareTo(T o);

CharSequence接口:

CharSequence是char值的可讀序列。該接口提供對許多不同種類的char序列的統一隻讀訪問。CharSequence是一個接口,它只包括length(), charAt(int index), subSequence(int start, int end)這幾個API接口。除了String實現了CharSequence之外,StringBuffer和StringBuilder也實現了 CharSequence接口。

那麼String為什麼實現Charsequence這個接口呢。這裏就要涉及一個java的重要特性,也就是多態。看下面的代碼

   public void charSetTest(CharSequence charSequence){
        System.out.println(charSequence+"實現了多態");
    }
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        StringTest strTest = new StringTest();
        strTest.charSetTest(new String("我是String"));
        strTest.charSetTest(new StringBuffer("我是StringBuffer"));
        strTest.charSetTest(new StringBuilder("我是StringBuilder"));
}

執行結果:

我是String實現了多態
我是StringBuffer實現了多態
我是StringBuilder實現了多態

繼承這個接口的原因就很明顯:

因為String對像是不可變的,StringBuffer和StringBuilder這兩個是可變的,所以我們在構造字符串的過程中往往要用到StringBuffer和StringBuilder。如果那些方法定義String作為參數類型,那麼就沒法對它們用那些方法,先得轉化成String才能用。但StringBuffer和StringBuilder轉換為String再轉換過來很化時間的,用它們而不是直接用String的“加法”來構造新String本來就是為了省時間。

Serializable接口:

繼承該接口,就是表明這個類是是可以別序列化的,這裏就標記了string這個類是可以被序列化的,序列化的定義以及使用時機可以移步這裏。

2.常用方法的使用和源碼解析:

2.1構造方法

string總共提供了15種構造方法:

當然,這麼多的構造方法,只需搞明白四個構造方法就可以了,因為其他的構造方法就是這四個構造方法的調用:

在看構造方法之前,先看看String的屬性

 private final char value[];
 private int hash; // Default to 0
 private static final ObjectStreamField[] serialPersistentFields =
        new ObjectStreamField[0];

通過屬性了解到,底層聲明了一個final類型的char數組,這個char數組就是String的構造方法的核心,因為String的本質就是字符char(只針對javaSE8 以前的版本),下面來分析有代表的四個構造方法:

  1. String()和String(String original)

    這兩個構造方法我們平時用的比較經常,源碼如下:

     public String() {   this.value = "".value; }
    
     public String(String original) {
            this.value = original.value;
            this.hash = original.hash;
        }
    

    分析:

    通過源碼可以看到,核心還是和String類聲明的char[]數組value屬性建立聯繫,進而來處理這個屬性的值。

    在String()這個空構造的方法中,就是屬性char數組的值聲明為一個空的字符串賦值給屬性value.

    而在String(String original),也是將方法參數的value的屬性賦值給聲明char[]數組的value屬性,方便String的其他方法對char[]數組處理。

    記住,在java的String操作中,大多數情況下還是對char[]數組的操作,這點很重要。

    一般情況下定義一個新的字符串,有下面的兩種方式:

    String chen = new String("chen");   // 這個我們一般不會使用
    String chen = "chen";
    

    我們一般會選擇第二種,這又是什麼原因呢:

    其實這兩種聲明的方式在JVM看來時等價的。

    劃重點:

    但是String password=”chen”,利用了字符串緩衝池,也就是說如果緩衝池中已經存在了相同的字符串,就不會產生新的對象,而直接返回緩衝池中的字符串對象的引用。

    如:
    String a = "chen";
    String b = "chen";
    String c = new String("chen");
    String d = new String("chen");
    
    System.out.println(a==b);//將輸出"true";因為兩個變量指向同一個對象。利用了字符串的緩衝池
    System.out.println(c==d);//將輸出"flase";因為兩個變量不指向同一個對象。雖然值相同,只有用c.equals(d)才能返回true.
    

    所以實際中,建議用第一種,可以減少系統資源消耗。

  2. String(char[]vlaue,int offest,int count) 與字符相關的構造方法

    源碼如下:

    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);
            }
            this.value = Arrays.copyOfRange(value, offset, offset+count);
        }
    

    分析:

    這個構造方法的作用就是傳入指定大小的char[] 數組,指定一個起始位置,然後構造出從起始位置開始計算的指定長度個數的字符串,具體用法:

    public static void main(String[] args) {
            char[] chars = new char[]{'a','b','c','d'};
            
           // 從chars數組的第二位開始,總數為3個字符的字符串 
            String rangeChar=new String(chars,1,3);
            System.out.println(rangeChar);
        }
    
    輸出:
        abc   
    

    這個構造方法的核心在於:

    this.value = Arrays.copyOfRange(value, offset, offset+count);
    
    //而這個方法在追一層,就到了Arrays這個工具類copyOfRange這個方法
        public static char[] copyOfRange(char[] original, int from, int to) {
            int newLength = to - from;
            if (newLength < 0)
                throw new IllegalArgumentException(from + " > " + to);
            char[] copy = new char[newLength];
            System.arraycopy(original, from, copy, 0,
                             Math.min(original.length - from, newLength));
            return copy;
        }
    

    其實看到這裏,他的實現原理就基本清楚了,分析copyOfRange()這個方法的執行步驟:

    首先是獲取原字符數組的orginal[]要構造字符串的長度,也就是這一行代碼:

    int newLength = to - from;
    

    然後異常判斷,並聲明新的數組,來存儲原數組指定長度的值

     if (newLength < 0)
                throw new IllegalArgumentException(from + " > " + to);
            char[] copy = new char[newLength];
    

    將原字符數組指定長度的值拷貝到新數組,返回這個數組:

     System.arraycopy(original, from, copy, 0,
                             Math.min(original.length - from, newLength));
            return copy;
    

    最後再將數組的值賦值給String的屬性value,完成初始化:

     this.value = Arrays.copyOfRange(value, offset, offset+count);
    

    歸根結底還是和String聲明的屬性value建立聯繫,完成相關的操作。

  3. String(byte[] bytes,int offest,int length,Charset charset) 字節相關的構造方法

    這個構造方法的作用就是將指定長度的字節數組,構造成字符串,且還可以指定編碼值:

    涉及的源碼如下:

    public String(byte bytes[], int offset, int length, Charset charset) {
            if (charset == null)
                throw new NullPointerException("charset");
            checkBounds(bytes, offset, length);
            this.value =  StringCoding.decode(charset, bytes, offset, length);
        }
    
    // StringCoding中的方法:
     static char[] decode(Charset cs, byte[] ba, int off, int len) {
             // 1, 構造解碼器
            CharsetDecoder cd = cs.newDecoder();
            int en = scale(len, cd.maxCharsPerByte());
            char[] ca = new char[en];
            if (len == 0)
                return ca;
            boolean isTrusted = false;
            if (System.getSecurityManager() != null) {
                if (!(isTrusted = (cs.getClass().getClassLoader0() == null))) {
                    ba =  Arrays.copyOfRange(ba, off, off + len);
                    off = 0;
                }
            }
            cd.onMalformedInput(CodingErrorAction.REPLACE)
              .onUnmappableCharacter(CodingErrorAction.REPLACE)
              .reset();
            if (cd instanceof ArrayDecoder) {
                int clen = ((ArrayDecoder)cd).decode(ba, off, len, ca);
                return safeTrim(ca, clen, cs, isTrusted);
            } else {
                ByteBuffer bb = ByteBuffer.wrap(ba, off, len);
                CharBuffer cb = CharBuffer.wrap(ca);
                try {
                    CoderResult cr = cd.decode(bb, cb, true);
                    if (!cr.isUnderflow())
                        cr.throwException();
                    cr = cd.flush(cb);
                    if (!cr.isUnderflow())
                        cr.throwException();
                } catch (CharacterCodingException x) {
                    // Substitution is always enabled,
                    // so this shouldn't happen
                    throw new Error(x);
                }
                return safeTrim(ca, cb.position(), cs, isTrusted);
            }
        }
    
     private static char[] safeTrim(char[] ca, int len,
                                       Charset cs, boolean isTrusted) {
            if (len == ca.length && (isTrusted || System.getSecurityManager() == null))
                return ca;
            else
                return Arrays.copyOf(ca, len);
        }
    

    這個方法構造的方法的複雜之處就是在於對於指定編碼的處理,但是我們如果看完這個方法調用的整個流程最終還是落到

    return Arrays.copyOf(ca, len);
    返回一個指定編碼的字符數組,然後和String類的value屬性建立聯繫
    

    字節數組構造方法的基本邏輯:就是將字節數組轉化為字符數組,再和String的value屬性建立聯繫,完成初始化。

  4. String(StringBuilder builder) 和String(StringBuffer buffer)與String擴展類相關的構造方法:

    這個構造方法就是將StringBuilder或者StringBuffer類初始化String類,源碼如下:

    public String(StringBuffer buffer) {
            synchronized(buffer) {
                this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
            }
        }
    
     public String(StringBuilder builder) {
            this.value = Arrays.copyOf(builder.getValue(), builder.length());
        }
    

    分析:

    核心的代碼還是這一句:

      this.value = Arrays.copyOf(builder.getValue(), builder.length());
    

​ 在往下看builder.getValue(),的源碼

   final char[] getValue() {
        return value;
    }
返回一個字符數組

這樣就能很好理解這個構造方法了: 先利用builder.getValue()將指定的類型轉化為字符數組,通過Arrays.copyOf()方法進行拷貝,將返回的數組賦值給String的屬性value,完成初始化。

2.2常用的方法分析

String的方法大概有60多個,這裏只分析幾個常用的方法,了解其他的方法,可以移步javaSE官方文檔:

  1. 字符串轉化為字符的方法:charAt(int index)

     public char charAt(int index) {
             //1. 判斷異常
            if ((index < 0) || (index >= value.length)) {
                throw new StringIndexOutOfBoundsException(index);
            }
            // 2.返回指定位置的字符
            return value[index];
        }
    

    用法示例:

            String StrChar = "chen";
            char getChar = StrChar.charAt(1);
            System.out.println(getChar);
    輸出:
        h
    

    2.字符串轉化為字節數組的方法:getBytes()

    // 源碼
    public byte[] getBytes() {
        return StringCoding.encode(value, 0, value.length);
    }
    
    // encode的源碼
    static byte[] encode(char[] ca, int off, int len) {
            String csn = Charset.defaultCharset().name();
            try {
                // use charset name encode() variant which provides caching.
                return encode(csn, ca, off, len);
            } catch (UnsupportedEncodingException x) {
                warnUnsupportedCharset(csn);
            }
            try {
                return encode("ISO-8859-1", ca, off, len);
            } catch (UnsupportedEncodingException x) {
                // If this code is hit during VM initialization, MessageUtils is
                // the only way we will be able to get any kind of error message.
                MessageUtils.err("ISO-8859-1 charset not available: "
                                 + x.toString());
                // If we can not find ISO-8859-1 (a required encoding) then things
                // are seriously wrong with the installation.
                System.exit(1);
                return null;
            }
        }
    

​ 用法示例:

       String strByte = "chen";
       
       // 將string轉化為字節數組
       byte[] getBytes = strByte.getBytes();
       
       // 遍歷輸出
        for (byte getByte : getBytes) {
            System.out.println(getByte);
        }
 輸出結果:
     99
     104
     101
     110

3.返回字符串中指定字符的下標的方法: indexOf(String str):

這裏的參數為字符串:

這個方法一共涉及了四個方法,源碼如下:

 public int indexOf(String str) {
        return indexOf(str, 0);
    }

 public int indexOf(String str, int fromIndex) {
        return indexOf(value, 0, value.length,
                str.value, 0, str.value.length, fromIndex);
    }

 static int indexOf(char[] source, int sourceOffset, int sourceCount,
            String target, int fromIndex) {
        return indexOf(source, sourceOffset, sourceCount,
                       target.value, 0, target.value.length,
                       fromIndex);
    }

static int indexOf(char[] source, int sourceOffset, int sourceCount,
            char[] target, int targetOffset, int targetCount,
            int fromIndex) {
    
        // 1. 判斷範圍
        if (fromIndex >= sourceCount) {
            return (targetCount == 0 ? sourceCount : -1);
        }
        if (fromIndex < 0) {
            fromIndex = 0;
        }
        if (targetCount == 0) {
            return fromIndex;
        }
       
        // 2,判斷目標字符串是否時原子符串的子序列,並返回目標序列的第一個字符在原字符序列的索引。
        char first = target[targetOffset];
        int max = sourceOffset + (sourceCount - targetCount);

        for (int i = sourceOffset + fromIndex; i <= max; i++) {
            /* Look for first character. */
            if (source[i] != first) {
                while (++i <= max && source[i] != first);
            }

            /* Found first character, now look at the rest of v2 */
            if (i <= max) {
                int j = i + 1;
                int end = j + targetCount - 1;
                for (int k = targetOffset + 1; j < end && source[j]
                        == target[k]; j++, k++);

                if (j == end) {
                    /* Found whole string. */
                    return i - sourceOffset;
                }
            }
        }
        return -1;
    }

具體執行過程已在方法的註釋中進行了說明:

用法示例:

 String strIndex = "chen";
         int getIndex = strIndex.indexOf("he");
        System.out.println(getIndex);
輸出:
    1

注意:也就是輸出字符串中的第一個字符在原子符串中的索引,前提是傳入的參數必須是原子符串的子序列,以上面的列子為例,傳入的字符串序列必須是chen這個字符串的子序列,才能輸出正確的索引,比如傳入的序列不是chen的子序列,輸出為-1

 String strIndex = "chen";
         int getIndex = strIndex.indexOf("hew");
        System.out.println(getIndex);
輸出:
    -1        

使用這個方法時,這點非常注意。

4.將字符串中的某個字符進行替換:replace(char oldChar, char newChar)

參數就是被替換的字符和新的字符,源碼如下:

public String replace(char oldChar, char newChar) {
    
    if (oldChar != newChar) {
        int len = value.length;
        int i = -1;
        char[] val = value; /* avoid getfield opcode */
        
        // 1.遍歷字符數組,找到原子符的位置
        while (++i < len) {
            if (val[i] == oldChar) {
                break;
            }
        }
        //2. 聲明一個臨時的字符數組,用來存儲替換后的字符串,
        if (i < len) {
            char buf[] = new char[len];
            for (int j = 0; j < i; j++) {
                // 3. 將原字符數組拷貝到新的字符數組中去
                buf[j] = val[j];
            }
            while (i < len) {
                char c = val[i];
                // 4.
                buf[i] = (c == oldChar) ? newChar : c;
                i++;
            }
            // 3. 初始化一個新的字符串
            return new String(buf, true);
        }
    }
    return this;
}

具體的執行邏輯就是註釋的語句。

用法示例:

 String strIndex = "chee";
        String afterReplace = strIndex.replace('e','n');
        System.out.println(afterReplace);
輸出:
    chnn

注意:這裏的替換是字符串中的所有與舊字符的相同的字符,比如上面的這個例子,就是將原子符中的e全部替換為n。

5.字符串的分隔:split(String regex) :

源碼如下:

   public String[] split(String regex) {
        return split(regex, 0);
    }

  public String[] split(String regex, int limit) {
        /* fastpath if the regex is a
         (1)one-char String and this character is not one of the
            RegEx's meta characters ".$|()[{^?*+\\", or
         (2)two-char String and the first char is the backslash and
            the second is not the ascii digit or ascii letter.
         */
        char ch = 0;
        if (((regex.value.length == 1 &&
             ".$|()[{^?*+\\".indexOf(ch = regex.charAt(0)) == -1) ||
             (regex.length() == 2 &&
              regex.charAt(0) == '\\' &&
              (((ch = regex.charAt(1))-'0')|('9'-ch)) < 0 &&
              ((ch-'a')|('z'-ch)) < 0 &&
              ((ch-'A')|('Z'-ch)) < 0)) &&
            (ch < Character.MIN_HIGH_SURROGATE ||
             ch > Character.MAX_LOW_SURROGATE))
        {
            int off = 0;
            int next = 0;
            boolean limited = limit > 0;
            ArrayList<String> list = new ArrayList<>();
            while ((next = indexOf(ch, off)) != -1) {
                if (!limited || list.size() < limit - 1) {
                    list.add(substring(off, next));
                    off = next + 1;
                } else {    // last one
                    //assert (list.size() == limit - 1);
                    list.add(substring(off, value.length));
                    off = value.length;
                    break;
                }
            }
            // If no match was found, return this
            if (off == 0)
                return new String[]{this};

            // Add remaining segment
            if (!limited || list.size() < limit)
                list.add(substring(off, value.length));

            // Construct result
            int resultSize = list.size();
            if (limit == 0) {
                while (resultSize > 0 && list.get(resultSize - 1).length() == 0) {
                    resultSize--;
                }
            }
            String[] result = new String[resultSize];
            return list.subList(0, resultSize).toArray(result);
        }
        return Pattern.compile(regex).split(this, limit);
    }

用法示例:

// 將一句話使用空格進行分隔 
String sentence = "People who hear thumb up can get rich";
        
       // 使用空格進行分隔
       String[] subSequence = sentence.split("\\s");
        for (String s : subSequence) {
            System.out.println(s);
    }

輸出:
    People
    who
    hear
    thumb
    up
    can
    get
    rich

注意:使用這個方法,對正則表達式有所了解,才能實現更強大的功能,正則表達式的學習,可以移步菜鳥教程

6.實現字符串的指定範圍的切割substring(int beginIndex, int endIndex):

源碼如下:

 public String substring(int beginIndex, int endIndex) {
     
        // 1.判斷異常
        if (beginIndex < 0) {
            throw new StringIndexOutOfBoundsException(beginIndex);
        }
        if (endIndex > value.length) {
            throw new StringIndexOutOfBoundsException(endIndex);
        }
        // 2,確定切割的長度
        int subLen = endIndex - beginIndex;
        if (subLen < 0) {
            throw new StringIndexOutOfBoundsException(subLen);
        }
        // 3.使用構造方法,返回切割后字符串
        return ((beginIndex == 0) && (endIndex == value.length)) ? this
                : new String(value, beginIndex, subLen);
    }

具體的執行邏輯如註釋所示,這個方法的邏輯總體比較簡單:

具體用法:

     String sentence = "hhchennn";
       String subSequence = sentence.substring(2,6);
        System.out.println(subSequence);
輸出:
    chen

注意:從源碼了解到,這個方法的在切割的時候,一般將第一個參數包含,包含第二個參數,也就是說上面的例子中在切割後的字符串中包含2這個字符,但是不包含6這個字符。

7.當然處理這些還有一些常用的方法,比如:

// 1.去除字符串前後空格的方法
trim()
    
//2.大小寫轉換的方法
 toLowerCase()
 toUpperCase()
    
//3. 將字符串轉化為數組
 toCharArray()
    
// 4.將基本類型轉化字符串    
 valueOf(boolean b)
    
// 5.返回對象本身的字符串形式   
 toString()

這些方法使用起來都比較簡單,強烈建議看看java官方文檔。

3.面試常問

3.1. 為什麼是不可變的

1、什麼是不可變?

從java角度來講就是說成final的。參考Effective Java中第15條使可變性最小化中對不可變類的解釋:

不可變類只是其實例不能被修改的類。每個實例中包含的所有信息都必須在創建該實例的時候就提供,並且在對象的整個生命周期內固定不變。為了使類不可變,要遵循下面五條規則:

1. 不要提供任何會修改對象狀態的方法。

2. 保證類不會被擴展。 一般的做法是讓這個類稱為 `final` 的,防止子類化,破壞該類的不可變行為。

3. 使所有的域都是 final 的。

4. 使所有的域都成為私有的。 防止客戶端獲得訪問被域引用的可變對象的權限,並防止客戶端直接修改這些對象。

5. 確保對於任何可變性組件的互斥訪問。 如果類具有指向可變對象的域,則必須確保該類的客戶端無法獲得指向這些對象的引用。

當然在Java平台類庫中,包含許多不可變類,例如String ,基本類型的包裝類,BigInteger, BigDecimal等等。綜上所述,不可變類具有一些顯著的通用特徵:類本身是final修飾的;所有的域幾乎都是私有final的;不會對外暴露可以修改對象屬性的方法。通過查閱String的源碼,可以清晰的看到這些特徵。

2.為什麼不可變

String real = "chen"
 real = "Wei";

下圖就很好解釋了代碼的執行過程:

執行第一行代碼時,在堆上新建一個對象實例chen,real是一個指向該實例的引用,引用包含的僅僅只是實例在堆上的內存地址而已。執行第二行代碼時,僅僅只是改變了real這個引用的地址,指向了另一個實例wei。所以,正如前面所說過的,不可變類只是其實例不能被修改的類。給real重新賦值僅僅只是改變了它的引用而已,並不會真正去改變它本來的內存地址上的值。這樣的好處也是顯而易見的,最簡單的當存在多個String的引用指向同一個內存地址時,改變其中一個引用的值並不會對其他引用的值造成影響。

那麼,String是如何保持不可變性的呢?結合Effective Java中總結的五條原則,閱讀它的源碼之後就一清二楚了。

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

    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    private static final long serialVersionUID = -6849794470754667710L;

    private static final ObjectStreamField[] serialPersistentFields =
        new ObjectStreamField[0];

String類是final修飾的,滿足第二條原則:保證類不會被擴展。分析一下它的幾個域:

  • private final char value[] :可以看到Java還是使用字節數組來實現字符串的,並且用final修飾,保證其不可變性。這就是為什麼String實例不可變的原因。
  • private int hash : String的哈希值緩存
  • private static final long serialVersionUID = -6849794470754667710L : String對象的 serialVersionUID
  • private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0] : 序列化時使用

其中最主要的域就是value,代表了String對象的值。由於使用了private final修飾,正常情況下外界沒有辦法去修改它的值的。正如第三條使所有的域都是final的。和第四條使所有的域都成為私有的所描述的。難道這樣一個private加上final就可以保證萬無一失了嗎?看下面代碼示例:

    final char[] value = {'a', 'b', 'c'};
    value[2] = 'd';

這時候的value對像在內存中已經是a b d了。其實final修飾的僅僅只是value這個引用,你無法再將value指向其他內存地址,例如下面這段代碼就是無法通過編譯的:

    final char[] value = {'a', 'b', 'c'};
    value = {'a', 'b', 'c', 'd'};

所以僅僅通過一個final是無法保證其值不變的,如果類本身提供方法修改實例值,那就沒有辦法保證不變性了。Effective Java中的第一條原則不要提供任何會修改對象狀態的方法。String類也很好的做到了這一點。在String中有許多對字符串進行操作的函數,例如substring concat replace replaceAll等等,這些函數是否會修改類中的value域呢?我們看一下concat()函數的內部實現:

public String concat(String str) {
    int otherLen = str.length();
    if (otherLen == 0) {
        return this;
    }
    int len = value.length;
    char buf[] = Arrays.copyOf(value, len + otherLen);
    str.getChars(buf, len);
    return new String(buf, true);
}

注意其中的每一步實現都不會對value產生任何影響。首先使用Arrays.copyOf()方法來獲得value的拷貝,最後重新new一個String對像作為返回值。其他的方法和contact一樣,都採取類似的方法來保證不會對value造成變化。的的確確,String類中並沒有提供任何可以改變其值的方法。相比final而言,這更能保障String不可變。

3.不可變類的好處:

Effective Java 中總結了不可變類的特點。

  • 不可變類比較簡單。
  • 不可變對象本質上是線程安全的,它們不要求同步。不可變對象可以被自由地共享。
  • 不僅可以共享不可變對象,甚至可以共享它們的內部信息。
  • 不可變對象為其他對象提供了大量的構建。
  • 不可變類真正唯一的缺點是,對於每個不同的值都需要一個單獨的對象。

3.2. 使用什麼方式可以改變String類的不可變性

當然使用反射,java的反射機制可以做到我們平常做不到的很多事情:

        String str = "chen";
        System.out.println(str);
        Field field = String.class.getDeclaredField("value");
        field.setAccessible(true);
        char[] value = (char[]) field.get(str);
        value[1] = 'a';
        System.out.println(str);

執行結果:
  chen
  caen  

3.3. String和stringBuffer和StringBuilder的區別

從以下三個方面來考慮他們之間的異同點:

1.可變和不可變性:

String: 字符串常量,在修改時,不會改變自身的值,若修改,就會重新生成新的字符串對象。

StringBuffer: 在修改時會改變對象本身,不會生成新的對象,使用場景:對字符經常改變的情況下,主要方法: append(),insert() 等。

2.線程是否安全

String: 定義之後不可改變,線程安全

String Buffer: 是線程安全的,但是執行效率比較低,適用於多線程下操作字符串緩衝區的大量數據。

StringBuilder: 線程不安全的,適用於單線程下操作字符串緩衝區的大量數據

3.共同點

StringBuilder和StringBuffer有共有的父類AbstractStringBuilder(抽象類)。

StringBuilder,StringBuffer的方法都會調用AbstractStringBuilder中的公共方法,如: super().append()…

只是StringBuffer會在方法上加上synchronized關鍵字,進行同步。

4.優秀的工具包推薦

4.1guava

Guava工程包含了若干被Google的Java項目廣泛依賴的核心庫,例如:集合[collections] 、緩存[caching] 、原生類型支持[primitives support] 、併發庫[concurrency libraries] 、通用註解[common annotations] 、字符串處理[string processing] 、I/O 等等。所有這些工具每天都在被Google的工程師應用在產品服務中。具體的中文參考文檔,Guava中文參考文檔。

4.2 Hutool

Hutool是一個Java工具包,也只是一個工具包,它幫助我們簡化每一行代碼,減少每一個方法,讓Java語言也可以“甜甜的”。Hutool最初是我項目中“util”包的一個整理,後來慢慢積累並加入更多非業務相關功能,並廣泛學習其它開源項目精髓,經過自己整理修改,最終形成豐富的開源工具集。Hutool參考文檔

追本溯源,方能闊步前行

參考資料:

參考博客:

​ https://juejin.im/post/59cef72b518825276f49fe40

​ https://www.cnblogs.com/ChrisMurphy/p/4760197.html

參考書籍: java官方文檔《深入理解JVM虛擬機》

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※回頭車貨運收費標準

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※推薦評價好的iphone維修中心

※教你寫出一流的銷售文案?

※台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

※台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

好站推薦

  • 健康醫療 減重知識專區
  • 婚紗世界 婚紗攝影寫真網
  • 成人話題 未滿18請勿進入
  • 流行時尚 時下流行愛美情報
  • 理財資訊 當舖借貸信用卡各式理財方法
  • 生活情報 各行各業情報資訊
  • 科技資訊 工業電子3C產品
  • 網路資訊 新奇趣味爆笑內容
  • 美食分享 全台各式名產 伴手禮
  • 裝潢設計 買屋賣屋裝修一羅框
  • 視覺設計 T恤、團體服、制服、polo衫

近期文章

  • 奧方地產11.23億元競得廣州增城1宗商住用地
  • 廣州高新投資20.18億元競得廣州黃埔區1宗商業用地
  • 廣州開發投資聯合體5.91億元競得廣州黃埔1宗商業用地
  • 廣東開新睿智生物1961萬元競得廣州1宗工業用地
  • 廣州130.87億元出讓7宗地塊 龍湖、香江控股、越秀各競得1宗

標籤

USB CONNECTOR  南投搬家公司費用 古典家具推薦 台中一中住宿 台中一中民宿 台中室內設計 台中室內設計師 台中室內設計推薦 台中搬家 台中電動車 台北網頁設計 台東伴手禮 台東名產 地板施工 大圖輸出 如何寫文案 婚禮錄影 宜蘭民宿 家具工廠推薦 家具訂製工廠推薦 家具訂製推薦 實木地板 復刻家具推薦 新竹婚宴會館 木地板 木質地板 柚木地板 桃園機場接送 桃園自助婚紗 沙發修理 沙發換皮 海島型木地板 牛軋糖 租車 網站設計 網頁設計 網頁設計公司 貨運 超耐磨木地板 銷售文案 隱形鐵窗 電動車 馬賽克拼貼 馬賽克磁磚 馬賽克磚

彙整

  • 2021 年 3 月
  • 2021 年 2 月
  • 2021 年 1 月
  • 2020 年 12 月
  • 2020 年 11 月
  • 2020 年 10 月
  • 2020 年 9 月
  • 2020 年 8 月
  • 2020 年 7 月
  • 2020 年 6 月
  • 2020 年 5 月
  • 2020 年 4 月
  • 2020 年 3 月
  • 2020 年 2 月
  • 2020 年 1 月
  • 2019 年 12 月
  • 2019 年 11 月
  • 2019 年 10 月
  • 2019 年 9 月
  • 2019 年 8 月
  • 2019 年 7 月
  • 2019 年 6 月
  • 2019 年 5 月
  • 2019 年 4 月
  • 2019 年 3 月
  • 2019 年 2 月
  • 2019 年 1 月
  • 2018 年 12 月
©2021 值得您信賴的旅遊品牌 | 團體旅遊、自由行的專家‎ | Built using WordPress and Responsive Blogily theme by Superb