String的理解

在做java開(kāi)發(fā)中,我們一定沒(méi)少使用String這個(gè)東西,對(duì)它可謂是非常熟悉了,但是我今天要來(lái)仔細(xì)的學(xué)習(xí)學(xué)習(xí)String;

初看String

首先學(xué)習(xí)一個(gè)類的最好方式肯定是看源碼,我對(duì)源碼整理一下:

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

    /** The offset is the first index of the storage that is used. */
    private final int offset;

    /** The count is the number of characters in the String. */
    private final int count;

    /** 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;

    ........
}

大概就是這個(gè)樣子,我們從中可以得到很多信息:

首先,String是被final修飾的,因此便知道為什么不能被繼承了,且成員方法都是默認(rèn)final類型;
其次,String的底層實(shí)現(xiàn)是使用char數(shù)組來(lái)保存字符串;

因?yàn)镾tring是被final修飾的,因此其實(shí)不可被修改的,那么我們平時(shí)使用的concat、replace、sub等是怎么回事尼?跟進(jìn)去看看就可以發(fā)現(xiàn):

public String substring(int beginIndex, int endIndex) {
    if (beginIndex < 0) {
        throw new StringIndexOutOfBoundsException(beginIndex);
    }
    if (endIndex > count) {
        throw new StringIndexOutOfBoundsException(endIndex);
    }
    if (beginIndex > endIndex) {
        throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
    }
    return ((beginIndex == 0) && (endIndex == count)) ? this :
        new String(offset + beginIndex, endIndex - beginIndex, value);
}

public String concat(String str) {
    ...
    return new String(0, count + otherLen, buf);
}

public String replace(char oldChar, char newChar) {
    if (oldChar != newChar) {
        int len = count;
        int i = -1;
        char[] val = value; /* avoid getfield opcode */
        int off = offset;   /* avoid getfield opcode */

        while (++i < len) {
        if (val[off + i] == oldChar) {
            break;
        }
        }
        if (i < len) {
        char buf[] = new char[len];
        for (int j = 0 ; j < i ; j++) {
            buf[j] = val[off+j];
        }
        while (i < len) {
            char c = val[off + i];
            buf[i] = (c == oldChar) ? newChar : c;
            i++;
        }
        return new String(0, len, buf);
        }
    }
    return this;
}

無(wú)論是sub、concat、replace操作都不是在原有的字符串上進(jìn)行的,而是重新生成了一個(gè)新的字符串對(duì)象。一句話解釋就是,最原始的字符串始終沒(méi)有被改變。
我們可以得出一個(gè)結(jié)論:
String對(duì)象一旦被創(chuàng)建就固定不變了,對(duì)String對(duì)象的任何操作都不會(huì)影響到原對(duì)象,所改變都是生成了新的對(duì)象

常量池

String和其他類一樣,產(chǎn)生對(duì)象就得為其分配空間,但是String的使用太常見(jiàn)了,JVM為了提高性能,就在字符串實(shí)例化的時(shí)候做了一些優(yōu)化:

每當(dāng)我們創(chuàng)建字符串常量時(shí),JVM會(huì)首先檢查字符串常量池,如果該字符串已經(jīng)存在常量池中,那么就直接返回常量池中的實(shí)例引用。如果字符串不存在常量池中,就會(huì)實(shí)例化該字符串并且將其放到常量池中。由于String字符串的不可變性我們可以十分肯定常量池中一定不存在兩個(gè)相同的字符串.

String b = "chenssy";
String b = "chenssy";
String c = new String("chenssy");

a、b和字面上的chenssy都是指向JVM字符串常量池中的"chenssy"對(duì)象,他們指向同一個(gè)對(duì)象;new關(guān)鍵字一定會(huì)產(chǎn)生一個(gè)對(duì)象chenssy(注意這個(gè)chenssy和上面的chenssy不同),同時(shí)這個(gè)對(duì)象是存儲(chǔ)在堆中。所以上面應(yīng)該產(chǎn)生了兩個(gè)對(duì)象:保存在棧中的和保存堆中的。但是在Java中根本就不存在兩個(gè)完全一模一樣的字符串對(duì)象。故堆中的chenssy應(yīng)該是引用字符串常量池中chenssy。

