Java — String知識(shí)點(diǎn)學(xué)習(xí)

學(xué)習(xí)資料:

我電腦環(huán)境JDk 1.8

看到一篇很有深度的講解:How many Objects created with: String str=new String("Hello")预皇?

1. String字符串

String不是Java中的基本數(shù)據(jù)類型

C語言中俊柔,字符串的處理通常是使用char數(shù)組菜皂,但數(shù)組本身無法封裝字符串操作所需要的方法逮京。在Java中治唤,String對(duì)象可以看作是char數(shù)組的延伸和進(jìn)一步封裝

String主要由3部分組成:char數(shù)組花履,offset偏移牍疏,conut長(zhǎng)度

char數(shù)組表示String的內(nèi)容箍铲,String對(duì)象所表示字符串的超集菩收。String的真實(shí)內(nèi)容還需要由偏移量和長(zhǎng)度在char數(shù)組中進(jìn)行定位和截取


1.1 3個(gè)基本特點(diǎn)

  1. 不變性
  2. 針對(duì)常量池的優(yōu)化
  3. 類的final定義

1.1.1 不變性

當(dāng)String對(duì)象一旦生成梨睁,就不能對(duì)再對(duì)它進(jìn)行改變

個(gè)人理解:String類中的操作字符串的方法,并不是直接改變當(dāng)前的String對(duì)象娜饵,而創(chuàng)建了一個(gè)新的String對(duì)象

String的這個(gè)特性可以泛化成不變(immutable)模式坡贺,即一個(gè)對(duì)象被創(chuàng)建后就不再發(fā)生變化。作用在于當(dāng)一個(gè)對(duì)象需要被多線程共享箱舞,并且訪問頻繁時(shí)遍坟,可以省略同步和鎖等待的時(shí)間


1.1.2 針對(duì)常量池的優(yōu)化

當(dāng)兩個(gè)String對(duì)象擁有相同的值時(shí),只引用常量池中的同一個(gè)拷貝晴股。當(dāng)一個(gè)字符串反復(fù)出現(xiàn)時(shí)愿伴,可以節(jié)省內(nèi)存空間

String str1 = "abc";
String str2 = "abc";
String str3 = new String("abc");
System.out.println(str1 == str2);               // true
System.out.println(str1 == str3);               // false
System.out.println(str1 == str3.intern());      // true
String 內(nèi)存分配方式

str1str2引用了相同的地址,str3重新開辟了一塊內(nèi)存空間

str3在常量池中的位置和str1是一樣的电湘,也就是說隔节,雖然str3單獨(dú)占用了堆空間,但是它所指向的實(shí)體和str1完全一樣

str3.intern()返回了String對(duì)象在常量池中的引用

1.1.3 類的 final 定義

作為final類的String對(duì)象在系統(tǒng)中不可能有任何子類寂呛,這是對(duì)系統(tǒng)安全性的保護(hù)

JDK 1.5版本之前的環(huán)境中怎诫,使用final定義,有助于幫助虛擬機(jī)尋找機(jī)會(huì)贷痪,內(nèi)聯(lián)所有的final方法幻妓,提高系統(tǒng)效率。但在JDK 1.5以后劫拢,效果并不明顯

注意:

1.7之后肉津,Stringsubstring()方法已經(jīng)不會(huì)再引起內(nèi)存泄露

可能會(huì)引起內(nèi)存泄露的是String()中的一個(gè)私有構(gòu)造方法,在1.7之后已經(jīng)修復(fù)


1.2 字符串的分割和查找

1.2.1 分割

作者用的JDK版本不知道多少舱沧,但應(yīng)該不是1.8妹沙,很可能是1.6,感覺同樣的代碼測(cè)試狗唉,時(shí)間已經(jīng)比作者測(cè)試時(shí)間少了不止一個(gè)量級(jí)。除了JDK代碼迭代升級(jí)的優(yōu)化涡真,還得考慮電腦的差距

