深入Java基礎(二)——字符串家族

上一篇寫了基本數(shù)據(jù)類及其包裝類詳解,這篇我們就來討字符串這個家族吧祟印,這個也是重中之重半饶!T桃洹颤芬!

文章結(jié)構(gòu):1.眾說紛紛的equals和==;2.字符串家族的基本知識以及字符串家族的源碼解讀套鹅;3.字符串家族的坑以及使用推薦站蝠;


一、眾說紛紛的equals和==:

總述:1. ==對于基本類型是比較其值卓鹿,對于引用類型是比較地址菱魔,地址也可以是一個基本類型的值,因此可認為就是比較值的吟孙。2.equals只能用于對象的比較豌习,是所有類的一個基本方法存谎。如果用equals來比較基本類型的變量是有語法錯誤的,equals只是比較對象的內(nèi)容肥隆。

(博主神經(jīng)的牛角尖:假設兩個對象用==比較既荚,兩個基本數(shù)據(jù)類型也用==比較,栋艳,它們是基于什么判斷的恰聘,博主想了好久,想不通吸占,以后有機會看編譯原理的時候再出一篇)晴叨。

例子解析(輸出什么?矾屯?當然要大家親自去編譯查看啦兼蕊,這才有好效果嘛。)

public class CompareTest {
    public  static void main(String[] args){
        int t1=30;
        int t2=90;
        int t3=120;
        int t4=120;
        Boolean result1=(t1==t2);       //驗證不同值的比較是否相等
        Boolean result2=((t1+t2)==t3);      //驗證基本數(shù)據(jù)類型只要數(shù)值相等即相等
        Boolean result3=(t3==t4);           //驗證基本數(shù)據(jù)類型直接相等即相等

        System.out.println("--【t1==t2】"+result1+"-----【(t1+t2)=t3】"+result2+"-----【t3=t4】"+result3);

        //另外博主驗證過了件蚕,只要在Integer緩存大兴锛肌(-128-127)以內(nèi),只要數(shù)值相等排作,還是相等的牵啦。覺得大家應該動手試下這個就不貼太多出來了。
        Integer s1 = Integer.valueOf(t1);       //把基本數(shù)據(jù)類型傳遞給Integer包裝類構(gòu)建成對象
        Integer s2 = Integer.valueOf(t2);
        Integer s3 = Integer.valueOf(t3);
        Integer s4 = Integer.valueOf(t4);
        Integer s5 = Integer.valueOf(130);
        Integer s6 = Integer.valueOf(130);

        Boolean b1 = ((s1+s2)==s3);         //驗證只要數(shù)值相等妄痪,還是相等的哈雏。即使它是一個Integer對象相加
        Boolean b2 = s3.equals(s3);         //驗證使用equals對象比較,因為還是在緩存區(qū)域以內(nèi)衫生,所以當然相等啦裳瘪。
        Boolean b3 = (s3==s4);          //驗證了Integer對象的緩存-128~127以內(nèi),值相等即可相等啦罪针。但是只要超出緩存區(qū)域盹愚,就不相等了。
        //以下就思考地址與對象的比較關(guān)系啦
        Boolean b4 = (s5==s6);          //驗證啦超出緩存的對象啦站故,當然不相等啦。而且這個是對象地址的比較
        Boolean b5 = s5.equals(s6);     //驗證比較兩個擁有相同屬性值的對象比較的話讨惩,就相等了嘛对竣。只是比較對象的值踪危,也就是對象的內(nèi)容而已。

        System.out.println("---【(s1+s2)==s3】"+b1+"-----【s3.equals(s3)】"+b2+"-----【s3==s4】"+b3+"-----【s5==s6】"+b4+"-----【s5.equals(s6)】"+b5);
    }
}

補充:

“==”比較的是值--變量(棧)內(nèi)存中存放的對象的(堆)內(nèi)存地址

equals用于比較兩個對象的值是否相同--不是比地址岂津,是比較內(nèi)容。

【特別注意】Object類中的equals方法和“==”是一樣的悦即,沒有區(qū)別吮成,而String類橱乱,Integer類等等一些類,是重寫了equals方法粱甫,才使得equals和“==不同”(這個可參看我上一篇博客基本數(shù)據(jù)類及其包裝類詳解)泳叠,所以,當自己創(chuàng)建類時茶宵,自動繼承了Object的equals方法危纫,要想實現(xiàn)不同的等于比較,必須重寫equals方法乌庶。

另外种蝶,"=="比"equal"運行速度快,因為"=="只是比較引用。


二瞒大、字符串家族的基本知識:

(1)String:是不可變類螃征,即一旦String對象被創(chuàng)建,包含這個對象的字符序列是不可改變的透敌。

我們結(jié)合源碼來看下重要的部分代碼

