1. 程式人生 > >Java----談談String

Java----談談String

一.簡介

String 是 Java 中使用得最頻繁的一個類了,不管是作為開發者的業務使用,還是一些系統級別的字元使用, String 都發揮著重要的作用。又因 String 是不可變的、final的 且 Java 在執行時也儲存了一個字串池(String pool) ,就使得 String 變得很特殊。

二.==/equals

String 物件的建立有兩種方式:

    String strComplier = "A";
    String strNew = new String("A");

雖然同樣是建立字串 “A” ,但是這兩種方式在記憶體分配上是由區別的。

1.String strComplier = “A”;

Java 程式在執行的時候會維護者一個常量池,編譯期生成的各種字面量和符號引用會在類載入後進入方法區的執行時常量池。對於 上述這種實現字串的方式就可以在編譯的時候確定字串的內容,因此這一行生成的記憶體結構就如下圖。 image.png

不嚴謹的講:虛擬機器棧中的 strComplier 儲存的就是 A 在常量池中的地址。

2. String strNew = new String(“A”);

因為使用的 new 的方式,所以這句程式碼只有的執行的時候才能確定字串的內容。而對於 new 關鍵字,java 是將物件的例項資料存放堆上的,但是又因 String 常量池的存在,因此實際上在堆上的 String 物件的資料又指向了字串常量池。

image.png

不嚴謹的講:虛擬機器棧中的 strNew 儲存的就是 strNew 這個物件在堆記憶體的地址,而 strNew 中的字串資料又指向了常量池中的 A .

3.==/equals

有了前面的知識基礎之後,對於字串這兩個比較操作就十分簡單了。

(1).==

== 比較的兩個物件的引用是否相等,也就是說比較兩個地址是否相等,顯然對於下面的比較。

String a = "A";
String a1 = "A";
System.out.println(a1 == a); //指向的都是 A 的地址,地址相同,返回的是 true
  String a = new String("A");
  String a1 = new String("A");
  System.out.println(a==a1);//分別指向的是在堆記憶體上的物件的地址,地址不同,返回的是 false
   String a = "A";
   String a1 = new String("A");
   System.out.println(a == a1);//一個指向常量池,一個指向堆,返回 false 
   String a = "A";
   String a1 = a; //編譯的時候不能確定
   System.out.println(a == a1);//一個指向常量池,一個指向堆,返回 false 

通過這個例子可以看出實際上對於 == 的比較,只要在編譯的時候能夠確定的,都是相同 的。

        String a = "A1";
        String a1 = "A" +1;
        System.out.println(a == a1);//true

        String a = "A1";
        String a1 = a +1;
        System.out.println(a == a1);//false
        
        String a = "A1";
        final String a1 = a + 1;
        System.out.println(a == a1);// final 修飾的在 a1 在編譯的時候就能確定下來

        private static String get1() {
            return "1";
        }
        String a = "A";
        final String a1 = get1();
        String a2 = "a" + a1;
        System.out.println(a == a2); //使用了 方法,只能在執行的時候確定,所以返回false 
(2)equals
public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String aString = (String)anObject;
            if (coder() == aString.coder()) {
                return isLatin1() ? StringLatin1.equals(value, aString.value)
                                  : StringUTF16.equals(value, aString.value);
            }
        }
        return false;
    }

對於 equals 的比較:

  • 首先如果是兩個物件相同的物件就直接返回 true .
  • 接著先判斷時候屬於 String 類的物件,不是就直接返回 false ,這就說明 對於 StringBuilder(“A”) 和 String(“A”) 顯然 equals 返回的是 false ,但是對於前面說過的兩種方式,實際上都是 String 物件。
  • 然後,就要判斷編碼的方式如果編碼的方式不同就直接返回 false ,如果編碼方式相同就比較兩個字串是否完全相等。
  • 如果兩個字串是完全的一樣, 就是 true .
      String stringCompiler = "A";
      String stringNew = new String("A");
      StringBuilder stringBuilder = new StringBuilder("A");
      StringBuffer stringBuffer = new StringBuffer("A")
      System.out.println(stringCompiler.equals(stringNew));  // true
      System.out.println(stringCompiler.equals(stringBuilder));  // false
      System.out.println(stringNew.equals(stringBuffer));   // false
(3)引申
  String strNew = new String("A");

對於這行程式碼,前面說過會在堆儲存物件的例項資料,然後在常量池儲存了 “A” 這個常量。所以這一句實際涉及了兩個 String 物件。

三.final

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {}

