深刻理解字符串

字符串介紹

字符串是程序開發當中,使用最頻繁的類型之一,有着與基礎類型相同的地位,甚至在JVM(Java虛擬機)編譯的時候對字符串作特殊的處理,好比拼接操做可能會被JVM直接合成一個最終的字符串,從而達到高效運行的目的.編程

1 String 特性

  • String是標準的不可變類(immutable),對它的任何改動,其實就是建立了一個新對象,再把引用指向該對象;
  • String對象賦值以後就會在常量池中緩存,若是下次建立會斷定常量池是否已經有緩存對象,若是有的話直接返回該引用給建立者.

2 字符串建立

字符串建立的兩種方式:緩存

  • String str = "laowang";
  • String str = new String("laowang");

3 注意事項

查看下面代碼:安全

        String s1 = "laowang";
        String s2 = s1;
        String s3 = new String(s1);
        System.out.println(s1 == s2);
        System.out.println(s1 == s3);

輸出結果:  true , false 多線程

爲何會這樣?緣由是 s3 使用 new String 時必定會在堆中從新建立一個內存區域,而 s2 則會直接使用了s1 的引用,因此獲得的結果也徹底不一樣.app

字符串的使用

1 字符串拼加

字符串拼加的幾種方式:性能

  • String str = "lao" + "wang";
  • String str = "lao"; str += "wang";
  • String str = "lao"; String str2 = str + "wang";

2 JVM 對字符串的優化

根據前面的知識咱們知道,對於String的任何操做實際上是建立了一個新對象,而後再把引用地址返回該對象,但JVM也會對String進行特殊處理,以此來提升程序的運行效率,好比如下代碼:測試

String str = "hi," + "lao" + "wang"; 

通過JVM優化後的代碼是這樣的:優化

String str = "hi, laowang";

驗證代碼以下:ui

        String str1 = "hi," + "lao" + "wang";
        String str2 = "hi,laowang";
        System.out.println(str1 == str2);

執行結果:  true this

這就說明JVM在某些狀況下回特殊處理String類型.

3 字符串截取

字符串截取使用  substring() 方法,使用以下:

        String str = "abcdef";
        // 結果: cdef (從下標爲2的開始截取到最後,包含開始下標)
        System.out.println(str.substring(2));
        // 結果: cd (從下標爲2的開始截取到下標爲4的,包含開始下標不包含結束下標)
        System.out.println(str.substring(2,4));

4 字符串格式化

字符串格式化可讓代碼更簡潔更直觀,好比"我叫老王,今年26歲,喜歡讀書"在這條信息中,姓名,年齡,興趣都是要動態改變的,若是使用"+"號拼接的話很容易出錯,這個時候字符串格式化方法String.format() 就派上用場了,代碼以下:

        String str = String.format("我叫%s,今年%d歲,喜歡%s","老王",26,"讀書");

轉換符說明列表:

轉換符 說明
%s 字符串類型
%d 整數類型(十進制)
%c 字符類型
%b 布爾類型
%x 整數類型(十六進制)
%o 整數類型(八進制)
%f 浮點類型
%a 浮點類型(十六進制)
%e 指數類型
%% 百分比類型
%n 換行符

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

5 字符串對比

根據前面的知識咱們知道,使用String 和 new String聲明的對象是不一樣的,那有沒有簡單的方法,能夠忽略他們的建立方式(有沒有new)而只對比他們的值是否相同呢? 答案是確定的,使用 equals() 方法能夠實現,代碼以下:

        String s1 = "hi," + "lao" + "wang";
        String s2 = "hi,";
        s2 += "lao";
        s2 += "wang";
        String s3 = "hi,laowang";
        System.out.println(s1.equals(s2));  // true
        System.out.println(s1.equals(s3));  // true
        System.out.println(s2.equals(s3));  // true

 以上使用equals 對比的結果都爲true.

若是要忽略字符串的大小寫對比值可使用equalsIgoreCase(), 代碼示例:

        String s1 = "Hi,laowang";
        String s2 = "hi,laowang";
        System.out.println(s1.equals(s2));    // false
        System.out.println(s1.equalsIgnoreCase(s2));    // true

6 String , StringBuffer, StringBuilder

