1. 程式人生 > 其它 >JAVA String介紹、常量池及String、StringBuilder和StringBuffer得區別. 以及8種基本型別的包裝類和常量池得簡單介紹

JAVA String介紹、常量池及String、StringBuilder和StringBuffer得區別. 以及8種基本型別的包裝類和常量池得簡單介紹

一、概述

  String是代表字串的類,本身是一個最終類,使用final修飾,不能被繼承。

二、定義方式

  

  方式一:直接賦值法

String str1 = "hello";

  方式二:建立法

String str2 = new String("hello");

  方式三:建立一個字元陣列ch,new String ( ch )

char chs[] = {'h','e','l','l','l','o'};
String str3 = new String(chs);

三、記憶體

  字串在記憶體中是以字元陣列的形式來儲存的。

  在此之前我們要先引入一個概念常量是在編譯期存放在常量池中的。

我們常說的常量池,就是指方法區中的執行時常量池。

執行時常量池在jvm記憶體結構的方法區中。

因為常量是編譯期可以得知的,在編譯期會執行全是常量的計算式(編譯器優化),把常量以及計算結果值(也是常量)存放在常量池中,減輕執行期的負擔。

方法呼叫和new是編譯期無法得知的,不會放在常量池,而是在執行期放在堆記憶體中。

  接下來我們通過一系列的練習來熟悉 字串常量池以及 字串型別資料在記憶體中的存放。

    public static void main(String[] args) {
        String str1 = "hello";
        String str2 
= new String("hello"); System.out.println(str1 == str2); String str3 = "hello"; System.out.println(str1 == str3); }

  以上程式碼得執行結果是什麼呢?

  

  這個時候大家會不會疑惑 這不都是hello嗎?怎麼會不相同呢? 不要著急,接下來我來給畫圖講解一下

  

  1.   “hello” 如果存放在常量池當中,就會佔用記憶體,假如這個hello得空間地址為aaa,那麼str1存放得就是hello得地址aaa
  2.   str2建立一個String物件,那麼這個時候肯定在堆上面開闢一塊空間,假設這個地址是bbb,在這個String 物件中,存在一個value[] 儲存著 orginal傳入的字串,這個val ==“hello”,因為在字串常量池中已經有了"hello",所以val 直接指向 常量池中的"hello".但是str2 指向的依然是 bbb在堆中的空間。所以 System.out.println(str1 == str2) 為false
  3.   之後呢,str3 也等於"hello",他也準備把hello放在常量池當中.此時常量池中已經存在"hello",那麼之後str3 在存放"hello"地址的時候,就指向的是常量池中原來hello的地址。所以System.out.println(str1 == str3); 為true

注:此為String得原始碼
public
final class String implements java.io.Serializable, Comparable<String>, 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 /** use serialVersionUID from JDK 1.0.2 for interoperability */ private static final long serialVersionUID = -6849794470754667710L; /** * Class String is special cased within the Serialization Stream Protocol. * * A String instance is written into an ObjectOutputStream according to * <a href="{@docRoot}/../platform/serialization/spec/output.html"> * Object Serialization Specification, Section 6.2, "Stream Elements"</a> */ private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0]; /** * Initializes a newly created {@code String} object so that it represents * an empty character sequence. Note that use of this constructor is * unnecessary since Strings are immutable. */ public String() { this.value = "".value; } /** * Initializes a newly created {@code String} object so that it represents * the same sequence of characters as the argument; in other words, the * newly created string is a copy of the argument string. Unless an * explicit copy of {@code original} is needed, use of this constructor is * unnecessary since Strings are immutable. * * @param original * A {@code String} */ public String(String original) { this.value = original.value; this.hash = original.hash; } }

  再看另一組練習

  

public static void main(String[] args) {

        String str1 = "hello";
        String str2 = "hel"+"lo";
        System.out.println(str1==str2);

        String str3 = new String("hel")+"lo";
        System.out.println(str1==str3);

    }

  請判斷兩次列印結果是什麼?

  

  1. str1 指向字串常量池中的 “hello”
  2. str2 是"hel"與"lo" 組合而成的,常量在編譯的時候就已經確定了,所以在編譯時,已經被處理為"hello",所以也指向 常量池中的"hello"。
  3.  str3 首先new 了一個String(“hel”)物件,在堆中開闢一塊空間,這個物件中的"hel"同時存放在常量池中,之後又在常量池中開闢一塊空間存放 “lo”。兩塊部分之間的"+",將 String 的物件 與常量池中的 "lo"結合在堆中再次開闢一塊新的空間,這塊記憶體中的val ==“hello”,str3指向的是合併之後的物件 ,地址為ddd.

四、字串常量池

    

  在上面的例子中, String類的兩種例項化操作, 直接賦值和 new 一個新的 String.

  String類的設計使用了共享設計模式

  在JVM底層實際上會自動維護一個物件池(字串常量池)

    如果現在採用了直接賦值的模式進行String類的物件例項化操作,那麼該例項化物件(字串內容)將自動儲存到這個物件池之中.

    如果下次繼續使用直接賦值的模式宣告String類物件,此時物件池之中如若有指定內容,將直接進行引用

    如若沒有,則開闢新的字串物件而後將其儲存在物件池之中以供下次使用
  

  理解 “池” (pool)

    “池” 是程式設計中的一種常見的, 重要的提升效率的方式, 我們會在未來的學習中遇到各種 “記憶體池”, “執行緒池”, “資料庫連線池” …然而池這樣的概念不是計算機獨有, 也是來自於生活中.

String str = new String("hello");

這樣的做法有兩個缺點:


1.  如果使用String構造方法就會開闢兩塊堆記憶體空間,並且其中一塊堆記憶體將成為垃圾空間(字串常量 “hello” 也是一個匿名物件, 用了一次之後就不再使用了, 就成為垃圾空間, 會被 JVM 自動回收掉).

2.  字串共享問題. 同一個字串可能會被儲存多次, 比較浪費空間.

intern(),手動入池

String str1 = "hello";
String str2 = new String("hello").intren();

傳入構造方法的字串在字串常量池中是否存在,如果有的話,就把常量池中的引用傳給當前的引用型別變數。

五、理解字串不可變

public static void main(String[] args) {
        String str = "hello" ;
        str = str + " world" ;
        str += "!!!" ;
        System.out.println(str);
    }

 對於這種程式碼,乍一看我們以為成功的將str 每次與其他的字串拼接,但是這樣是不可以的, str 原來指向的是"hello",但是 在與" world"拼接之後,又會產生一個新的物件"helll world",再次拼接一個"!!!",那麼又會產生一個新的物件"hello world!!!",在記憶體中就會產生多個物件。

我們最後需要的是"hello world!!!",但是卻開闢了5塊記憶體空間。

如果在一個迴圈中拼接,那麼會開闢更多的記憶體空間!!

所以這樣的程式碼是極為不可取的!!!

那麼如何拼接呢,具體在之後的StringBuff、StringBuilder中介紹。

六、StringBuffer 和 StringBuilder

 StringBuffer 和 StringBuilder 又是一種新的字串型別。

  通常來講String的操作比較簡單,但是由於String的不可更改特性,為了方便字串的修改,提供 StringBuffer 和 StringBuilder 類。

  StringBuffer 和 StringBuilder 在功能上大部分是相同的,在這裡我們著重介紹 StringBuffer. 

(1)append 方法

  在String中使用"+"來進行字串連線,但是這個操作在StringBuffer類中需要更改為append()方法。


  String和StringBuffer最大的區別在於:String的內容無法修改,而StringBuffer的內容可以修改。頻繁修改字串的情況考慮使用 StingBuffer。

 public static void main(String[] args) {
        StringBuffer sb = new StringBuffer();
        sb.append("a");
        sb.append("b");
        sb.append("c");
        System.out.println(sb);
    }
 


最後返回的是 this,在字串本身拼接字串。同時StringBuffer 有自己重寫的 toString 方法,可以直接進行列印。

//我們將此程式碼進行反編譯
public static void main(String[] args) { String str1 = "abc"; String str2 = "def"; String str3 = str1+str2; System.out.println(str3); }

在編譯的過程中,我們發現StringBuilder.append 方法的出現;說明:String 的“+” 拼接,會被底層優化為一個 StringBuilder ,拼接的時候會用到 append 方法,也就是說String得“+”拼接就是StringBuilder。

區別

String 和 StringBuilder 及 StringBuffer 的區別

String 進行拼接時,底層會被優化為StringBuilder

String的拼接會產生臨時物件,但是後兩者每次都只是返回當前物件的引用。

String的內容不可修改,StringBuffer與StringBuilder的內容可以修改.

StringBuffer採用同步處理,屬於執行緒安全操作;而StringBuilder未採用同步處理,屬於執行緒不安全操作 synchronized

StringBuilder得原始碼:

    @Override
    public StringBuilder append(String str) {
        super.append(str);
        return this;
    }

StringBuffer得原始碼:

    @Override
    public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }

8種基本型別的包裝類和常量池

Java 基本型別的包裝類的大部分都實現了常量池技術,即Byte,Short,Integer,Long,Character,Boolean;這5種包裝類預設建立了數值[-128,127]的相應型別的快取資料,但是超出此範圍仍然會去建立新的物件。•兩種浮點數型別的包裝類 Float,Double 並沒有實現常量池技術。

/**
*此方法將始終快取-128到127(包括端點)範圍內的值,並可以快取此範圍之外的其他值。
*/
    public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }
 public static Double valueOf(double d) {
        return new Double(d);
    }
        Integer i1 = 33;
        Integer i2 = 33;
        System.out.println(i1 == i2);// 輸出true
        Integer i11 = 333;
        Integer i22 = 333;
        System.out.println(i11 == i22);// 輸出false  因為333 不在-128到127之間,所以他不再常量池當中, 每次都是進行得new 在堆當中建立物件得操作,==相當於比較他們得引用地址,所以為false
        Double i3 = 1.2;
        Double i4 = 1.2;
        System.out.println(i3 == i4);// 輸出false     double沒有常量池 所以都是在記憶體中開闢一塊空間。所以他們得應用地址不同
  Integer i1 = 40;
  Integer i2 = 40;
  Integer i3 = 0;
  Integer i4 = new Integer(40);
  Integer i5 = new Integer(40);
  Integer i6 = new Integer(0);

  System.out.println("i1=i2   " + (i1 == i2));      //true
  System.out.println("i1=i2+i3   " + (i1 == i2 + i3));    //true
  System.out.println("i1=i4   " + (i1 == i4));   //false
  System.out.println("i4=i5   " + (i4 == i5));   //fasle
  System.out.println("i4=i5+i6   " + (i4 == i5 + i6));    //true     語句i4 == i5 + i6,因為+這個操作符不適用於Integer物件,首先i5和i6進行自動拆箱操作,進行數值相加,即i4 == 40。然後Integer物件無法與數值進行直接比較,所以i4自動拆箱轉為int值40,最終這條語句轉為40 == 40進行數值比較。
  System.out.println("40=i5+i6   " + (40 == i5 + i6));      //true