總所周知,String 是 final ,也就是String 是不可變的。即一個 String 物件建立之後所有對它修改後的字串都是新生成的 String 物件。

String 設計成 final 主要有下的原因:

  • 實現字串常量池,只有當字串是不可變時字串池才有可能實現,字串池的實現可以在執行時節約很多 heap 空間,因為不同的字串變數都指向池中的同一個字串,即可以實現多個變數引用 JVM 記憶體中的同一個字串例項,如果字串不是不變的,String interning 將不能實現(String interning 是指對不同的字串僅僅只儲存一個,即不會儲存多個相同的字串),因為這樣的話,如果變數改變了它的值,那麼其它指向這個值的變數的值也會一起改變。
  • 安全問題,在系統中有很多地方都是以字串的形式存在的,比如資料庫的使用者名稱,Socket 的主機和埠,當你在呼叫其他方法,比如呼叫一些系統級操作之前,可能會有一系列校驗,如果是可變類的話,可能在你校驗過後,其內部的值被改變了,可能引起嚴重的系統崩潰問題。
  • 快取提升效能,String 大量運用在雜湊的處理中,由於 String 的不可變性,可以只計算一次雜湊值,然後快取在內部,後續直接取就好了,字串的處理速度要快過其它的鍵物件,這就是 HashMap 中的鍵往往都使用字串的原因。
  • 執行緒安全,因為字串是不可變的,所以是多執行緒安全的,同一個字串例項可以被多個執行緒共享。這樣便不用因為執行緒安全問題而使用同步。

使用 StringBuilder 和 StringBuffer 拼接字串

前面說過 String 是final 類的,因此對於字串的拼接實際上就是建立了新的物件。

        String str = "111";
        str += "222";
        str += "333";
        System.out.println(str);

編譯器每次碰到 ”+=” 的時候,會 new 一個 StringBuilder 出來,接著呼叫 append 方法,在呼叫 toString 方法,生成新字串。因此對於字串的拼接,應該直接使用 StringBuilder 或者 StringBuffer。雖然 這兩類也是 final 但是他們在內部 維護了父類一個可變的 byte 陣列,每次 append 的時候就往 byte 數組裡面放字元.

StringBuffer 和 StringBuilder用法一模一樣,唯一的區別只是 StringBuffer 是執行緒安全的,它對所有方法都做了同步,StringBuilder 是執行緒非安全的,所以在不涉及執行緒安全的場景,比如方法內部,儘量使用 StringBuilder,避免同步帶來的消耗.

  • StringBuffer
 @Override
    public synchronized StringBuffer append(Object obj) {
        toStringCache = null;
        super.append(String.valueOf(obj));
        return this;
    }

  • StringBuilder
 @Override
    public StringBuilder append(Object obj) {
        return append(String.valueOf(obj));
    }

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

四.null

String 是一個 引用型物件,因此也就存在著 null 值,又因 String 是一個經常使用的物件,如果為 null 的話很可能導致整個系統崩潰,因此 String 對 null 值有很高的容錯率

public static String valueOf(Object obj) {
        return (obj == null) ? "null" : obj.toString();
    }
  • String 物件:直接判斷是否為 null,如果為 null 給 null 物件賦值為”null”。
  • 非 String 物件:通過呼叫 String.valueOf 方法,如果是 null 物件,就返回 “null” ,否則呼叫物件的 toString 方法

對於字串的拼接也是如此

String s = null;
s = s + "!";
System.out.print(s);// 輸出 null!

前面說過對於 “+=” 都是轉為 StringBuffer 的 append ,下面就看 AbstractStringBuilder 是如何處理 null 的 (AbstractStringBuilder 是 StringBuffer 和 StringBuilder 的父類 )

 private AbstractStringBuilder appendNull() {
        ensureCapacityInternal(count + 4);
        int count = this.count;
        byte[] val = this.value;
        if (isLatin1()) {
            val[count++] = 'n';
            val[count++] = 'u';
            val[count++] = 'l';
            val[count++] = 'l';
        } else {
            count = StringUTF16.putCharsAt(val, count, 'n', 'u', 'l', 'l');
        }
        this.count = count;
        return this;
    }

可以看到也是直接新增 “null” 字串。

五.保密性

為什麼針對安全保密高的資訊,char[]比String更好?

因為 String 是不可變的,就是說它一旦建立,就不能更改了,直到垃圾收集器將它回收走。而字元陣列中的元素是可以更改的(這就意味著可以在使用完之後將其更改,而不會保留原始的資料)。所以使用字元陣列的話,安全保密性高的資訊(如密碼之類的)將不會存在於系統中被他人看到。