//String類由final修飾盯滚,故String不能被繼承。并且被標記了可序列化
//CharSequence 是字符串協(xié)議接口
public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
        /** The value is used for character storage. */
        //看到源碼也這么說了拙泽,用于字符存儲淌山,并且它是一個final修飾的屬性,所以String一旦創(chuàng)建即不可被修改顾瞻。因此所有對String字符串的修改(如追加字符串泼疑,刪除部分字符串,截取字符串)都不是在原來的對象基礎上修改荷荤,而是新建一個String對象修改并返回退渗,這會造成原對象被廢棄,浪費資源且性能較差(特別是追加字符串和刪除部分字符串)蕴纳,若遇到字符串將被頻繁修改的情況会油,建議不要使用String,改用StringBuffer或StringBuilder。
    private final char value[];
       /**String類型的hash值**/
    private int hash; // Default to 0
    //數(shù)了下7個構(gòu)造器古毛,分析些常用和基本類似的吧翻翩。我們在生成一個String對象的時候必須對該對象的offset、count稻薇、value三個屬性進行賦值嫂冻,這樣我們才能獲得一個完成的String類型。
    //這個就是直接賦值給字符存儲的數(shù)組嘛塞椎。默認value為一個長度為0的數(shù)組桨仿,所以為null。
    public String() {
        this.value = new char[0];
    }
    //初始化新創(chuàng)建的字符串對象案狠,它代表相同的字符序列的參數(shù).新創(chuàng)建的字符串是參數(shù)字符串的副本(就是增刪改string的時候的string對象)服傍,此構(gòu)造函數(shù)的使用是因為字符串是不可變的钱雷。
    public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }
    //就是可以傳入一個字符數(shù)組嘛。而且還是這個問題吹零,string不可變罩抗,你一創(chuàng)建就被copy了。隨后修改的字符對象已經(jīng)不是原來的對象了瘪校。
     public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);
    }
    //跟上面的構(gòu)造器差不多澄暮,多點規(guī)范
    public String(char value[], int offset, int count) {
        if (offset < 0) {
            throw new StringIndexOutOfBoundsException(offset);//起始值下標小于0,拋異常  
        }
        if (count < 0) {
            throw new StringIndexOutOfBoundsException(count);//取值長度小于0阱扬,拋異常  
        }
        // Note: offset or count might be near -1>>>1.
        if (offset > value.length - count) {
            throw new StringIndexOutOfBoundsException(offset + count);//起始值下標加長度大于數(shù)組長度泣懊,拋異常  
        }
        this.value = Arrays.copyOfRange(value, offset, offset+count);
    }
    //在構(gòu)造對象時,傳入了下標以及長度
    public String(int[] codePoints, int offset, int count) {
        if (offset < 0) {
            throw new StringIndexOutOfBoundsException(offset);
        }
        if (count < 0) {
            throw new StringIndexOutOfBoundsException(count);
        }
        // Note: offset or count might be near -1>>>1.
        if (offset > codePoints.length - count) {
            throw new StringIndexOutOfBoundsException(offset + count);
        }
    //構(gòu)造對象后:(步驟一)精確計算String所需要的長度麻惶。自動計算string所需長度馍刮。
    int n = count;
    for (int i = offset; i < end; i++) {
            int c = codePoints[i];
            if (Character.isBmpCodePoint(c))
                continue;
            else if (Character.isValidCodePoint(c))
                n++;
            else throw new IllegalArgumentException(Integer.toString(c));
    }
    //構(gòu)造對象后:(步驟二)分配和填充字符對象。提取每一位的字符窃蹋,并將其放入String字符串卡啰。
    final char[] v = new char[n];

        for (int i = offset, j = 0; i < end; i++, j++) {
            int c = codePoints[i];
            if (Character.isBmpCodePoint(c))
                v[j] = (char)c;
            else
                Character.toSurrogates(c, v, j++);
        }
        this.value = v;
    }
    //可以看到StringBuffer 和StringBuilder 傳進來也可成為String對象。但是三者并沒繼承關(guān)系警没。
    public String(StringBuffer buffer) {
    //嘿嘿匈辱,神奇的一個線程關(guān)鍵字synchronized。一會有解析
        synchronized(buffer) {
            this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
        }
    }
    public String(StringBuilder builder) {
        this.value = Arrays.copyOf(builder.getValue(), builder.length());
    }
    //很普通的方法啦
    public int length() {
        return value.length;
    }
     public boolean isEmpty() {
        return value.length == 0;
    }
    //用于取得字符串下標為i的字符
    public char charAt(int index) {
        if ((index < 0) || (index >= value.length)) {
            throw new StringIndexOutOfBoundsException(index);
        }
        return value[index];
    }
    //返回指定索引處的字符(Unicode代碼點)杀迹。該索引引用char值(Unicode代碼單元)亡脸,其范圍從 0 到 length() - 1。就是返回一個Unicode值树酪。
    public int codePointAt(int index) {
        if ((index < 0) || (index >= value.length)) {
            throw new StringIndexOutOfBoundsException(index);
        }
        return Character.codePointAtImpl(value, index, value.length);
    }
    //上面解析過浅碾,Integer,String這些類都是重寫了equals才有不同的效果续语。比較字符串的值是否相同 垂谢。
     public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;//若比較的兩個對象引用地址值相同,則為同一個對象疮茄,值當然相同  
        }
        if (anObject instanceof String) {//String與另一個對象能比較的前提是滥朱,對方也屬于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) {//將兩個String對象的值放入數(shù)組中,遍歷比較懂版,全部相同才表示相同 
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }
    //如果是直接String比較,很快的嘛躏率。
    public boolean equalsIgnoreCase(String anotherString) {
        return (this == anotherString) ? true
                : (anotherString != null)
                && (anotherString.value.length == value.length)
                && regionMatches(true, 0, anotherString, 0, value.length);
    }
    //比較兩個字符串字典躯畴。什么意思呢民鼓?比較是基于字符串中的每個字符的Unicode值,就是遍歷去比較兩個字符串的每個字符Unicode值大小蓬抄。其結(jié)果是負的整數(shù)丰嘉,如果此String對象字典前面的參數(shù)字符串;其結(jié)果是一個正整數(shù)嚷缭,如果此String對象字典如下的參數(shù)字符串饮亏;結(jié)果是零,如果兩個字符串相等阅爽,CompareTo返回0時路幸,equal(Object)方法將返回true。
    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;
    }
    //判斷一個字符串是否以prefix字符串開頭付翁,toffset是相同的長度
     public boolean startsWith (String prefix,int toffset){
            char ta[] = value;
            int to = offset + toffset;
            char pa[] = prefix.value;
            int po = prefix.offset;
            int pc = prefix.count;
            // Note: toffset might be near -1>>>1.
            if ((toffset < 0) || (toffset > count - pc)) {
                return false;
            }
            while (--pc >= 0) {
                if (ta[to++] != pa[po++]) {
                    return false;
                }
            }
            return true;
     }
     //連接兩個字符串
   public String concat (String str){
            int otherLen = str.length();
            if (otherLen == 0) {
                return this;
            }
            char buf[] = new char[count + otherLen];
            getChars(0, count, buf, 0);
            str.getChars(0, otherLen, buf, count);
            return new String(0, count + otherLen, buf);//哈哈哈简肴,看到了吧,都是新建一個String對象百侧,然后返回砰识。
      }
      //String與基本類型的包裝類轉(zhuǎn)換源碼啦。都是靜態(tài)方法
    public static String valueOf(boolean b) {
        return b ? "true" : "false";
    }
    //這個比較有意思佣渴,用了一個非public的構(gòu)造器辫狼,不過還是直接賦值給value屬性去存儲而已。但前提是傳入一個字節(jié)數(shù)組辛润,而不是字節(jié)
    public static String valueOf(char c) {
        char data[] = {c};
        return new String(data, true);
    }
    public static String valueOf(int i) {
        return Integer.toString(i);
    }
     public static String valueOf(long l) {
        return Long.toString(l);
    }
    public static String valueOf(float f) {
        return Float.toString(f);
    }
    //String截取方法膨处,傳入截取開始的下標 
    public String substring(int beginIndex) { 
        if (beginIndex < 0) {  
            throw new StringIndexOutOfBoundsException(beginIndex);  
        }  
        int subLen = value.length - beginIndex;  
        if (subLen < 0) {  
            throw new StringIndexOutOfBoundsException(subLen);  
        }  
        return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);//當傳入的開始下標符合且不為0時,新建一個String,注意這個String的值并沒有變化频蛔,只是改變了偏移量  
        //傳入開始index以及結(jié)束下標去截取
   public 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);  
        }  
        return ((beginIndex == 0) && (endIndex == value.length)) ? this  
                : new String(value, beginIndex, subLen);//與上面的方法類似灵迫,沒有改變String的value屬性,只是而是改變了偏移量和count長度晦溪。  
    }  
    public String replace(char oldChar, char newChar) {  
        if (oldChar != newChar) {  
            int len = value.length;//替代的是整個value中的oldChar瀑粥,而不是從偏移量開始替代  
            int i = -1;  
            char[] val = value;  
  
            while (++i < len) {//先遍歷數(shù)組中是否有原字母,沒有就無需替換三圆,高效的設計  
                if (val[i] == oldChar) {  
                    break;  
                }  
            }  
            if (i < len) {//獲得需要替換的char的下標,此下表以前的char直接復制路媚,  
            此下標以后的char才開始一個一個比較围苫,若等于oldchar則替換剃盾,高效  
                char buf[] = new char[len];  
                for (int j = 0; j < i; j++) {  
                    buf[j] = val[j];//  
                }  
                while (i < len) {  
                    char c = val[i];  
                    buf[i] = (c == oldChar) ? newChar : c;  
                    i++;  
                }  
                return new String(buf, true);  
            }  
        }  
        return this;  
    }  
  
}  

}

