1. 程式人生 > >Java基礎3——深入理解String類

Java基礎3——深入理解String類

深入理解String類

String類的不變性

   java.lang.String類使用了final修飾,不能被繼承。Java程式中的所有字面值,即雙引號括起的字串,如"abc",都是作為String類的例項實現的。String是常量,其物件一旦構造就不能再被改變。換句話說,String物件是不可變的,每一個看起來會修改String值的方法,實際上都是創造了一個全新的String物件,以包含修改後的字串內容。而最初的String物件則絲毫未動。String物件具有隻讀特性,指向它的任何引用都不可能改變它的值,因此,也不會對其他的引用有什麼影響。但是字串引用可以重新賦值。java字串在記憶體中採用unicode編碼方式,任何一個字元對應兩個位元組的定長編碼,即任何一個字元(無論中文還是英文)都算一個字元長度,佔用兩個位元組


在這裡插入圖片描述

public class Immutable {
      public static String upcase(String s) {
           return s.toUpperCase();
      }
      public static void main(String[ ] args) {
           String str1= "Hello World";
           System.out.println(str1);  //Hello World
           String str2 = upcase(str1);
           System.
out.println(str2); //HELLO WORLD System.out.println(str1); //Hello World } }

  當把str1傳遞給upcase()方法時,實際傳遞的是引用的一個拷貝。其實,每當把String物件作為方法的引數時,都會複製一份引用,而該引用所指的物件其實一直待在單一的物理位置上,從未動過。

String常量池(上一章仔細講了)

   常量池(constant pool)指的是在編譯期被確定,並被儲存在已編譯的.class檔案中的一些資料。它包括了關於類、方法、介面等中的常量,也包括字串常量。Java為了提高效能,靜態字串(字面量/常量/常量連線的結果)在常量池中建立,並儘量使用同一個物件,重用靜態字串。對於重複出現的字串直接量,JVM會首先在常量池中查詢,如果常量池中存在即返回該物件。

public class test1 {
    public static void main(String[] args){
        String str1 = "Hello";
        //不會建立新的String物件,而是使用常量池中已有的"Hello",
        String str2 = "Hello";
        System.out.println(str1 == str2); //true
        //使用new關鍵字會建立新的String物件
        String str3 = new String("Hello");
        System.out.println(str1 == str3); //false 
    }
}

String、StringBuffer和StringBuilder的區別

1.物件的可變與不可變(不可變的底層原因)

   翻開JDK原始碼,java.lang.String類起手前三行,是這樣寫的:

public final class String implements java.io.Serializable, Comparable<String>, CharSequence {   
  /** String本質是個char陣列. 而且用final關鍵字修飾.*/     
private final char value[];  ...  ...
 } 
--------------------- 
作者:How 2 Play Life 
來源:CSDN 
原文:https://blog.csdn.net/a724888/article/details/80048782 
版權宣告:本文為博主原創文章,轉載請附上博文連結!

  首先String類是用final關鍵字修飾,這說明String不可繼承。再看下面,String類的主力成員欄位value是個char[]陣列,而且是用final修飾的。final修飾的欄位建立以後就不可改變。 有的人以為故事就這樣完了,其實沒有。因為雖然value是不可變,也只是value這個引用地址不可變。擋不住Array陣列是可變的事實

Array的資料結構看下圖:
在這裡插入圖片描述
  也就是說Array變數只是stack上的一個引用,陣列的本體結構在heap堆。String類裡的value用final修飾,只是說stack裡的這個叫value的引用地址不可變。沒有說堆裡array本身資料不可變

final int[] value={1,2,3}int[] another={4,5,6};
 value=another;    //編譯器報錯,final不可變 value用final修飾,編譯器不允許我把value指向堆區另一個地址。
但如果我直接對陣列元素動手,分分鐘搞定。

 final int[] value={1,2,3};
 value[2]=100;  //這時候數組裡已經是{1,2,100}   所以String是不可變,關鍵是因為SUN公司的工程師。在後面所有String的方法裡很小心的沒有去動Array裡的元素,沒有暴露內部成員欄位。

private final char value[]這一句裡,private的私有訪問許可權的作用都比final大。而且設計師還很小心地把整個String設成final禁止繼承,避免被其他人繼承後破壞。所以String是不可變的關鍵都在底層的實現,而不是一個final。考驗的是工程師構造資料型別,封裝資料的功力。
--------------------- 
作者:How 2 Play Life 
來源:CSDN 
原文:https://blog.csdn.net/a724888/article/details/80048782 
版權宣告:本文為博主原創文章,轉載請附上博文連結!

重點理解:
  private的私有訪問許可權的作用都比final大。而且設計師還很小心地把整個String設成final禁止繼承,避免被其他人繼承後破壞。所以String是不可變的關鍵都在底層的實現,而不是一個final。
String的不可變也是基於程式碼封裝和訪問控制的

  StringBuilder與StringBuffer都繼承自AbstractStringBuilder類,在AbstractStringBuilder中也是使用字元陣列儲存資料,這兩種物件都是可變的。如下:char[ ] value;

下面是jdk1.8中AbstractStringBuilder的部分原始碼
abstract class AbstractStringBuilder implements Appendable, CharSequence {
    /**
     * The value is used for character storage.
     */
    char[] value;

    /**
     * The count is the number of characters used.
     */
    int count;
jdk1.8 StringBuilder部分原始碼
public final class StringBuilder
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence
{

    /** use serialVersionUID for interoperability */
    static final long serialVersionUID = 4383685877147921099L;

    /**
     * Constructs a string builder with no characters in it and an
     * initial capacity of 16 characters.
     */
    public StringBuilder() {
        super(16);
    }
jdk1.8 StringBuffer部分原始碼
public final class StringBuffer
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence
{

    /**
     * A cache of the last value returned by toString. Cleared
     * whenever the StringBuffer is modified.
     */
    private transient char[] toStringCache;

    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    static final long serialVersionUID = 3388685877147921107L;

    /**
     * Constructs a string buffer with no characters in it and an
     * initial capacity of 16 characters.
     */
    public StringBuffer() {
        super(16);
    }

作者:lucus_guo
連結:https://www.jianshu.com/p/f87fd4dbdf23
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。在這裡插入程式碼片

2.執行緒是否安全

  String中的物件是不可變的,也就可以理解為常量,所以是執行緒安全
  AbstractStringBuilder是StringBuilder與StringBuffer的公共父類,定義了一些字串的基本操作,如expandCapacity、append、insert、indexOf等公共方法。
  StringBuffer對方法加了同步鎖或者對呼叫的方法加了同步鎖,所以是執行緒安全的
  StringBuilder並沒有對方法進行加同步鎖,所以是非執行緒安全的
  如果程式不是多執行緒的,那麼使用StringBuilder效率高於StringBuffer。

建立了幾個物件的問題

1.String str1 = "abc";
2.String str2 = new String("abc");

  對於1中的 String str1 = “abc”,首先會檢查字串常量池中是否含有字串abc,如果有則直接指向,如果沒有則在字串常量池中新增abc字串並指向它.所以這種方法最多建立一個物件,有可能不建立物件
   對於2中的String str2 = new String(“abc”),首先會在堆記憶體中申請一塊記憶體儲存字串abc,str2指向其記憶體塊物件。同時還會檢查字串常量池中是否含有abc字串,若沒有則新增abc到字串常量池中。所以 new String()可能會建立兩個物件

1     String temp="apple";  
2     for(int i=0;i<1000;i++) {  
3           temp=temp+i;  
4     }  
答案: 1001個物件
1     String temp=new String("apple")  
2     for(int i=0;i<1000;i++) {  
3            temp=temp+i;  
4     }  
答案:1002個物件