1.用new String() 創(chuàng)建的字符串不是常量,不能在編譯期就確定,所以new String() 創(chuàng)建的字符串不放入常量池中,它們有自己的地址空間.
2.JVM對(duì)String str="abc"對(duì)象放在常量池中是在編譯時(shí)做的,而String str1=str+str是在運(yùn)行時(shí)刻才能知道的,new對(duì)象也是在運(yùn)行時(shí)才做的.
3.字符串的"+"連接中,如果有字符串引用存在,而引用的值在程序編譯期是無(wú)法確定的,例如s2="a"+s1;s1="b";即"a" + s1無(wú)法被編譯器優(yōu)化,只有在程序運(yùn)行期來(lái)動(dòng)態(tài)分配并將連接后的新地址賦給s2;字面量"+"拼接是在編譯期間進(jìn)行的,拼接后的字符串存放在字符串池中;而字符串引用的"+"拼接運(yùn)算實(shí)在運(yùn)行時(shí)進(jìn)行的,新創(chuàng)建的字符串存放在堆中。

字符串重要提示

  • String使用private final char value[]來(lái)實(shí)現(xiàn)字符串的存儲(chǔ),也就是說(shuō)String對(duì)象創(chuàng)建之后,就不能再修改此對(duì)象中存儲(chǔ)的字符串內(nèi)容,就是因?yàn)槿绱耍耪f(shuō)String類型是不可變的(immutable),而且String的編輯功能是通過(guò)創(chuàng)建一個(gè)新的對(duì)象來(lái)實(shí)現(xiàn)的,而不是對(duì)原有對(duì)象進(jìn)行修改,例如replace。
  • A a;這個(gè)語(yǔ)句聲明一個(gè)類A的引用變量a[我們常常稱之為句柄],而對(duì)象一般通過(guò)new創(chuàng)建,所以aa僅僅是一個(gè)引用變量,它不是對(duì)象。
  • 創(chuàng)建字符串的方式
    (1)使用""引號(hào)創(chuàng)建字符串;
    (2)使用new關(guān)鍵字創(chuàng)建字符串。
    (3)單獨(dú)使用""引號(hào)創(chuàng)建的字符串都是常量,編譯期就已經(jīng)確定存儲(chǔ)到String Pool中;
    (4)使用new String("")創(chuàng)建的對(duì)象會(huì)存儲(chǔ)到heap中,是運(yùn)行期新創(chuàng)建的;new創(chuàng)建字符串時(shí)首先查看池中是否有相同值的字符串,如果有,則拷貝一份到堆中,然后返回堆中的地址;如果池中沒(méi)有,則在堆中創(chuàng)建一份,然后返回堆中的地址(注意,此時(shí)不需要從堆中復(fù)制到池中,否則,將使得堆中的字符串永遠(yuǎn)是池中的子集,導(dǎo)致浪費(fèi)池的空間)!
    (5)使用只包含常量的字符串連接符如"aa" + "aa"創(chuàng)建的也是常量,編譯期就能確定,已經(jīng)確定存儲(chǔ)到String Pool中;
    (6)使用包含變量的字符串連接符如"aa" + s1創(chuàng)建的對(duì)象是運(yùn)行期才創(chuàng)建的,存儲(chǔ)在heap中;
  • 在執(zhí)行到雙引號(hào)包含字符串的語(yǔ)句時(shí),如String a = "123",JVM會(huì)先到常量池里查找,如果有的話返回常量池里的這個(gè)實(shí)例的引用,否則的話創(chuàng)建一個(gè)新實(shí)例并置入常量池里。所以,當(dāng)我們?cè)谑褂弥T如String str = "abc";的格式定義對(duì)象時(shí),總是想當(dāng)然地認(rèn)為,創(chuàng)建了String類的對(duì)象str。對(duì)象可能并沒(méi)有被創(chuàng)建!而可能只是指向一個(gè)先前已經(jīng)創(chuàng)建的對(duì)象。只有通過(guò)new()方法才能保證每次都創(chuàng)建一個(gè)新的對(duì)象。
  • 在執(zhí)行String a = new String("123")的時(shí)候,首先走常量池的路線取到一個(gè)實(shí)例的引用,然后在堆上創(chuàng)建一個(gè)新的String實(shí)例,通過(guò)構(gòu)造函數(shù)給value屬性賦值,然后把實(shí)例引用賦值給a:雖然是新創(chuàng)建了一個(gè)String的實(shí)例,但是value是等于常量池中的實(shí)例的value,即是說(shuō)沒(méi)有new一個(gè)新的字符數(shù)組來(lái)存放"123"。
  • intern方法使用:一個(gè)初始為空的字符串池,它由類String獨(dú)自維護(hù)。當(dāng)調(diào)用 intern方法時(shí),如果池已經(jīng)包含一個(gè)等于此String對(duì)象的字符串(用equals(oject)方法確定),則返回池中的字符串。否則,將此String對(duì)象添加到池中,并返回此String對(duì)象的引用。它遵循以下規(guī)則:對(duì)于任意兩個(gè)字符串 s 和 t,當(dāng)且僅當(dāng) s.equals(t) 為 true 時(shí),s.intern() == t.intern() 才為 true
  • 關(guān)于equals和==
    (1)對(duì)于==,如果作用于基本數(shù)據(jù)類型的變量(byte,short,char,int,long,float,double,boolean ),則直接比較其存儲(chǔ)的"值"是否相等;如果作用于引用類型的變量(String),則比較的是所指向的對(duì)象的地址(即是否指向同一個(gè)對(duì)象)。
    (2)equals方法是基類Object中的方法,因此對(duì)于所有的繼承于Object的類都會(huì)有該方法。在Object類中,equals方法是用來(lái)比較兩個(gè)對(duì)象的引用是否相等,即是否指向同一個(gè)對(duì)象。
    (3)對(duì)于equals方法,注意:equals方法不能作用于基本數(shù)據(jù)類型的變量。如果沒(méi)有對(duì)equals方法進(jìn)行重寫,則比較的是引用類型的變量所指向的對(duì)象的地址;而String類對(duì)equals方法進(jìn)行了重寫,用來(lái)比較指向的字符串對(duì)象所存儲(chǔ)的字符串是否相等。其他的一些類諸如Double,Date,Integer等,都對(duì)equals方法進(jìn)行了重寫用來(lái)比較指向的對(duì)象所存儲(chǔ)的內(nèi)容是否相等。
  • String中的"+"
    (1).String中使用 + 字符串連接符進(jìn)行字符串連接時(shí),連接操作最開(kāi)始時(shí)如果都是字符串常量,編譯后將盡可能多的直接將字符串常量連接起來(lái),形成新的字符串常量參與后續(xù)連接(通過(guò)反編譯工具jd-gui也可以方便的直接看出);
    (2).接下來(lái)的字符串連接是從左向右依次進(jìn)行,對(duì)于不同的字符串,首先以最左邊的字符串為參數(shù)創(chuàng)建StringBuilder對(duì)象,然后依次對(duì)右邊進(jìn)行append操作,最后將StringBuilder對(duì)象通過(guò)toString()方法轉(zhuǎn)換成String對(duì)象(注意:中間的多個(gè)字符串常量不會(huì)自動(dòng)拼接)。
    也就是說(shuō)String c = "xx" + "yy " + a + "zz" + "mm" + b; 實(shí)質(zhì)上的實(shí)現(xiàn)過(guò)程是: String c = new StringBuilder("xxyy ").append(a).append("zz").append("mm").append(b).toString();
    由此得出結(jié)論:當(dāng)使用+進(jìn)行多個(gè)字符串連接時(shí),實(shí)際上是產(chǎn)生了一個(gè)StringBuilder對(duì)象和一個(gè)String對(duì)象。
  • String、StringBuffer、StringBuilder的區(qū)別
    (1)可變與不可變:String是不可變字符串對(duì)象,StringBuilder和StringBuffer是可變字符串對(duì)象(其內(nèi)部的字符數(shù)組長(zhǎng)度可變)。
    (2)是否多線程安全:String中的對(duì)象是不可變的,也就可以理解為常量,顯然線程安全。StringBuffer 與 StringBuilder 中的方法和功能完全是等價(jià)的,只是StringBuffer 中的方法大都采用了synchronized 關(guān)鍵字進(jìn)行修飾,因此是線程安全的,而 StringBuilder 沒(méi)有這個(gè)修飾,可以被認(rèn)為是非線程安全的。
    (3)String、StringBuilder、StringBuffer三者的執(zhí)行效率:
    StringBuilder > StringBuffer > String 當(dāng)然這個(gè)是相對(duì)的,不一定在所有情況下都是這樣。比如String str = "hello"+ "world"的效率就比 StringBuilder st = new StringBuilder().append("hello").append("world")要高。因此,這三個(gè)類是各有利弊,應(yīng)當(dāng)根據(jù)不同的情況來(lái)進(jìn)行選擇使用:
    當(dāng)字符串相加操作或者改動(dòng)較少的情況下,建議使用 String str="hello"這種形式;
    當(dāng)字符串相加操作較多的情況下,建議使用StringBuilder,如果采用了多線程,則使用StringBuffer。
  • 關(guān)于String str = new String("abc")創(chuàng)建了多少個(gè)對(duì)象?
    new只調(diào)用了一次,也就是說(shuō)只創(chuàng)建了一個(gè)對(duì)象。而這道題目讓人混淆的地方就是這里,這段代碼在運(yùn)行期間確實(shí)只創(chuàng)建了一個(gè)對(duì)象,即在堆上創(chuàng)建了"abc"對(duì)象。而為什么大家都在說(shuō)是2個(gè)對(duì)象呢,這里面要澄清一個(gè)概念,該段代碼執(zhí)行過(guò)程和類的加載過(guò)程是有區(qū)別的。在類加載的過(guò)程中,確實(shí)在運(yùn)行時(shí)常量池中創(chuàng)建了一個(gè)"abc"對(duì)象,而在代碼執(zhí)行過(guò)程中確實(shí)只創(chuàng)建了一個(gè)String對(duì)象。
    因此,這個(gè)問(wèn)題如果換成 String str = new String("abc")涉及到幾個(gè)String對(duì)象?合理的解釋是2個(gè)。
    個(gè)人覺(jué)得在面試的時(shí)候如果遇到這個(gè)問(wèn)題,可以向面試官詢問(wèn)清楚”是這段代碼執(zhí)行過(guò)程中創(chuàng)建了多少個(gè)對(duì)象還是涉及到多少個(gè)對(duì)象“