嗯闰歪,String常用的源碼基本解析完了。下面我們看些有趣的問題去思考饱亿。

下面就是java對String的深度優(yōu)化問題啦。很恐怖。

String對象的地址問題腐巢,異同問題以及java對string的優(yōu)化問題

public class StringTest {
    public static void main(String[] args) {
        String a="fuzhu";   //這樣的方式也是創(chuàng)建一個string對象!泞莉!這樣創(chuàng)建是放在常量池捺弦,new的方式是放在堆中寞钥。
        String b="fuzhu";
        String c=new String ("fuzhu");
        String d=new String ("fuzhu");
        System.out.println(a==b);//true,fuzhu被創(chuàng)建在String Pool中,a和b會指向同一個對象,
        System.out.println(a==c);//a指向的fuzhu被創(chuàng)建在String Pool中窝剖,而c所指向的對象被創(chuàng)建在heap中狂魔,兩者為不同的對象淫痰,地址值不同
        System.out.println(d==c);//c和d所指的對象都被創(chuàng)建在堆中最楷,但是兩個不同的對象,地址值不同
        System.out.println("---------重要的優(yōu)化問題!W阉铩烈评!----------");
        String e = "ab";
        String f = "a" + "b";
        System.out.println((e == f));//答案是什么?犯建?是true=补凇!因為什么适瓦?因為java對String有個字符串常量池竿开,恐怖優(yōu)化!下面有解析玻熙。
        System.out.println("-----------驗證什么時候是常量表達式7癫省!--------");
        String g = "a1";
        String h = "a" + 1;
        System.out.println((g == h)); //result = true
        String i = "a" + true;          //String i = "atrue";
        String j = "atrue"  ;
        System.out.println((i == j)); //result = true
        String k = "a" + 3.4;       // String k = "a3.4";
        String l = "a3.4" ;
        System.out.println((k == l)); //result = true
        System.out.println("---------驗證非常量表達式的情況----------");
        String o = "ab";
        String p = "b";
        String q = "a" + p;
        System.out.println((o == q)); //result = false
    }
}

1. String s="a"是一種非常特殊的形式,和new 有本質(zhì)的區(qū)別嗦随。它是java中唯一不需要new 就可以產(chǎn)生對象的途徑列荔。以 String s="a";形式賦值在java中叫直接量,它是在常量池中而不是象new 一樣放在壓縮堆中.

2. String s="a"這種形式的字符串,在JVM內(nèi)部發(fā)生字符串拘留,即當聲明這樣的一個字符串后,JVM會在常量池中先查找有有沒有一個值為"a"的對象,如果有,就會把它賦給當前引用.即原來那個引用和現(xiàn)在這個引用指點向了同一對象,如果沒有,則在常量池中新創(chuàng)建一個"a",下一次如果有String s2 = "1";又會將s1指向"abcd"這個對象,即以這形式聲明的字符串,只要值相等,任何多個引用都指向同一對象.

