Java源碼:String類

1.分類

  1. 基本屬性:String最終的屬性就一個char[] value;
  2. 構造方法區域:比較複雜的就是通過unicode碼和byte[]構造;
  3. 字符串比較:是否相等、大小(排序);
  4. 查詢:indexOf、startsWith、endWith、contains
  5. 截取:subString、
  6. 工具方法:格式化打印、unicode 碼點相關的位操作方法;

2. 基礎

面向對象的封裝思想爲什麼屏蔽了很多底層細節,String類作爲我們使用頻率最高的類,幫我們實現了很多功能,

2.1 不可變性

JDK中有很多不可變的類,所謂的不可變類不是表現在字符串聲明爲final,而是內部的value是不可以被修改的,所有對value的操作都是返回一個新的對象,比如BigDecimal,下面是一些錯誤的方法

String str = "  hello,你好 ";
str.trim();
str.replace(',',',');//這行是編譯不過去的,中文的char是有問題的
System.out.println(str);

2.2 字符串常量池

JVM爲了提升8種基本類型(short/int/long/double/float/char/boolean/byte)和String的效率,建立了常量池的概念。這些變量在編譯爲class文件後,放入到class文件的常量池部分。JVM運行時會建立字符串常量池,預編譯的字符串會加入常量池,通過intern也可以將字符串加入常量池;

public class StringPoolTest {

	public static final String str1 = "a"; // a進入常量池,str1使用常量池引用

	public final String str2 = "b"; // b進入常量池,str2使用常量池引用

	public String str3 = "c"; // c進入常量池,str3 使用常量池引用

	public final String str4 = str2 + str3; // bc不進入常量池, 實際上是 str4 = new StringBuilder(str2).append(str3).toString();

	public static void main(String[] args) {
		String str5 = "d"; // d進入常量池
		final String str7 = "f"; // f 進入常量池
		final String str8 = str5 + "j"; //dj不進入常量池 實際上是str8 = new StringBuilder(str5).append(j).toString()
	}

}

使用 javap -v做反編譯查看字節碼的詳細信息如下

 public class com.saillen.demo.lang.StringPoolTest
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:   
   ……
   #4 = String             #44            // c
   //這裏很明確引入了StringBuilder,但是我們的代碼中並沒有import
   #6 = Class              #46            // java/lang/StringBuilder
   ……
   //表示d的引用值在#51定義
  #12 = String             #51            // d
  #13 = String             #52            // e
  #14 = String             #53            // f
  #15 = String             #54            // j
  ……
  //
  #17 = Utf8               str1
  #20 = String             #56            // a
  #21 = Utf8               str2
  #22 = Utf8               str3
  #23 = Utf8               str4
  #35 = Utf8               str5
  #36 = Utf8               str6
  #37 = Utf8               str7
  #38 = Utf8               str8
  ……
  #42 = Utf8               b
  #44 = Utf8               c
  #51 = Utf8               d
  #52 = Utf8               e
  #53 = Utf8               f
  #54 = Utf8               j
  #56 = Utf8               a

{
  
  //類的初始化指令
  public com.saillen.demo.lang.StringPoolTest();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=1, args_size=1
         //加載b,str1的代碼被優化掉了,因爲沒有被使用,還是很智能的
         //這裏JDK8優化了無用的code,但是JDK7、6不一定
         //優化掉了但是常量池中存在,因爲在其他類中可以使用str1,
         4: aload_0
         5: ldc           #2                  // String b
         7: putfield      #3                  // Field str2:Ljava/lang/String;
        //加載c
        10: aload_0
        11: ldc           #4                  // String c
        13: putfield      #5                  // Field str3:Ljava/lang/String;
        
        //str4 實際是,
        //先調用new StringBuilder()
        //然後調用append(str2).append(str3)
        //然後調用toString()
        //然後賦值給str4
        16: aload_0
        17: new           #6                  // class java/lang/StringBuilder
        20: dup
        21: invokespecial #7                  // Method java/lang/StringBuilder."<init>":()V
        //加載b做append(str2)
        24: ldc           #2                  // String b
        26: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        //加載c做append(str3)
        29: aload_0
        30: getfield      #5                  // Field str3:Ljava/lang/String;
        33: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        //toString給str4
        36: invokevirtual #10                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        39: putfield      #11                 // Field str4:Ljava/lang/String;

	……
	//其他str5、str6、str7、str8同理

}
SourceFile: "StringPoolTest.java"

可以參考:http://blog.csdn.net/sugar_rainbow/article/details/68150249

NOTE:涉及到這種‘基本的’,‘JVM’層次的要注意JDK版本,還有有些結論不一定“正確”;

Unicode 和 UTF-16

  • Java使用unicode字符集表示內存中的每個字符(不僅僅是字母和漢字);
  • unicode標準中爲每個字符確定了唯一的 碼點(codePoint);
  • unicode標準將碼點以16進製表示即\uxxxx形式;
  • unicode標準將碼點分區域管理,稱爲平面(plane);
  • unicode將最前面的65536字符稱爲 基本平面(BMP);
  • unicode僅僅是一個標準,不管實際怎麼存儲的;
  • UTF-16僅僅是unicode的一種實現方案;

