Java基礎之String的理解與應用,擴展StringBuffer的使用

前言

真的懂String么?真的懂String里面的==與equals的差別么?我想說原來可能我懂,但是后來就沒有后來了。。。

想要了解一個類,最好的辦法就是看這個類的實現源代碼,自己去編譯器里面去看吧。我就不粘貼代碼了。

一、String類

1)String類是final類,也即意味著String類不能被繼承,并且它的成員方法都默認為final方法。在Java中,被final修飾的類是不允許被繼承的,并且該類中的成員方法都默認為final方法。
2)String類其實是通過char數組來保存字符串的。
3)substring,concat,replace從源碼中的這三個方法可以看出,無論是substring、concat還是replace操作都不是在原有的字符串上進行的,而是重新生成了一個新的字符串對象。也就是說進行這些操作后,最原始的字符串并沒有被改變。

在這里要永遠記住一點:“String對象一旦被創建就是固定不變的了,對String對象的任何改變都不影響到原對象,相關的任何change操作都會生成新的對象”。

看下繼承結構源碼:

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

可以看到String是final的,不允許繼承.里面用來存儲value的是一個final數組,也是不允許修改的。

構造方法

public String() {
    this.value = "".value;
}
public String(String original) {
    this.value = original.value;
    this.hash = original.hash;
}
public String(char value[]) {
    this.value = Arrays.copyOf(value, value.length);
}
...

可以看到默認的構造器是構建的空字符串,其實所有的構造器就是給value數組賦初值.

二、字符串常量池

我們知道字符串的分配和其他對象分配一樣,是需要消耗高昂的時間和空間的,而且字符串我們使用的非常多。JVM為了提高性能和減少內存的開銷,在實例化字符串的時候進行了一些優化:使用字符串常量池
每當我們創建字符串常量時,JVM會首先檢查字符串常量池,如果該字符串已經存在常量池中,那么就直接返回常量池中的實例引用。如果字符串不存在常量池中,就會實例化該字符串并且將其放到常量池中。由于String字符串的不可變性我們可以十分肯定常量池中一定不存在兩個相同的字符串(這點對理解上面至關重要)。

Java中的常量池,實際上分為兩種形態:靜態常量池運行時常量池
靜態常量池:即.class文件中的常量池,class文件中的常量池不僅僅包含字符串(數字)字面量,還包含類、方法的信息,占用class文件絕大部分空間。
運行時常量池:則是jvm虛擬機在完成類裝載操作后,將class文件中的常量池載入到內存中,并保存在方法區中,我們常說的常量池,就是指方法區中的運行時常量池

來看下面的程序:

String a = "yzzCool";
String b = "yzzCool";

a、b和字面上的yzzCool都是指向JVM字符串常量池中的"yzzCool"對象,他們指向同一個對象。

String c = new String("yzzCool");

new關鍵字一定會產生一個對象yzzCool(注意這個yzzCool和上面的yzzCool不同),同時這個對象是存儲在堆中。所以上面應該產生了兩個對象:保存在棧中的c和保存堆中abcdef。但是在Java中根本就不存在兩個完全一模一樣的字符串對象。故堆中的abcdef應該是引用字符串常量池中abcdef。所以c、abcdef、池abcdef的關系應該是:c--->yzzCool--->池yzzCool。整個關系如下:


關系圖

圖水平一般,努力看還是能看清楚的???。
總結:雖然a、b、c、yzzCool是不同的對象,但是從String的內部結構我們是可以理解上面的。String c = new String("yzzCool");雖然c的內容是創建在堆中,但是他的內部value還是指向JVM常量池的yzzCool的value,它構造yzzCool時所用的參數依然是yzzCool字符串常量。

三、常見String面試題

3.1 String str = new String(“abc”)創建了多少個實例?

這個問題其實是不嚴謹的,但面試一般會遇到,所以我們要補充來說明。
類的加載和執行要分開來講:
創建了兩個。

1、當加載類時,”abc”被創建并駐留在了字符創常量池中(如果先前加載中沒有創建駐留 過)。

2、當執行此句時,因為”abc”對應的String實例已經存在于字符串常量池中,所以JVM會將此實例復制到會在堆(heap)中并返回引用地址。

