成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

簡談Java String

ssshooter / 2904人閱讀

摘要:而用關(guān)鍵字調(diào)用構(gòu)造器,總是會創(chuàng)建一個新的對象,無論內(nèi)容是否相同。中對象的哈希碼被頻繁地使用比如在等容器中。字符串不變性保證了碼的唯一性因此可以放心地進行緩存。對于所有包含方式新建對象包括的連接表達式,它所產(chǎn)生的新對象都不會被加入字符串池中。

前言

前陣子和同事在吃飯時聊起Java的String,覺得自己之前的筆記寫的略顯零散。故此又重新整理了一下。

String在Java中算是一個有意思的類型,是final類型,因此不可以繼承這個類、不能修改這個類。

兩個小問題

我們先來看一段代碼:

String s = "Hello";
s = s + " world!";

試問:這兩行代碼執(zhí)行后,原始的 String 對象中的內(nèi)容到底變了沒有?

答案是沒有。因為 String 被設(shè)計成不可變(immutable)類,所以它的所有對象都是不可變對象。在 這段代碼中,s 原先指向一個 String 對象,內(nèi)容是 "Hello",然后我們對 s 進行了+操作。這時,s 不指向原來那個對象了, 而指向了另一個 String 對象,內(nèi)容為"Hello world!",原來那個對象還存在于內(nèi)存之中,只 是 s 這個引用變量不再指向它了。

通過上面的說明,我們很容易導出另一個結(jié)論,如果經(jīng)常對字符串進行各種各樣的修改,或 者說,不可預見的修改,那么使用 String 來代表字符串的話會引起很大的內(nèi)存開銷。因為 String 對象建立之后不能再改變,所以對于每一個不同的字符串,都需要一個 String 對象來 表示。這時,應該考慮使用 StringBuffer類,它允許修改,而不是每個不同的字符串都要生 成一個新的對象。并且,這兩種類的對象轉(zhuǎn)換十分容易。

同時,我們還可以知道,如果要使用內(nèi)容相同的字符串,不必每次都 new 一個 String。例 如我們要在構(gòu)造器中對一個名叫 s 的 String 引用變量進行初始化,把它設(shè)置為初始值,應當這樣做:

 public class Demo {
   private String s;
   ...
   public Demo {
     s = "Initial Value";
      }
      ...
      //而非 s = new String("Initial Value");
}

前者每次都會調(diào)用構(gòu)造器,生成新對象,性能低下且內(nèi)存開銷大,并且沒有意義,因為 String 對象不可改變,所以對于內(nèi)容相同的字符串,只要一個 String 對象來表示就可以了。也就 說,多次調(diào)用上面的構(gòu)造器創(chuàng)建多個對象,他們的 String 類型屬性 s 都指向同一個對象。 上面的結(jié)論還基于這樣一個事實:對于字符串常量,如果內(nèi)容相同,Java 認為它們代表同 一個 String 對象。而用關(guān)鍵字 new 調(diào)用構(gòu)造器,總是會創(chuàng)建一個新的對象,無論內(nèi)容是否 相同。

再請大家看一段代碼:

String s = new String("xyz");

問題:創(chuàng)建了幾個 String Object?二者之間有什么區(qū)別?

一個或兩個 。

”xyz”對應一個對象,這個對象放在字符串常量池,常量”xyz”不管出現(xiàn)多少遍,都是緩沖區(qū)中的那一個。New String 每寫一遍,就創(chuàng)建一個新的對象在堆上。

如果以前就用過’xyz’,這句代表就不會 創(chuàng)建”xyz”自己了,直接從字符串常量池拿。

常量池

在Java中,其實有很多常量池相關(guān)的概念:

常量池表(constant_pool table)

Class文件中存儲所有常量(包括字符串)的table

這是Class文件中的內(nèi)容,還不是運行時的內(nèi)容,不要理解它是個池子,其實就是Class文件中的字節(jié)碼指令

運行時常量池(Runtime Constant Pool)

JVM內(nèi)存中方法區(qū)的一部分,這是運行時的內(nèi)容

這部分內(nèi)容(絕大部分)是隨著JVM運行時候,從常量池轉(zhuǎn)化而來,每個Class對應一個運行時常量池

前面說的絕大部分是因為:除了 Class中常量池內(nèi)容,還可能包括動態(tài)生成并加入這里的內(nèi)容

字符串常量池(String Pool)

這部分也在方法區(qū)中,但與Runtime Constant Pool不是一個概念,String Pool是JVM實例全局共享的,全局只有一個

JVM規(guī)范要求進入這里的String實例叫“被駐留的interned string”,各個JVM可以有不同的實現(xiàn),HotSpot是設(shè)置了一個哈希表StringTable來引用堆中的字符串實例,被引用就是被駐留。

類似這種常量池的思想即涉及到了一個設(shè)計模式——享元模式。顧名思義,共享元素。