String.split()用法小結(jié)

在java.lang包中有String.split()方法,返回是一個(gè)數(shù)組;
1、如果用“.”作為分隔的話,必須是如下寫法,String.split("\."),這樣才能正確的分隔開(kāi),不能用String.split(".");
2、如果用“|”作為分隔的話,必須是如下寫法,String.split("\|"),這樣才能正確的分隔開(kāi),不能用String.split("|");“.”和“|”都是轉(zhuǎn)義字符,必須得加"\";
3、如果在一個(gè)字符串中有多個(gè)分隔符,可以用“|”作為連字符,比如,“acount=? and uu =? or n=?”,把三個(gè)都分隔出來(lái),可以用String.split("and|or");使用String.split方法分隔字符串時(shí),分隔符如果用到一些特殊字符,可能會(huì)得不到我們預(yù)期的結(jié)果。
4、用豎 * 分隔字符串運(yùn)行將拋出java.util.regex.PatternSyntaxException異常,用加號(hào) + 也是如此。
5、顯然, + * 不是有效的模式匹配規(guī)則表達(dá)式,用"\*" "\+"轉(zhuǎn)義后即可得到正確的結(jié)果。"|" 分隔串時(shí)雖然能夠執(zhí)行,但是卻不是預(yù)期的目的,"\|"轉(zhuǎn)義后即可得到正確的結(jié)果。還有如果想在串中使用""字符,則也需要轉(zhuǎn)義.首先要表達(dá)"aaaa\bbbb"這個(gè)串就應(yīng)該用"aaaa\bbbb",如果要分隔就應(yīng)該這樣才能得到正確結(jié)果。