3.2 JDK1.7的Intern的執行

   /**
     * 測試intern
     */
    private static void testIntern() {

        System.out.println("======================");
        String s0 = "event";
        String s1 = new String("event");
        String s2 = new String("event");
        s2 = s2.intern(); //把常量池中“event”的引用賦給s2
        System.out.println(s0 == s1);//false
        System.out.println(s0 == s1.intern());//true
        System.out.println(s0 == s2);//true


        System.out.println("======================");
        String s11 = new String("look");
        String s12 = s11.intern();
        String s13 = "look";
        System.out.println(s11 == s12);//false
        System.out.println(s12 == s13);//true
        System.out.println(s11 == s13);//false


        System.out.println("======================");
        String s3 = new String("abc") + new String("abc");
        String s4 = "abcabc";
        String s5 = s3.intern();
        System.out.println(s5 == s3);//false
        System.out.println(s5 == s4);//true
        System.out.println(s3 == s4);//false

        System.out.println("======================");
        String s6 = new String("go") + new String("od");
        String s7 = s6.intern();
        String s8 = "good";
        System.out.println(s6 == s7);//true
        System.out.println(s7 == s8);//true
        System.out.println(s6 == s8);//true

    }

結果如下

false
true
true
======================
false
true
false
======================
false
true
false
======================
true
true
true

擴展1:String、StringBuffer、StringBuilder區別

StringBuffer、StringBuilder和String一樣,也用來代表字符串。

  • String類是不可變類,任何對String的改變都 會引發新的String對象的生成;
  • StringBuffer則是可變類,任何對它所指代的字符串的改變都不會產生新的對象。既然可變和不可變都有了,為何還有一個StringBuilder呢?相信初期的你,在進行append時,一般都會選擇StringBuffer吧!

先說一下集合的故事,HashTable是線程安全的,很多方法都是synchronized方法,而HashMap不是線程安全的,但其在單線程程序中的性能比HashTable要高。StringBuffer和StringBuilder類的區別也是如此,他們的原理和操作基本相同,區別在于StringBufferd支持并發操作,線性安全的,適 合多線程中使用。StringBuilder不支持并發操作,線性不安全的,不適合多線程中使用。新引入的StringBuilder類不是線程安全的,但其在單線程中的性能比StringBuffer高。
適合多線程使用的是StringBuffer,性能就沒有StringBuilder好,其他大致一樣

StringBuffer常用方法

由于StringBuffer和StringBuilder在使用上幾乎一樣,所以只寫一個,以下部分內容網絡各處收集,不再標注出處
1.初始化;

StringBuffer s = new StringBuffer();

這樣初始化出的StringBuffer對象是一個空的對象,

 StringBuffer sb1=new StringBuffer(512);

分配了長度512字節的字符緩沖區。

StringBuffer sb2=new StringBuffer(“how are you?”)

創建帶有內容的StringBuffer對象,在字符緩沖區中存放字符串“how are you?”

2.append()追加

public StringBuffer append(boolean b)

append里面的參數是多種多樣啊!
該方法的作用是追加內容到當前StringBuffer對象的末尾,類似于字符串的連接,調用該方法以后,StringBuffer對象的內容也發生改 變,例如:
StringBuffer sb = new StringBuffer(“abc”);
sb.append(true);
則對象sb的值將變成”abctrue”
使用該方法進行字符串的連接,將比String更加節約內容,經常應用于數據庫SQL語句的連接。
3.deleteCharAt(int index)刪除

public StringBuffer deleteCharAt(int index)

該方法的作用是刪除指定位置的字符,然后將剩余的內容形成新的字符串。例如:
StringBuffer sb = new StringBuffer(“KMing”);
sb. deleteCharAt(1);
該代碼的作用刪除字符串對象sb中索引值為1的字符,也就是刪除第二個字符,剩余的內容組成一個新的字符串。所以對象sb的值變 為”King”。
如果是這么寫:
StringBuffer sb = new StringBuffer(“KMing”);
StringBuffer sb1 = sb. deleteCharAt(1);
你會發現sb和sb1的值是一樣的。
還存在一個功能類似的delete方法:

public StringBuffer delete(int start,int end)

該方法的作用是刪除指定區間以內的所有字符,包含start,不包含end索引值的區間。例如:
StringBuffer sb = new StringBuffer(“TestString”);
sb. delete (1,4);
該代碼的作用是刪除索引值1(包括)到索引值4(不包括)之間的所有字符,剩余的字符形成新的字符串。則對象sb的值是”TString”。