3. 解析直接創(chuàng)建的java對string的優(yōu)化過程:編譯優(yōu)化+ 常量池。String b = "a" + "b";編譯器將這個"a" + "b"作為常量表達式枚尼,在編譯時進行優(yōu)化贴浙,直接取結(jié)果"ab"。

至于怎么驗證真的是直接仁鸹小悬而??當然只有看編譯的二進制代碼啦锭汛,我們可下載個ultraedit工具笨奠,去查看兩個一模一樣的類的十六進制。就可以看出來了唤殴。

4. 什么時候是常量表達式般婆,什么時候不是?朵逝?

肯定是的:(1)String + String(這的string指的是直接量)蔚袍;(2)string + 基本類型;

不是的情況:兩個變量啊配名。比如兩個string變量就不行了啤咽。


(2)StringBuffer:也代表字符串對象,與其余兩個基本類似渠脉。但StringBuffer是線程安全宇整,StringBuilder沒有線程安全功能,所以StringBuilder性能略高芋膘。

因為StringBuffer和StringBuilder都繼承了AbstractStringBuilder 鳞青,所以我們先看AbstractStringBuilder 霸饲。

AbstractStringBuilder 源碼:

//為抽象類,主要的屬性有兩個臂拓,一個為value厚脉,一個為count,value用于存放值胶惰,count用于管理該類的容量
abstract class AbstractStringBuilder implements Appendable, CharSequence {
   //用來存字符串
    char[] value;
    //用來計算存儲使用的字符數(shù)量
    int count;
    public int length() {//length方法返回的是count的值傻工,而不是value.length  
        return count;  
    }  
    //抽象類的構(gòu)造器沒啥必要看了吧
    //這個才是返回容量,字符串總長度
    public int capacity() {
        return value.length;
    }
    //一層扣一層的封裝的擴容機制:
     public void ensureCapacity(int minimumCapacity) {
        if (minimumCapacity > 0)
            ensureCapacityInternal(minimumCapacity);
    }
    private void ensureCapacityInternal(int minimumCapacity) {
        if (minimumCapacity - value.length > 0)//如果最小容量大于長度就要擴容了
            expandCapacity(minimumCapacity);
    }
    void expandCapacity(int minimumCapacity) {
        int newCapacity = value.length * 2 + 2;//自動擴容機制孵滞,每次擴容(value.length+1)*2  
        if (newCapacity - minimumCapacity < 0)
            newCapacity = minimumCapacity;//若傳入的參數(shù)小于0精钮,則直接把容量設置到Integer的最  
        if (newCapacity < 0) {
            if (minimumCapacity < 0) // overflow
                throw new OutOfMemoryError();
            newCapacity = Integer.MAX_VALUE;//若擴容后的容量還是小于傳入的參數(shù),則將傳入的參數(shù)設為容量
        }
        value = Arrays.copyOf(value, newCapacity);//當count小于value.length時剃斧,將value多余長度的值刪除,這時value.length的長度等于count
    }
    //用于保留value的值忽你,保留的長度為count的值幼东,只有當count的值小于value.length時才起作用科雳,
    public void trimToSize() {
        if (count < value.length) {
            value = Arrays.copyOf(value, count);
        }
    }
    public void setLength(int newLength) {
        if (newLength < 0)
            throw new StringIndexOutOfBoundsException(newLength);
        ensureCapacityInternal(newLength);//當傳入的值大于等于0時根蟹,需要擴容  

        if (count < newLength) { //當傳入值大于字符統(tǒng)計量
            Arrays.fill(value, count, newLength, '\0');//為新擴容的元素賦值'\0'尿赚,為結(jié)束符  
        }
        count = newLength;//排除那堆不合理參數(shù)干擾后就是那個真正的字符統(tǒng)計量了琴庵。
    }
    //append依賴的一個方法翰苫,用以添加一個字符串數(shù)組
    public void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)
    {
        if (srcBegin < 0)
            throw new StringIndexOutOfBoundsException(srcBegin);
        if ((srcEnd < 0) || (srcEnd > count))
            throw new StringIndexOutOfBoundsException(srcEnd);
        if (srcBegin > srcEnd)
            throw new StringIndexOutOfBoundsException("srcBegin > srcEnd");
        System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);//用于添加字符串鹰晨,將value的值添加到dst[]中  
    }
    //在對象后拼接字符串或其他對象判没,效率較高熊杨,可以觀察到,在拼接時并沒有創(chuàng)建新的對象土至,也沒有舍棄舊的對象购对,相對于String的機制,性能提升相當明顯
    //根據(jù)傳入?yún)?shù)對應不同的方法
    public AbstractStringBuilder append(Object obj) {
        return append(String.valueOf(obj));
    }
    public AbstractStringBuilder append(String str) {
        if (str == null)
            return appendNull();//若傳入的字符串長度為0陶因,則默認添加null這個字符串
        int len = str.length();
        ensureCapacityInternal(count + len);//擴容到這么大
        str.getChars(0, len, value, count);//然后用getChar方法去添加字符串數(shù)組
        count += len;//確定存儲了這么多的字符
        return this;
    }
    //拼接StringBuffer 也是可以的
     public AbstractStringBuilder append(StringBuffer sb) {
        if (sb == null)
            return appendNull();
        int len = sb.length();
        ensureCapacityInternal(count + len);
        sb.getChars(0, len, value, count);
        count += len;
        return this;
    }
    //這意味只要是AbstractStringBuilder 就可以拼接(暗指builder吧)
     AbstractStringBuilder append(AbstractStringBuilder asb) {
        if (asb == null)
            return appendNull();
        int len = asb.length();
        ensureCapacityInternal(count + len);//直接檢驗容量骡苞,有需要則執(zhí)行擴容  
        asb.getChars(0, len, value, count);//然后用getChar方法去添加字符串數(shù)組
        count += len;//確定存儲了這么多的字符
        return this;
    }
    //添加null這個字符串
     private AbstractStringBuilder appendNull() {
        int c = count;
        ensureCapacityInternal(c + 4);
        final char[] value = this.value;
        value[c++] = 'n';
        value[c++] = 'u';
        value[c++] = 'l';
        value[c++] = 'l';
        count = c;
        return this;
    }
    //同理char的字節(jié)。就不看了楷扬,我們看下Interger等一些基本類型包裝類解幽,其余都基本類似的。
     public AbstractStringBuilder append(int i) {
        if (i == Integer.MIN_VALUE) {
            append("-2147483648");//Integer最小值為特例烘苹,特殊處理  
            return this;
        }
        int appendedLength = (i < 0) ? Integer.stringSize(-i) + 1
                                     : Integer.stringSize(i);//判斷Integer的位數(shù)躲株,負數(shù)有負號,要多加一位
        int spaceNeeded = count + appendedLength;//確定字符串大小
        ensureCapacityInternal(spaceNeeded);//還是擴容
        Integer.getChars(i, spaceNeeded, value);//還是添加镣衡,不過是用Integer的靜態(tài)方法
        count = spaceNeeded;
        return this;
    }
    //以刪除一部分的字符霜定,傳入開始下標以及截止下標
    public AbstractStringBuilder delete(int start, int end) {
     //驗證參數(shù)的有效性  
        if (start < 0)
            throw new StringIndexOutOfBoundsException(start);
        if (end > count)
            end = count;//結(jié)束下標大于count時档悠,將count設為結(jié)束下標  
        if (start > end)
            throw new StringIndexOutOfBoundsException();//開始下標就大于結(jié)束下標,當然異常
        int len = end - start;
        if (len > 0) {
        //System的靜態(tài)方法來實現(xiàn)數(shù)組之間的復制望浩。src:源數(shù)組辖所;    srcPos:源數(shù)組要復制的起始位置;dest:目的數(shù)組曾雕;destPos:目的數(shù)組放置的起始位置奴烙;length:復制的長度。注意:src and dest都必須是同類型或者可以進行轉(zhuǎn)換類型的數(shù)組.
        //這個函數(shù)可以實現(xiàn)自己到自己復制剖张。解析:http://blog.csdn.net/kesalin/article/details/566354
            System.arraycopy(value, start+len, value, start, count-end);//執(zhí)行刪除
            count -= len;//重置count大小 
        }
        return this;
    }
    //在對象中間插入字符串數(shù)組
    public AbstractStringBuilder insert(int dstOffset, CharSequence s) {
    //驗證參數(shù)有效性  
        if (s == null)
            s = "null";
        if (s instanceof String)//這個必須要String才準插入
            return this.insert(dstOffset, (String)s);
        return this.insert(dstOffset, s, 0, s.length());
    }
    //傳入的是插入開始下標切诀,以及要插入的字符串。其他插入方法差不多搔弄,就不多說了幅虑。
    public AbstractStringBuilder insert(int offset, String str) {
    //驗證參數(shù)有效性  
        if ((offset < 0) || (offset > length()))
            throw new StringIndexOutOfBoundsException(offset);
        if (str == null)
            str = "null";
        int len = str.length();
        ensureCapacityInternal(count + len);//擴容
        System.arraycopy(value, offset, value, offset + len, count - offset);//使用arraycopy方法去創(chuàng)建那么多長度先,就是先在value中建立起用于存放插入值的空位   
          str.getChars(value, offset);//向空位中插入str  
        count += len;//更新count值  
        return this;
    }
    //將對象本身的字符順序調(diào)轉(zhuǎn)后返回給原對象.顾犹。就是反轉(zhuǎn)字符串
    public AbstractStringBuilder reverse() {
        boolean hasSurrogates = false;
        int n = count - 1;
        //采用從中間向兩端遍歷倒庵,對換對稱位置上的字符  
        for (int j = (n-1) >> 1; j >= 0; j--) {
            int k = n - j;
            char cj = value[j];//兩個暫存變量
            char ck = value[k];
            value[j] = ck;//直接對應位置交換
            value[k] = cj;
            //驗證每個字符的編碼是否在范圍內(nèi)
            if (Character.isSurrogate(cj) ||
                Character.isSurrogate(ck)) {
                hasSurrogates = true;
            }
        }
        if (hasSurrogates) {
        //直接反轉(zhuǎn)后,如果兩字符順序錯了炫刷,就需要重新調(diào)整擎宝。考慮到存在增補字符浑玛,需要成對校驗绍申,就是超出了字符的編碼范圍的話就會重新翻轉(zhuǎn)到原來那樣
            reverseAllValidSurrogatePairs();
        }
        return this;
    }
    //reverse的依賴方法--重新調(diào)整字符順序
    private void reverseAllValidSurrogatePairs() {
        for (int i = 0; i < count - 1; i++) {
            char c2 = value[i];
            if (Character.isLowSurrogate(c2)) {
                char c1 = value[i + 1];
                if (Character.isHighSurrogate(c1)) {
                    value[i++] = c1;
                    value[i] = c2;
                }
            }
        }
    }
    //返回一個新字符串,它是此字符串的一個子字符串顾彰。該子字符串從指定的 start索引處開始极阅,一直到索引 end- 1 處的字符。因此涨享,該子字符串的長度為 end-start筋搏。
    public String substring(int start, int end) {
        if (start < 0)
            throw new StringIndexOutOfBoundsException(start);
        if (end > count)
            throw new StringIndexOutOfBoundsException(end);
        if (start > end)
            throw new StringIndexOutOfBoundsException(end - start);
        return new String(value, start, end - start);
    }
    
}