字符串相關類型主要有這三種: String, StringBuffer, StringBuilder, 其中StringBuffer, StringBuilder 都是可變的字符串類型, StringBuffer在字符串拼接時使用synchronized來保障線程安全,所以在多線程字符串拼接中推薦使用StringBuffer.

StringBuffer 使用:

 

        StringBuffer sf = new StringBuffer("lao");
        // 添加字符串到尾部
        sf.append("wang");                  // 執行結果: laowang
        System.out.println(sf);
        // 插入字符串到當前字符串下標的位置
        sf.insert(0,"hi,");     // 執行結果: hi,laowang
        System.out.println(sf);
        // 修改字符串中的某個下標的值
        sf.setCharAt(0,'H');    // 執行結果: Hi,laowang
        System.out.println(sf);

StringBuilder 的使用方法和StringBuffer同樣,他們都繼承於AbstractStringBuilder.

小測試~~~

1.String 屬於基礎數據類型嗎?

答: String不是基礎數據類型,它是從堆上分配來的.基礎數據類型有8個,分別是: boolean, byte, short, int, long, float, double, char.

2.如下能夠正確獲取字符串長度的是?

A:  str.length

B:  str.size

C:  str.length()

D:  str.size()

答案: C

題目解析:  字符串沒有length屬性,只有length()方法.

3. "==" 和 equals 的區別是什麼?

答: "==" 對基本類型來講是值比較,對於引用類型來講比較的是引用; 而equals默認狀況下是引用比較, 只是不少類重寫了equals方法,好比String, Integer 等把它變成了值比較,因此通常狀況下equals 比較的是值是否相等.

  ① "==" 解讀

對於基本類型和引用類型 == 的做用效果是不一樣的,以下所示:

  • 基本類型: 比較的是值是否相同
  • 引用類型: 比較的是引用是否相同

代碼實例:

        String x = "string";
        String y = "string";
        String z = new String("string");
        System.out.println(x==y);   // true
        System.out.println(x==z);   // false
        System.out.println(x.equals(y));    // true
        System.out.println(x.equals(z));    // true

代碼說明: 由於x和y指向同一個引用,因此 == 也是true, 而new String()方法則從新開闢了內存空間, 因此 == 結果爲 false,而equals 比較的一直是值, 因此結果都爲 true.

② equals 解讀

equals 本質上就是 == , 只不過 String 和 Integer 等重寫了 equals 方法,把它變成了值比較. 看下面的代碼就明白了.

首先來看默認狀況下equals 比較一個有相同值得對象,代碼以下: 

        class Cat{
            private String name;

            public Cat(String name) {
                this.name = name;
            }

            public String getName() {
                return name;
            }

            public void setName(String name) {
                this.name = name;
            }
        }

        Cat c1 = new Cat("薰悟空");
        Cat c2 = new Cat("薰悟空");
        System.out.println(c1.equals(c2));      // false

源碼以下:

    public boolean equals(Object obj) {
        return (this == obj);
    }

原來equals 本質上就是 == .

那麼問題來了, 兩個相同值的 String 對象, 爲何返回的是 true 呢? 代碼以下:

        String s1 = new String("薰悟空");
        String s2 = new String("薰悟空");
        System.out.println(s1.equals(s2));          // true

  一樣的,咱們進入String的equals 方法, 找到了答案, 代碼以下: 

    public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof 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) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }

原來是String 重寫了Object的equals方法,把引用比較改爲了值比較.

總結來講, "==" 對於基本類型來講是值比較, 對於引用類型來講比較的是引用; 而 equas 默認狀況下是 引用比較, 只是不少類重寫了 equas 方法, 好比 String , Integer 等把它變成了值比較, 因此通常狀況下 equals 比較的是值是否相等.

4. 如下代碼輸出的結果是?
        String str = "laowang";
        str.substring(0,1);
        System.out.println(str);

A:  l

B:  a

C:  la

D:  laowang

答: D

題目解析: 由於 String 的substring() 方法不會修改原字符串內容, 因此結果仍是 laowang.

5.如下字符串對比的結果是什麼?
        String s1 = "hi," + "lao" + "wang";
        String s2 = "hi,";
        s2 += "lao";
        s2 += "wang";
        String s3 = "hi,laowang";
        System.out.println(s1 == s2);
        System.out.println(s1 == s3);
        System.out.println(s2 == s3);