String.trim()

String.Trim()方法會(huì)去除字符串兩端,不僅僅是空格字符,它總共能去除25種字符: ('/t', '/n', '/v', '/f', '/r', ' ', '/x0085', '/x00a0', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '?', '/u2028', '/u2029', ' ', '?')
如果你想保留其中的一個(gè)或多個(gè)(例如/t制表符,/n換行符,/r回車符等),請(qǐng)慎用Trim方法。
請(qǐng)注意,Trim刪除的過(guò)程為從外到內(nèi),直到碰到一個(gè)非空白的字符為止,所以不管前后有多少個(gè)連續(xù)的空白字符都會(huì)被刪除掉

空格 != 空白字符,刪除空格請(qǐng)使用: Trim(‘ ‘);

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 前言 真的懂String么?真的懂String里面的==與equals的差別么?我想說(shuō)原來(lái)可能我懂,但是后來(lái)就沒(méi)有...
    yzzCool閱讀 753評(píng)論 0 3
  • 從網(wǎng)上復(fù)制的,看別人的比較全面,自己搬過(guò)來(lái),方便以后查找。原鏈接:https://www.cnblogs.com/...
    lxtyp閱讀 1,442評(píng)論 0 9
  • 注:都是在百度搜索整理的答案,如有侵權(quán)和錯(cuò)誤,希告知更改。 一、哪些情況下的對(duì)象會(huì)被垃圾回收機(jī)制處理掉 ?當(dāng)對(duì)象對(duì)...
    Jenchar閱讀 3,315評(píng)論 3 2
  • String類 先看一下源碼(jdk1.8.0_144)中的對(duì)于類的定義 首先可以看到String類是被final...
    efan閱讀 671評(píng)論 1 1
  • 前言 RTFSC (Read the fucking source code )才是生活中最重要的。我們天天就是要...
    二毛_coder閱讀 527評(píng)論 1 1

友情鏈接更多精彩內(nèi)容