unicode的幾個問題:

  • 碼點如何表示:比如codePoint爲1,那麼1個字節就夠了,但是如果是10萬,那麼1個字節肯定不夠;
  • 如何存儲數據?最簡單 or 佔用空間最小?

可以參考:http://blog.csdn.net/gjb724332682/article/details/43229075

問題:爲什麼1byte = 8bit ? 爲什麼1 byte 不用 4bit或者 16bit?

2.3Java中UTF-16

  • java使用utf-16存儲和表示字符 – 在JVM中每個字符都是2個字節;
  • 2個字節能表示的codePoint 範圍:256 * 256 = 65536,能表示BMP的內容 O~0xFFFF範圍;
  • Charset類是char的包裝類也提供了很多Unicode的操作方法,保存了最大和最小字符數量;
  • 如果字符比如漢字codePoint的值很大怎麼辦 - 用2個char也就是4個字節來表示;
  • 在class中爲了減小文件大小,字符常量都是以UTF8編碼的,但是JVM內存中是UTF16的,即在load的時候是有編碼轉換的;

到底幾個字節?

  • char到底是幾個字節,很明確char是2個字節,那麼漢字爲什麼是2個字符,4個字節,如何定義?
  • char的實質存儲的是int值,如果codePoint在2個字節內能表示下,那麼就用一個char就夠了,在顯示打印的時候讀取一個char然後找打對應的字符去打印;
  • 如果codePoint需要4個字節,2個字符,那麼就讀取兩個連續的char來打印即可。
// 下面的代碼段不要用IDE編輯會報錯。
 char c = ''; //不要用IDE編輯。
 String s = ""; // char[]的長度爲2;

2,4 Java中char和String到底存儲的是什麼

  • String:存儲的是char[]數組;
  • char:存儲的是字符的unicode的codePoint的int值;
  • java中編碼轉換的實質就是:讀取目標編碼的codePoint的值轉換爲unicode的codePoint值然後按照UTF-16的格式存儲到內存或其他地方;

3 源碼

3.1 屬性

// 字符串是常量,字符串被創建後,字符串的值不可以被修改,
// final的不可修改
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {

    /** 存儲了原始字符 */
    private final char value[];

    /** 因爲字符串的不可變性,所以可以緩存一個hash值 */
    private int hash; // Default to 0

    private static final long serialVersionUID = -6849794470754667710L;

    //序列化使用
    private static final ObjectStreamField[] serialPersistentFields =
        new ObjectStreamField[0];

    // 內部方法用來比較字符串大小
    public static final Comparator<String> CASE_INSENSITIVE_ORDER
                                         = new CaseInsensitiveComparator();

3.2 常用方法

    //判斷長度就是char[]的長度
    public int length() {
        return value.length;
    }

    //是不是空串,空格不算空串;
    public boolean isEmpty() {
        return value.length == 0;
    }
    
    //第index個字符,要注意不在BMP內的字符取出的char可能無法轉換爲一個完整字符;
    public char charAt(int index) {
        if ((index < 0) || (index >= value.length)) {
            throw new StringIndexOutOfBoundsException(index);
        }
        return value[index];
    }

    
    //這裏copy了一個新的char,防止String的不可變性被破壞
    void getChars(char dst[], int dstBegin) {
        System.arraycopy(value, 0, dst, dstBegin, value.length);
    }

    //只copy部分,還是arraycopy
    //做數組copy的時候還是多使用arraycopy
    public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin){
        if (srcBegin < 0) {
            throw new StringIndexOutOfBoundsException(srcBegin);
        }
        if (srcEnd > value.length) {
            throw new StringIndexOutOfBoundsException(srcEnd);
        }
        if (srcBegin > srcEnd) {
            throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
        }
        System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
    }

3.3 相等性比較、hash

相等的比較思路最終都是:挨個循環比較char[],如果不一致則false

    //equals方法挨個比較char字符是否一樣
    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;
    }
    
     //可以看到是挨個字符計算一個最終的hash
    //如果計算了一遍了則不再計算
    //對於""的字符串,hashCode就是0
    public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;
			  //將unicode碼參與到計算中
            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

3.4 多種valueOf方法

valueOf(Object obj)的時候不會有null的問題,可以替代我們的的obj.toString();

  public static String valueOf(Object obj) {
        return (obj == null) ? "null" : obj.toString();
    }

    public static String valueOf(char data[]) {
        return new String(data);
    }

    public static String valueOf(char data[], int offset, int count) {
        return new String(data, offset, count);
    }

    public static String copyValueOf(char data[], int offset, int count) {
        return new String(data, offset, count);
    }

    public static String copyValueOf(char data[]) {
        return new String(data);
    }

    public static String valueOf(boolean b) {
        return b ? "true" : "false";
    }

    public static String valueOf(char c) {
        char data[] = {c};
        return new String(data, true);
    }

    public static String valueOf(int i) {
        return Integer.toString(i);
    }

    public static String valueOf(long l) {
        return Long.toString(l);
    }

    public static String valueOf(float f) {
        return Float.toString(f);
    }

    public static String valueOf(double d) {
        return Double.toString(d);
    }


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章