嗯,AbstractStringBuilder是Stringbuilder和StringBuffer的父類厕隧,兩者的共同抽象出來很多相似性質(zhì)嘛奔脐。

接下來我們看StringBuilder的關(guān)鍵源碼:

也代表字符串對象。但這個是線程不安全的吁讨。

//StringBuilder類由final修飾帖族,不能被繼承,并且繼承了AbstractStringBuilder類挡爵,并完成了toString方法竖般,同時使用了AbstractStringBuilder類中大量的方法。
public final class StringBuilder
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence{
 /** 序列號茶鹃, 對象序列化和反序列化需要的唯一標識版本號涣雕,基本類中艰亮,只要接入Serializable接口都會分配一個id */
     static final long serialVersionUID = 4383685877147921099L;
     ///默認構(gòu)造方法
      public StringBuilder() {
      //使用父類的構(gòu)造方法,默認初始化容量為capacity = 16 挣郭∑#基本所有jdk中實現(xiàn)類涉及初始化容量的大小都為16,加上一點擴容機制
        super(16);
      }
      //帶一個參數(shù)的構(gòu)造方法兑障,可以指定初始化容量
      public StringBuilder(int capacity) {
        super(capacity);
      }
     //帶一個參數(shù)構(gòu)造方法侄非,與前一個不同,這是指定一個String來初始化
    public StringBuffer(String str) {
        // 這里可以注意下流译,指定String初始化StringBuffer的時候指定容量大小為String的長度加上16
        super(str.length() + 16);
        //然后追加到value中
        append(str);
    }
    //其余的構(gòu)造方法就都類似的啦
    //下面是字符串修改方法逞怨。基本都是用父類的方法的福澡,我們剛剛就分析過了叠赦,就不多分析了。但是要注意:修改的方法全部都為線程不安全革砸,是犧牲了安全用以實現(xiàn)性能除秀。若需要考慮線程的安全性,建議使用StringBuffer算利。一會會看到為啥Buffer是線程安全的册踩。
    @Override
    public StringBuilder append(Object obj) {
        return append(String.valueOf(obj));
    }

    @Override
    public StringBuilder append(String str) {
        super.append(str);
        return this;
    }
      @Override
    public String toString() {
        // Create a copy, don't share the array
        //源碼所說,是new了一個String對象效拭,返回string出去暂吉,而不是直接給Builder出去。
        return new String(value, 0, count);
    }
    //將對象序列化允耿,寫入了count和value借笙。
     private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException {
        s.defaultWriteObject();
        s.writeInt(count);
        s.writeObject(value);
    }
    //用于反序列化扒怖,將count和value屬性讀取出來
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        s.defaultReadObject();
        count = s.readInt();
        value = (char[]) s.readObject();
    }
}