答: false  true  false

題目解析:  String s1 = "hi," + "lao" + "wang" 代碼會被JVM優化爲: String s1 = "hi,laowang", 這樣就和s3 徹底相同, s1 建立的時候會把字符串"hi,laowang" 放入常量池, s3 建立的時候,常量池中已經存在對應的緩存,會直接把引用返回給s3 , 因此 s1 == s3 就爲 true, 而 s2 使用了 += 其引用地址就和其餘兩個不一樣.

6. 如下String傳值修改後執行的結果是什麼?
    public static void main(String[] args) {
        String str = new String("laowang");
        change(str);
        System.out.println(str);
    }

    public static void change(String str){
        str = "xiaowang";
    }

答: laowang

7.如下StringBuffer 傳值修改後的執行結果是什麼?

    public static void main(String[] args) {
        StringBuffer sf = new StringBuffer("hi,");
        change(sf);
        System.out.println(sf);
    }

    public static void change(StringBuffer sf){
        sf.append("laowang");
    }

答: hi,laowang

題目解析: String 爲不可變類型, 在方法內對String修改的時候, 至關於修改傳遞過來的是一個String 的副本, 因此String 自己的值是不被修改的, 而 StringBuffer 爲可變類型, 參數傳遞過來的是對象的引用, 對其修改它自己就會發生改變.

 8.如下使用substring執行的結果是什麼?
        String str = "abcdef";
        System.out.println(str.substring(3,3));

答: "" (空)

9.斷定字符串是否爲空,有幾種方式?
  • str.equals("");
  • str.length()==0
10.String, StringBuffer, StringBuilder 的區別是什麼?

答:如下是String, StringBuffer, StringBuilder 的區別:

  • 可變性: String爲字符串常量是不可變對象, StringBuffer 與 StringBuilder爲字符串變量是可變對象.
  • 性能: String每次修改至關於生成一個新對象,所以性能最低; StringBuffer使用Synchronized 來保證線程安全, 性能優於String , 但不如StringBuilder;
  • 線程安全: StringBuilder 爲非線程安全類, StringBuffer 爲線程安全類.
11. String 對象的intern()有什麼做用?

答: intern() 方法用於查找常量池中是否存在該字符值, 若是常量池中不存在則先在常量池中建立, 若是已經存在則直接返回.

示例代碼:

        String s = "laowang";
        String s2 = s.intern();
        System.out.println(s == s2);    // true
12. String s = new String("laowang") 建立了幾個對象?

答: 總共建立了兩個對象, 一個是字符串"laowang", 另外一個是指向字符串的變量 s.  new String() 無論常量池有沒有相同的字符串, 都會在內存(非字符串常量池)中建立一個新的對象.

13.什麼是字符串常量池?

字符串常量池是存儲在Java堆內存中的字符串池, 是爲防止每次新建字符串帶的時間和空間消耗的一種解決方案. 在建立字符串時JVM會首先檢查字符串常量池,若是字符串已經存在池中,就返回池中的實例引用, 若是字符串不在池中, 就會實例化一個字符串放到池中並把當前引用指向該字符串.

14.String 不可變性都有哪些好處?

答: 不可變的好處以下:

  • 只有當字符串是不可變的,字符串常量池才能實現,字符串池的實現能夠在運行時節約不少堆空間,由於不一樣的字符串變量都指向池中的同一個字符串.
  • 能夠避免一些安全漏洞, 好比在Socket編程中,主機名和端口都是以字符串的形式傳入,由於字符串是不可變的,因此它的值是不可改變的,不然黑客們能夠鑽到空子,改變字符串指向的對象的值,形成安全漏洞.
  • 多線程安全,由於字符串是不可變得,因此同一個字符串實例能夠被多個線程共享,保證了多線程的安全性;
  • 適合作緩存的key,由於字符串是不可變的,因此在它建立的時候哈希值就被緩存了,不須要從新計算速度更快,因此字符串很適合做緩存中的key.
15.String類是否能夠被繼承?爲何?

答: String不能被繼承. 由於String 被聲明爲final (最終類), 因此不能被繼承.

相關文章
相關標籤/搜索