4.insert(int offset, Object obj)插入

public StringBuffer insert(int offset, Object obj)

該方法的作用是在StringBuffer對象中插入內容,然后形成新的字符串。第二個參數可以是好多形式,下面以boolean為例子。
StringBuffer sb = new StringBuffer(“TestString”);
sb.insert(4,false);
該示例代碼的作用是在對象sb的索引值4的位置插入false值,形成新的字符串,則執行以后對象sb的值是”TestfalseString”。

5.reverse()反轉

public StringBuffer reverse()

該方法的作用是將StringBuffer對象中的內容反轉,然后形成新的字符串。例如:
StringBuffer sb = new StringBuffer(“abc”);
sb.reverse();
經過反轉以后,對象sb中的內容將變為”cba”。

6.setCharAt(int index, char ch)插入字符

public void setCharAt(int index, char ch)

該方法的作用是修改對象中索引值為index位置的字符為新的字符ch。例如:
StringBuffer sb = new StringBuffer(“abc”);
sb.setCharAt(1,’D’);
則對象sb的值將變成”aDc”。

7.trimToSize()縮小存儲空間

public void trimToSize()

該方法的作用是將StringBuffer對象的中存儲空間縮小到和字符串長度一樣的長度,減少空間的浪費,和String的trim()是一樣的作用,不在舉例。

8.length()得到字符的長度

 public  void setLength(int newLength)

該方法的作用是設置字符串緩沖區大小。
StringBuffer sb=new StringBuffer();
sb.setlength(100);
如果用小于當前字符串長度的值調用setlength()方法,則新長度后面的字符將丟失。
意思就是:我們現在的StringBuffer的長度是120,如果我們設置setLength(100),則我們的StringBuffer最終的長度是100,如果是提前運行setLength(100),然后進行append()操作,則結果是是多少就是多少。

9.capacity()得到字符串容量的大小

 public int capacity() 

該方法的作用是獲取字符串的容量。
StringBuffer sb=new StringBuffer(“string”);
int i=sb.capacity();

10.ensureCapacity(int minimumCapacity)設置字符串的容量。

 public void ensureCapacity(int minimumCapacity)

該方法的作用是重新設置字符串容量的大小。
StringBuffer sb=new StringBuffer();
sb.ensureCapacity(32); //預先設置sb的容量為32
注意:我們來分析一下capacity()和ensureCapacity(int minimumCapacity),如果我們設置ensureCapacity(30),但是我們得到的capacity()的值并不是30,這是什么玩意???下面自己的理解:這里設置容量就是提前告訴字符串,我會達到這個水平,有一個明顯的現象就是字符串有一個默認的容量是16,當目前的容量小于16的時候,用capacity()得到的值都是16,當容量大于等于16時,要擴容了擴容的原則是“目前的容量+2”就是16+16+2=34,容量在16~34之間時,capacity()得到的值都是34..........以后根據這個原則擴容。
又有問題了,我的容量不是16,34,80.....這是什么情況??嘿嘿,是這樣的如果你在新建StringBuffer的時候是這樣寫的,如下:

  StringBuffer buffer1 = new StringBuffer("1234567890");

那么他的初始容量不是16,是“16+字符串的長度”,也就是上面的容量是10+16=26;那么它的規則就是26,54,110.........

11.getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)處理字符串的子字符串復制給數組。

 public  void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)

該方法的作用是將字符串的子字符串復制給數組。
StringBuffer sb = new StringBuffer("I love You");
int begin = 0;
int end = 5;
//注意ch字符數組的長度一定要大于等于begin到end之間字符的長度
//小于的話會報ArrayIndexOutOfBoundsException
//如果大于的話,大于的字符會以空格補齊
char[] ch = new char[end-begin];
sb.getChars(begin, end, ch, 0);
System.out.println(ch);
結果:I lov
參數的意義:srcBegin:從字符串的第幾位開始,(包括這一位);
srcEnd:從字符串的第幾位開始,(不包括這一位);
dst:要操作的字符串數組;
dstBegin:從數組的第幾個下表開始處理。

注意:

上面的解釋如果感覺不是很詳細可以看Java中String的實現與應用這篇博客。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,182評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,489評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,290評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,776評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,510評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,866評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,860評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,036評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,585評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,331評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,536評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,058評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,754評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,154評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,469評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,273評論 3 399
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,505評論 2 379

推薦閱讀更多精彩內容