結(jié)論:一般情況下直接使用split()方法足夠分俯,在一些要分割的目標(biāo)長(zhǎng)度很長(zhǎng)很長(zhǎng)并且需要分割的次數(shù)很多很多肾筐,split()耗時(shí)久時(shí),再考慮使用StringTokenzier缸剪,但感覺一個(gè)字符串要分割10000次已經(jīng)有些喪心病狂

測(cè)試代碼:

測(cè)試次數(shù)吗铐,為了方便觀察,跨度有些大杏节,應(yīng)該有梯度的測(cè)試

public class StringL {
    private static final String LINE = "************************************************";

    public static void main(String[] args) {
        test(1000);
        test(10000);
        test(100000);
        test(1000000);
        test(10000000);
    }

    /**
     * 根據(jù)字符串的長(zhǎng)度完成一輪測(cè)試
     */
    private static void test(int num) {
        String str = getHugeString(num);
        System.out.println("字符串長(zhǎng)度為 " + str.length() + " 唬渗,同一個(gè)字符串需要分割 " + num + " 次時(shí):");

        // split 方式
        splitTest(str);

        // StringTokenizer 方式
        StringTokenizerTest(str);

        // 打印分割線
        System.out.println(LINE);
    }

    /**
     * 使用 StringTokenizer 分割字符串
     */
    private static void StringTokenizerTest(String str) {
        StringTokenizer st = new StringTokenizer(str, ";");
        long now = System.currentTimeMillis();
        while (st.hasMoreTokens()) {
            st.nextToken();
        }
        System.out.println("StringTokenizer 用時(shí):" + (System.currentTimeMillis() - now));
    }

    /**
     * 原始的 split 方法分割字符串
     */
    private static void splitTest(String str) {
        long now = System.currentTimeMillis();
        str.split(";");
        System.out.println("split 用時(shí):" + (System.currentTimeMillis() - now));
    }

    /**
     * 獲取字符串
     */
    private static String getHugeString(int num) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < num; i++) {
            sb.append(i);
            sb.append(";");
        }
        return sb.toString();
    }
}

輸出結(jié)果,時(shí)間是毫秒:

字符串長(zhǎng)度為 3890 奋渔,同一個(gè)字符串需要分割 1000 次時(shí):
split 用時(shí):2
StringTokenizer 用時(shí):1
************************************************
字符串長(zhǎng)度為 48890 镊逝,同一個(gè)字符串需要分割 10000 次時(shí):
split 用時(shí):4
StringTokenizer 用時(shí):4
************************************************
字符串長(zhǎng)度為 588890 ,同一個(gè)字符串需要分割 100000 次時(shí):
split 用時(shí):28
StringTokenizer 用時(shí):21
************************************************
字符串長(zhǎng)度為 6888890 嫉鲸,同一個(gè)字符串需要分割 1000000 次時(shí):
split 用時(shí):503
StringTokenizer 用時(shí):60
************************************************
字符串長(zhǎng)度為 78888890 撑蒜,同一個(gè)字符串需要分割 10000000 次時(shí):
split 用時(shí):3895
StringTokenizer 用時(shí):770
************************************************

多次測(cè)試,當(dāng)對(duì)同一個(gè)字符串要分割次數(shù)達(dá)不到一定期限時(shí)玄渗,有時(shí)StringTokenizer的效率還不如split()方法座菠,只有到達(dá)一定期限后,StringTokenizer的優(yōu)勢(shì)就很明顯


1.2.1 查找

StringcharAt()藤树,indexOf()方法非常非常高效

測(cè)試代碼:

分別使用startWith()endWith()比較100w

public class CharAtTest {
    public static void main(String[] args) {
        String str = "abc123456xyz";
        int num = 1000000;
        String startTag = "abc";
        String endTag = "xyz";


        // startWith 方法
        long now = System.currentTimeMillis();
        for (int i = 0; i < num; i++) {
            boolean b = str.startsWith(startTag);
        }
        System.out.println("startWith()方法耗時(shí) : " + (System.currentTimeMillis() - now));

        // myStartWith() 方法
        now = System.currentTimeMillis();
        for (int i = 0; i < num; i++) {
            myStartWith(str);
        }
        System.out.println("myStartWith()方法耗時(shí) : " + (System.currentTimeMillis() - now));

        // endWith方法
        now = System.currentTimeMillis();
        for (int i = 0; i < num; i++) {
            boolean b = str.endsWith(endTag);
        }
        System.out.println("endsWith()方法耗時(shí) : " + (System.currentTimeMillis() - now));

        // myEndWith() 方法
        int length = str.length();
        now = System.currentTimeMillis();
        for (int i = 0; i < num; i++) {
            myEndWith(str, length);
        }
        System.out.println("myEndWith()方法耗時(shí) : " + (System.currentTimeMillis() - now));
    }