(3)StringBuffer:代表一個字符序列可變的字符串较锡。這個stringbuffer提供了一系列的修改方法去改變字符串對象序列。一旦StringBuffer生成了最終想要的對象盗痒,調(diào)用toString方法將它轉(zhuǎn)換成一個String對象即可蚂蕴。

來觀份源碼,將會詳細地去對比StringBuilder去講解

 public final class StringBuffer
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence
{
    //Builder沒有的功能(一):最后一次修改后的緩存值(字符數(shù)組保存)俯邓,只要修改了value骡楼,那么就會重置
    private transient char[] toStringCache;
      /** 序列號, 對象序列化和反序列化需要的唯一標識版本號 */
    static final long serialVersionUID = 3388685877147921107L;
    //構(gòu)造方法基本跟builder差不多的啦
     public StringBuffer() {
        super(16);
    }
     public StringBuffer(int capacity) {
        super(capacity);
    }
    //獲取字符串字符數(shù)量稽鞭,效率低鸟整,對象鎖,所以這樣才會線程安全
    @Override
    public synchronized int length() {
        return count;
    }
    // 獲取容量朦蕴,效率低--對象鎖
    @Override
    public synchronized int capacity() {
        return value.length;
    }
    //確保容量不小于minimumCapacity
    @Override
    public synchronized void ensureCapacity(int minimumCapacity) {
        if (minimumCapacity > value.length) {
        // 當最小容量值(傳進來的參數(shù)值)大于value.length(這個其實就是容量)篮条,那么就直接擴容
            expandCapacity(minimumCapacity);
        }
    }
    //擴容弟头,在它父類講過了。
    void expandCapacity(int minimumCapacity) {
        int newCapacity = value.length * 2 + 2;
        if (newCapacity - minimumCapacity < 0)
            newCapacity = minimumCapacity;
        if (newCapacity < 0) {
            if (minimumCapacity < 0) // overflow
                throw new OutOfMemoryError();
            newCapacity = Integer.MAX_VALUE;
        }
        value = Arrays.copyOf(value, newCapacity);
    }
    //將value數(shù)組中沒有存入元素的部分去掉(類似去空格),此時容量大小和size大小相等涉茧。也就是刪掉count后面的那堆空的東東
    @Override
    public synchronized void trimToSize() {
    //用父類的赴恨。但線程安全
        super.trimToSize();
    }
    //setLength講過了就省略了。注意它也是線程安全伴栓。擴充字符串容量到newLength伦连,并且用空格填充
     @Override
    public synchronized void setLength(int newLength) {
        toStringCache = null;
         //調(diào)用父類函數(shù)
        super.setLength(newLength);
    }
    //根據(jù)指定索引獲取字符,效率慢,對象鎖.
    @Override
    public synchronized char charAt(int index) {
        if ((index < 0) || (index >= count))
            throw new StringIndexOutOfBoundsException(index);
        return value[index];
    }
    /**
     * 根據(jù)索引修改字符串中某個字符值
     */
    @Override
    public synchronized void setCharAt(int index, char ch) {
        if ((index < 0) || (index >= count))
            throw new StringIndexOutOfBoundsException(index);
        toStringCache = null;//清除緩存钳垮,只要修改了value惑淳,此值就會clear
        value[index] = ch;
    }
    //修改操作跟父類的唯一區(qū)別就是線程安全了:就不多貼了
     @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;
    }
    /**
     * 不存在內(nèi)存泄漏,實現(xiàn)了線程安全
     */
    @Override
    public synchronized String substring(int start) {
        return substring(start, count);
        //結(jié)果是new String(value, start, end - start);弄了個新對象的扔枫。
        // 沒有復用char[]
    }
    //有一套這樣的同步與不同步方法
    /**
     * 此方法不同步汛聚, 而且也沒有 toStringCache = null;
     * 如果需要同步,那么需要將boolean b轉(zhuǎn)化為specific type特定類型(String)
     */
    @Override
    public  StringBuffer insert(int offset, boolean b) {
        super.insert(offset, b);
        return this;
    }
    @Override
    public synchronized StringBuffer insert(int offset, char c) {
        toStringCache = null;
        super.insert(offset, c);
        return this;
    }
    //toStringCache之前都不知道這個字段的含義短荐,看到這里似乎看懂了倚舀,。是提高toString函數(shù)的效率忍宋,不用每次都是調(diào)用痕貌。也就是有做了一個緩存