也就是說:一個系統(tǒng)中如果有多處用到了相同的一個元素,那么我們應該只存儲一份此元素,而讓所有地方都引用這一個元素

不可變的String

那么為什么要不可變呢?

主要是為了安全與效率。

安全

String被許多的Java類庫用來當做參數(shù)。例:

URL、IP

文件路徑path

反射機制所需要的String參數(shù)

等等...

假若String不是固定不變的,將會引起各種安全隱患。

效率

在前面提到過常量池的享元模式。這樣在拷貝或者創(chuàng)建內(nèi)容相同對象時,就不必復制對象本身,而是只要引用它即可。這樣的開銷比起copy object是天差地別的。另外,也就只有不可變對象才能使用常量池,因為可以保證引用同一常量值的多個變量不產(chǎn)生相互影響。

同樣也是由于String對象的不可變特性,所以String對象可以自身緩存HashCode。Java中String對象的哈希碼被頻繁地使用, 比如在hashMap 等容器中。字符串不變性保證了hash碼的唯一性,因此可以放心地進行緩存。這也是一種性能優(yōu)化手段,意味著不必每次都去計算新的哈希碼:

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

    /** Cache the hash code for the string */
    private int hash; // Default to 0
其他 String 和 StringBuffer

JAVA 平臺提供了兩個類:String 和 StringBuffer,它們可以儲存和操作字符串,即包含多個字符的字符數(shù)據(jù)。這個 String 類提供了數(shù)值不可改變的字符串。而這個 StringBuffer 類提供 的字符串進行修改。當你知道字符數(shù)據(jù)要改變的時候你就可以使用 StringBuffer。典型地, 你可以使用 StringBuffers 來動態(tài)構(gòu)造字符數(shù)據(jù)。另外,String 實現(xiàn)了 equals 方法,new String(“abc”).equals(newString(“abc”)的結(jié)果為true,而StringBuffer沒有實現(xiàn)equals方法, 所以,new StringBuffer(“abc”).equals(newStringBuffer(“abc”)的結(jié)果為 false。

接著要舉一個具體的例子來說明,我們要把1到100的所有數(shù)字拼起來,組成一個串。

StringBuffer sbf = new StringBuffer();
 for(int i=0;i<100;i++){
           sbf.append(i);
    }

上面的代碼效率很高,因為只創(chuàng)建了一個 StringBuffer 對象,而下面的代碼效率很低,因為 創(chuàng)建了101個對象。

 String str = new String();
   for(int i=0;i<100;i++) {
             str = str + i;
}

在講兩者區(qū)別時,應把循環(huán)的次數(shù)搞成10000,然后用 endTime-beginTime 來比較兩者執(zhí) 行的時間差異。

String 覆蓋了 equals 方法和 hashCode 方法,而 StringBuffer沒有覆蓋 equals 方法和 hashCode 方法,所以,將 StringBuffer對象存儲進 Java集合類中時會出現(xiàn)問題

StringBuilder與 StringBuffer

StringBuilder不是線程安全的,但是單線程中中的性能比StringBuffer高。

Demo Code String對象創(chuàng)建方式
  String str1 = "abcd";
  String str2 = new String("abcd");
  System.out.println(str1==str2);//false

這兩種不同的創(chuàng)建方法是有差別的:

第一種方式是在常量池中拿對象

第二種方式是直接在堆內(nèi)存空間創(chuàng)建一個新的對象。只要使用new方法,便會創(chuàng)建新的對象

連接表達式 +

只有使用引號包含文本的方式創(chuàng)建的String對象之間使用“+”連接產(chǎn)生的新對象才會被加入字符串池中。

對于所有包含new方式新建對象(包括null)的“+”連接表達式,它所產(chǎn)生的新對象都不會被加入字符串池中。

String str1 = "str";
String str2 = "ing";

String str3 = "str" + "ing";
String str4 = str1 + str2;
System.out.println(str3 == str4);//false

String str5 = "string";
System.out.println(str3 == str5);//true
連接表達式demo1
public static final String str1 = "ab";
public static final String str2 = "cd";

public static void main(String[] args) {
    String s = str1 + str2;  // 將兩個常量用+連接對s進行初始化
    String t = "abcd";
    if (s == t) {
        System.out.println("s等于t,它們是同一個對象");
    } else {
        System.out.println("s不等于t,它們不是同一個對象");
    }
}

s等于t,它們是同一個對象

A和B都是常量,值是固定的,因此s的值也是固定的,它在類被編譯時就已經(jīng)確定了。也就是說:String s=A+B; 等同于:String s="ab"+"cd";

連接表達式demo2
public static final String str1;
public static final String str2;

static {
    str1 = "ab";
    str2 = "cd";
}

public static void main(String[] args) {
// 將兩個常量用+連接對s進行初始化
    String s = str1 + str2;
    String t = "abcd";
    if (s == t) {
        System.out.println("s等于t,它們是同一個對象");
    } else {
        System.out.println("s不等于t,它們不是同一個對象");
    }
}

s不等于t,它們不是同一個對象

A和B雖然被定義為常量,但是它們都沒有馬上被賦值。在運算出s的值之前,他們何時被賦值,以及被賦予什么樣的值,都是個變數(shù)。因此A和B在被賦值之前,性質(zhì)類似于一個變量。那么s就不能在編譯期被確定,而只能在運行時被創(chuàng)建了。

intern方法

運行時常量池相對于Class文件常量池的另外一個重要特征是具備動態(tài)性,Java語言并不要求常量一定只有編譯期才能產(chǎn)生,也就是并非預置入Class文件中常量池的內(nèi)容才能進入方法區(qū)運行時常量池,運行期間也可能將新的常量放入池中,這種特性被開發(fā)人員利用比較多的就是String類的intern()方法。

String的intern()方法會查找在常量池中是否存在一份equal相等的字符串,如果有則返回該字符串的引用,如果沒有則添加自己的字符串進入常量池。

public static void main(String[] args) {    
   String s1 = new String("計算機");
   String s2 = s1.intern();
   String s3 = "計算機";
   System.out.println("s1 == s2? " + (s1 == s2));
   System.out.println("s3 == s2? " + (s3 == s2));
}
//s1 == s2? false
//s3 == s2? true
一個較為豐富的demo
public class Test {
    public static void main(String[] args) {
        String hello = "Hello", lo = "lo";
        System.out.println((hello == "Hello") + " ");
        System.out.println((Other.hello == hello) + " ");
        System.out.println((other.Other.hello == hello) + " ");
        System.out.println((hello == ("Hel" + "lo")) + " ");
        System.out.println((hello == ("Hel" + lo)) + " ");
        System.out.println(hello == ("Hel" + lo).intern());
    }
}
class Other {
    static String hello = "Hello";
}
package other;

public class Other {
    public static String hello = "Hello";
}
//true true true true false true

在同包同類下,引用自同一String對象

在同包不同類下,引用自同一String對象

在不同包不同類下,依然引用自同一String對象

在編譯成.class時能夠識別為同一字符串的,自動優(yōu)化成常量,引用自同一String對象

在運行時創(chuàng)建的字符串具有獨立的內(nèi)存地址,所以不引用自同一String對象

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/67997.html

相關(guān)文章

  • 簡談Java Enum

    摘要:常量接口是對接口的一種不良使用。如果這些常量最好被看作是枚舉類型成員,那就應該用枚舉類型來導出。因為客戶端既不能創(chuàng)建枚舉類型的實例,也不能對它進行擴展,因此很可能沒有實例,而只有聲明過的枚舉常量。換句話說,枚舉類型是實例受控的。 問題 我們偶爾能在項目中看到如下風格的代碼: public class ResponseCode { public static final int ...

    BicycleWarrior 評論0 收藏0
  • 簡談文件下載的三種方式

    摘要:一前言本文章將以報表下載為例,給大家介紹三種文件下載的方式。通過二進制數(shù)據(jù)流的方式下載這種方式是我目前采用的方式,用于處理報表下載。缺點對于數(shù)據(jù)量不大的文件,這種方式是可行的。 一、前言 本文章將以excel報表下載為例,給大家介紹三種文件下載的方式。 原文地址:簡談文件下載的三種方式 | Rychou 二、正文 1. 通過服務(wù)器文件地址下載 這是最常見的文件下載方式,大多數(shù)網(wǎng)站的音頻...

    lsxiao 評論0 收藏0
  • 簡談文件下載的三種方式

    摘要:一前言本文章將以報表下載為例,給大家介紹三種文件下載的方式。通過二進制數(shù)據(jù)流的方式下載這種方式是我目前采用的方式,用于處理報表下載。缺點對于數(shù)據(jù)量不大的文件,這種方式是可行的。 一、前言 本文章將以excel報表下載為例,給大家介紹三種文件下載的方式。 原文地址:簡談文件下載的三種方式 | Rychou 二、正文 1. 通過服務(wù)器文件地址下載 這是最常見的文件下載方式,大多數(shù)網(wǎng)站的音頻...

    2i18ns 評論0 收藏0
  • 簡談JavaScript閉包

    摘要:所以經(jīng)常看到的說閉包就是綁定了上下文環(huán)境的函數(shù)。我更偏向于閉包是一個函數(shù)和聲明該函數(shù)的詞法環(huán)境的組合。里面的閉包先上一個閉包該例子的解釋上面的代碼,在函數(shù)里面定義的函數(shù)和這個函數(shù)聲明的詞法環(huán)境就形成了一個閉包。 閉包是什么 第一種說法:閉包創(chuàng)建一個詞法作用域,這個作用域里面的變量被引用之后可以在這個詞法作用域外面被自由訪問,是一個函數(shù)和聲明該函數(shù)的詞法環(huán)境的組合 第二種說法:閉包就是...

    Zachary 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<