    private static void myEndWith(String str, int length) {
        boolean b = str.charAt(length - 3) == 'a' && str.charAt(length - 2) == 'b' && str.charAt(length - 1) == 'c';
    }

    private static void myStartWith(String str) {
        boolean b = str.charAt(0) == 'a' && str.charAt(1) == 'b' && str.charAt(2) == 'c';
    }

}

輸出結(jié)果:

startWith()方法耗時(shí) : 15
myStartWith()方法耗時(shí) : 6
endsWith()方法耗時(shí) : 13
myEndWith()方法耗時(shí) : 6

結(jié)論:在極為敏感的系統(tǒng)中浴滴,必要的時(shí)候可以考慮使用charAt()方法來代替startWith()或者endWith()。但幾乎大部分情況下岁钓,直接startWith()或者endWith()足夠


2. StringBuilder 和 StringBuffer

由于String對(duì)象是不可變對(duì)象升略,在需要對(duì)字符串進(jìn)行修改操作時(shí),如連接甜紫、替換降宅,總是會(huì)生成新的對(duì)象,導(dǎo)致在某些時(shí)候性能比較差囚霸,JDK專門提供了創(chuàng)建和修改字符串的工具類StringBufferStringBuilder


2.1 String 常量的累加操作

關(guān)于String s = "a" + "b" + "c"幾個(gè)對(duì)象: 一個(gè)

String s = "a" + "b" + "c"這種靜態(tài)字符串的連接操作腰根,Java在編譯時(shí)期將多個(gè)連接操作的字符串在編譯時(shí)合成一個(gè)單獨(dú)的長(zhǎng)字符串abc

對(duì)于常量字符串的累加,Java在編譯時(shí)期就做了充分優(yōu)化拓型,在編譯時(shí)期便能確定取值的字符串操作额嘿,在編譯時(shí)期進(jìn)行計(jì)算,在運(yùn)行時(shí)劣挫,并不會(huì)生成大量的String實(shí)例對(duì)象


2.2 String 變量的累加操作

測(cè)試代碼:

public class StringCreate {
    public static void main(String[] args) {
        commonConcat();
        stringBuilderConcat();
    }

    private static void stringBuilderConcat() {
        long now = System.currentTimeMillis();
        for (int i = 0 ; i < 50000; i ++){
            StringBuilder sb = new StringBuilder();
            String str1 = "abc";
            String str2 = "897";
            String str3 = "xyz";
            String str4 = "123";
            String result = sb.append(str1).append(str2).append(str3).append(str4).toString();
        }
        System.out.println("使用 StringBuilder 用時(shí):" + (System.currentTimeMillis() - now));
    }

    /**
     * 直接拼接
     */
    private static void commonConcat() {
        long now = System.currentTimeMillis();
        for (int i = 0; i < 50000; i++) {
            String str1 = "abc";
            String str2 = "897";
            String str3 = "xyz";
            String str4 = "123";
            String result = str1 + str2 + str3 + str4;
        }
        System.out.println("直接拼接用時(shí):" + (System.currentTimeMillis() - now));
    }
}

結(jié)果

直接拼接用時(shí):27
使用 StringBuilder 用時(shí):15

平均耗時(shí)差距并不大

原因在于册养,對(duì)于字符串變量的累加,Java也做了優(yōu)化压固,使用了StringBuidler對(duì)象來實(shí)現(xiàn)字符串的累加

直接拼接的代碼中球拦,for()內(nèi)字符串部分反編譯之后:

反編譯,我并沒有做,而是直接將反編譯的代碼照著書上的形式坎炼,抄了下來

String str1 = "abc";
String str2 = "897";
String str3 = "xyz";
String str4 = "123";
String result = (new StringBuilder(String.valueOf(str1))).append(str2).append(str3).append(str4).toString();

在構(gòu)建超大的String對(duì)象時(shí)愧膀,優(yōu)先考慮:顯示使用StringBuilder 或者 StringBuffer


2.3 StringBuilder 和 StringBuffer

兩者都繼承之AbstractStringBuiler,擁有幾乎相同的對(duì)外接口

兩者最大的區(qū)別: StringBuffer對(duì)幾乎所有的方法做了同步

也就是說選擇需要根據(jù)使用的具體場(chǎng)景來確定谣光,考慮線程安全時(shí)檩淋,多線程環(huán)境就選擇StringBuffer

在兩者的構(gòu)造方法中,都有一個(gè)方法提供了設(shè)置容量參數(shù)萄金,如果預(yù)先能確定容量蟀悦,指定容量大小,也可以再次對(duì)性能進(jìn)行提升

默認(rèn)情況下氧敢,不指定容量時(shí)日戈,是16字節(jié),之后需要的容量超過實(shí)際char數(shù)組長(zhǎng)度時(shí)福稳,就會(huì)進(jìn)行擴(kuò)容涎拉,int newCapacity = (value.length << 1) + 2,擴(kuò)容的細(xì)節(jié)的圆,暫時(shí)不考慮

若不指定容量鼓拧,擴(kuò)容又基本始終是2倍大小,就會(huì)造成一些空間浪費(fèi)


3. 最后

有錯(cuò)誤越妈,請(qǐng)指出

共勉 : )

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末季俩,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子梅掠,更是在濱河造成了極大的恐慌酌住,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,110評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件阎抒,死亡現(xiàn)場(chǎng)離奇詭異酪我,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)且叁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門都哭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人逞带,你說我怎么就攤上這事欺矫。” “怎么了展氓?”我有些...
    開封第一講書人閱讀 165,474評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵穆趴,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我遇汞,道長(zhǎng)未妹,這世上最難降的妖魔是什么簿废? 我笑而不...
    開封第一講書人閱讀 58,881評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮络它,結(jié)果婚禮上捏鱼,老公的妹妹穿的比我還像新娘。我一直安慰自己酪耕,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,902評(píng)論 6 392
  • 文/花漫 我一把揭開白布轨淌。 她就那樣靜靜地躺著,像睡著了一般递鹉。 火紅的嫁衣襯著肌膚如雪盟步。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,698評(píng)論 1 305
  • 那天塞关,我揣著相機(jī)與錄音椰于,去河邊找鬼。 笑死仪搔,一個(gè)胖子當(dāng)著我的面吹牛瘾婿,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播僻造,決...
    沈念sama閱讀 40,418評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼憋他,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了髓削?” 一聲冷哼從身側(cè)響起贞岭,我...
    開封第一講書人閱讀 39,332評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎猩谊,沒想到半個(gè)月后讹堤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體梯码,經(jīng)...
    沈念sama閱讀 45,796評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,968評(píng)論 3 337
  • 正文 我和宋清朗相戀三年好啰,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了轩娶。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,110評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡框往,死狀恐怖鳄抒,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情椰弊,我是刑警寧澤许溅,帶...
    沈念sama閱讀 35,792評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站秉版,受9級(jí)特大地震影響贤重,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜清焕,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,455評(píng)論 3 331
  • 文/蒙蒙 一并蝗、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧秸妥,春花似錦滚停、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,003評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至影晓,卻和暖如春镰吵,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背挂签。 一陣腳步聲響...
    開封第一講書人閱讀 33,130評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工疤祭, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人饵婆。 一個(gè)月前我還...
    沈念sama閱讀 48,348評(píng)論 3 373
  • 正文 我出身青樓勺馆,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親侨核。 傳聞我的和親對(duì)象是個(gè)殘疾皇子草穆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,047評(píng)論 2 355

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