//  Arrays.copyOfRange。糠排。舵稠。但是字符串修改后這個值需要clear
//線程安全
    @Override
    public synchronized String toString() {
        if (toStringCache == null) {
            toStringCache = Arrays.copyOfRange(value, 0, count);
        }
        return new String(toStringCache, true);//返回一個新的string對象過去
    }
    // 自定義序列化字段
    //**transient 用于指定哪個字段不被默認序列化,如public transient int a; 
     //serialPersistentFields 用于指定哪些字段需要被默認序列化.如下:
    private static final java.io.ObjectStreamField[] serialPersistentFields =
    {
        new java.io.ObjectStreamField("value", char[].class),
        new java.io.ObjectStreamField("count", Integer.TYPE),
        new java.io.ObjectStreamField("shared", Boolean.TYPE),
    };

    /**
     *  序列化大到ObjectOutputStream入宦,寫入了count和value哺徊、shared
     */
    private synchronized void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException {
        java.io.ObjectOutputStream.PutField fields = s.putFields();
        fields.put("value", value);
        fields.put("count", count);
        fields.put("shared", false);
        s.writeFields();
    }

    /**
     * 反序列化到對象,讀出count和value乾闰。
     */
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        java.io.ObjectInputStream.GetField fields = s.readFields();
        value = (char[])fields.get("value", null);
        count = fields.get("count", 0);
    }
    
}

三落追、字符串家族的坑以及使用推薦

1.效率問題:效率:StringBuilder>StringBuffer>String

 public static void main(String[] args) {
        long start = System.currentTimeMillis();
        String str = null;
        for (int i = 0; i < 20000; i++) {
            str = str + i + ",";//因為是不斷弄出一個新的對象出來的
        }
        System.out.println("String耗時  "+ (System.currentTimeMillis() - start));
        System.out.println("-------------------");

//buffer和builder都有自動擴容機制,不像string有兩大缺點:1.返回對象使用大量new操作涯肩,產(chǎn)生很多垃圾轿钠;2.雖然最終調(diào)用的是系統(tǒng)復制數(shù)組操作,但調(diào)用之前開銷非常大病苗,只能靠復制來解決拼接問題疗垛。

        start = System.currentTimeMillis();
        StringBuffer buffer = new StringBuffer();
        for (int i = 0; i < 20000; i++) {
            buffer.append(i + ",");//線程安全所以慢一點,但前提這里是單線程
        }
        System.out.println("StringBuffer耗時  "+(System.currentTimeMillis() - start));
        System.out.println("-------------------");
        start = System.currentTimeMillis();
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < 20000; i++) {
            builder.append(i + ",");//線程不安全硫朦,效率最高
        }
        System.out.println("StringBuilder耗時  "+(System.currentTimeMillis() - start));
    }
/*    
String耗時 2030
-------------------
StringBuffer耗時 5
-------------------
StringBuilder耗時 3
*/

2.線程安全與線程不安全問題:驗證:

/**
 * Created by ${符柱成} on 2017/1/11.
 */
public class StringBuilderTest {
    public static void main(String[] args) {
        /*
         * 聲明個字符串s贷腕,用下劃線和井號是因為兩個比較好區(qū)分。 分別實例化StringBuffer和StringBuilder兩個對象
         */
        String s = "####____";
        StringBuffer sf = new StringBuffer(s);
        StringBuilder sd = new StringBuilder(s);
        /*
         * 對sf和sd各自實例化兩個反轉(zhuǎn)他們的類
         */
//        ThreadString sfr1 = new ThreadString(sf);
//        ThreadString sfr2 = new ThreadString(sf);
        ThreadString sdr1 = new ThreadString(sd);
        ThreadString sdr2 = new ThreadString(sd);
        /*
         * 啟動這四個線程,此時sf和sd各自有兩個線程在對他們進行字符串反轉(zhuǎn)操作
         */
//        new Thread(sfr1).start();
//        new Thread(sfr2).start();
        new Thread(sdr1).start();
        new Thread(sdr2).start();
    }
}
class ThreadString implements Runnable {
    /*
     * 這個類用來完成字符串的反轉(zhuǎn)工作泽裳,使用了Runnable接口來實現(xiàn)多線程 times是用來表示循環(huán)多少次的
     * 因為懶的再寫一個變量所以用了一個Object類型的s芽世,后面再轉(zhuǎn)化
     */
    public Object s = null;
    int times = 10;

    /*
     * 兩個構(gòu)造方法把s傳進來
     */
    public ThreadString(StringBuffer s) {
        this.s = s;
    }

    public ThreadString(StringBuilder s) {
        this.s = s;
    }

    /*
     * 復寫run方法實現(xiàn)多線程 在我的電腦上大概循環(huán)十幾次可以看到效果了
     */
    public void run() {
        for (int i = 0; i <= times; i++) {
            //sleep一下讓輸出更加清晰
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

            if (s instanceof StringBuffer) {
                ((StringBuffer) s).reverse();//直接調(diào)用翻轉(zhuǎn)的方法
                System.out.println("BUFFER->" + s);
            } else if (s instanceof StringBuilder) {
                ((StringBuilder) s).reverse();//直接調(diào)用翻轉(zhuǎn)的方法
                System.out.println("        " + s + "<-Builder");
            }
            System.out.println(Thread.currentThread().getName());//輸出下線程名字更加清晰啦
            System.out.println("-------------------");
        }
    }
}
/*
 * 最后看一下控制臺的輸出會發(fā)現(xiàn)反轉(zhuǎn)后出現(xiàn)井號和下劃線交錯的都是StringBuilder的輸出
 */

/*
我們輸出的時候會發(fā)現(xiàn),Builder在一次線程操作中甚至可以這樣反轉(zhuǎn)诡壁,但是Buffer就可觀察到?jīng)]有這樣的搶占修改济瓢,是十分有序地修改。
-------------------
        ####____<-builder
        ____####<-builder
Thread-1
-------------------
*/

使用注意點總結(jié):

1. 如果要操作少量的數(shù)據(jù)用 = String

2. 單線程操作字符串緩沖區(qū)下操作大量數(shù)據(jù) --StringBuilder

3. 多線程操作字符串緩沖區(qū) 下操作大量數(shù)據(jù) -- StringBuffer


好了妹卿,深入Java基礎(二)——字符串家族講完了旺矾。本博客是經(jīng)過仔細研究其他的博客,結(jié)合源代碼注釋夺克,并在這里做出進一步拓展以及寫出自己的理解箕宙。另外,這個系列會逐步更新铺纽,也希望閱讀更多源碼柬帕,分享經(jīng)驗給大家。歡迎在下面指出錯誤狡门,共同學習O萸蕖!你的點贊是對我最好的支持F淞蟆凤跑!

這個系列的文章因為涉及很多源碼,所以會根據(jù)網(wǎng)友們的反應以及我的學習深入去補充叛复。希望大家一起來討論學習仔引。

轉(zhuǎn)載請注明:【JackFrost的博客】

更多內(nèi)容,可以訪問JackFrost的博客

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末褐奥,一起剝皮案震驚了整個濱河市咖耘,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌撬码,老刑警劉巖儿倒,帶你破解...
    沈念sama閱讀 217,826評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異耍群,居然都是意外死亡义桂,警方通過查閱死者的電腦和手機找筝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評論 3 395
  • 文/潘曉璐 我一進店門蹈垢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人袖裕,你說我怎么就攤上這事曹抬。” “怎么了急鳄?”我有些...
    開封第一講書人閱讀 164,234評論 0 354
  • 文/不壞的土叔 我叫張陵谤民,是天一觀的道長堰酿。 經(jīng)常有香客問我,道長张足,這世上最難降的妖魔是什么触创? 我笑而不...
    開封第一講書人閱讀 58,562評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮为牍,結(jié)果婚禮上哼绑,老公的妹妹穿的比我還像新娘。我一直安慰自己碉咆,他們只是感情好抖韩,可當我...
    茶點故事閱讀 67,611評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著疫铜,像睡著了一般茂浮。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上壳咕,一...
    開封第一講書人閱讀 51,482評論 1 302
  • 那天席揽,我揣著相機與錄音,去河邊找鬼谓厘。 笑死驹尼,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的庞呕。 我是一名探鬼主播新翎,決...
    沈念sama閱讀 40,271評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼住练!你這毒婦竟也來了地啰?” 一聲冷哼從身側(cè)響起唱较,我...
    開封第一講書人閱讀 39,166評論 0 276
  • 序言:老撾萬榮一對情侶失蹤夯巷,失蹤者是張志新(化名)和其女友劉穎吱瘩,沒想到半個月后说敏,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體怠缸,經(jīng)...
    沈念sama閱讀 45,608評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡置森,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,814評論 3 336
  • 正文 我和宋清朗相戀三年动雹,在試婚紗的時候發(fā)現(xiàn)自己被綠了捧弃。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片许赃。...
    茶點故事閱讀 39,926評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡止喷,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出混聊,到底是詐尸還是另有隱情弹谁,我是刑警寧澤,帶...
    沈念sama閱讀 35,644評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站预愤,受9級特大地震影響沟于,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜植康,卻給世界環(huán)境...
    茶點故事閱讀 41,249評論 3 329
  • 文/蒙蒙 一旷太、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧销睁,春花似錦泳秀、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至檩赢,卻和暖如春吕嘀,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背贞瞒。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評論 1 269
  • 我被黑心中介騙來泰國打工偶房, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人军浆。 一個月前我還...
    沈念sama閱讀 48,063評論 3 370
  • 正文 我出身青樓棕洋,卻偏偏與公主長得像,于是被迫代替她去往敵國和親乒融。 傳聞我的和親對象是個殘疾皇子掰盘,可洞房花燭夜當晚...
    茶點故事閱讀 44,871評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關(guān)的語法赞季,內(nèi)部類的語法愧捕,繼承相關(guān)的語法,異常的語法申钩,線程的語...
    子非魚_t_閱讀 31,631評論 18 399
  • 集合框架: 1)特點:存儲對象次绘;長度可變;存儲對象的類型可不同2)Collection(1)List:有序的撒遣;元素...
    Demo_Yang閱讀 1,260評論 0 4
  • (一)Java部分 1邮偎、列舉出JAVA中6個比較常用的包【天威誠信面試題】 【參考答案】 java.lang;ja...
    獨云閱讀 7,104評論 0 62
  • “書單”似乎成為越來越多的讀書公眾號的一種“月經(jīng)”帖禾进,什么“有生之年必看”,“一生必讀書單100本”轩缤,現(xiàn)在干脆每個...
    溫在闊閱讀 686評論 0 5
  • 拒絕反抗命迈,拒絕乞求,威脅火的,拒絕謾罵壶愤,利誘,尋死覓活與死纏爛打馏鹤。 如果對方要求分手征椒,不論以何種理由,不論你有多愛ta...
    幾何月閱讀 602評論 1 4