Java中Sring類源碼分析學習


(一)關於如何查看Java中String類的源碼?

String類源碼在JDK文件中的src壓縮包下,src-java-lang-String.java。


(二)String類源碼分析

2.1 定義

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

可以看到String類是被final修飾的,這就意味着String是不可以被繼承,這樣String類中的方法是沒有機會被覆

蓋的,簡而言之,String類是不可變類。與此同時,該類實現了三個接口Serializable, Comparable,CharSequence。

2.2 屬性

/** 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];
 /** The value is used for character storage. */
    private final char value[];

這是一個字符數組,並且是final類型,他用於存儲字符串內容,從fianl這個關鍵字中我們可以看出,String的內容

一旦被初始化了是不能被更改的。 雖然有這樣的例子: String s = “a”; s = “b” 但是,這並不是對s的修改,而是重新指

向了新的字符串,從這裏我們也能知道,String其實就是用char[]實現的。

2.3 爲什麼字符串要被設計成不可變類

a、如果字符串可變的話,當兩個引用指向指向同一個字符串時,對其中一個做修改就會影響另外一個。

b、在HashMap中,字符串的不可變能保證其hashcode永遠保持一致,這樣就可以避免一些不必要的麻煩。這也就意味着

每次在使用一個字符串的hashcode的時候不用重新計算一次,這樣更加高效。

private int hash;//this is used to cache hash code.
以上代碼中hash變量中就保存了一個String對象的hashcode,因爲String類不可變,所以一旦對象被創建,該hash值也

無法改變。所以,每次想要使用該對象的hashcode的時候,直接返回即可。
c、安全性方面:
String被廣泛的使用在其他Java類中充當參數。比如網絡連接、打開文件等操作。如果字符串可變,那麼

類似操作可能導致安全問題。因爲某個方法在調用連接操作的時候,他認爲會連接到某臺機器,但是實際上並沒有(其他

引用同一String對象的值修改會導致該連接中的字符串內容被修改)。可變的字符串也可能導致反射的安全問題,因爲他的

參數也是字符串。

2.4 Java中的equals()和hashcode()的關係

所有Java類的父類——java.lang.Object中定義了兩個重要的方法:

public boolean equals(Object obj)
public int hashCode()
下面是一段摘取自網絡的代碼:

import java.util.HashMap;

public class Apple {
    private String color;

    public Apple(String color) {
        this.color = color;
    }

    public boolean equals(Object obj) {
        if(obj==null) return false;
        if (!(obj instanceof Apple))
            return false;   
        if (obj == this)
            return true;
        return this.color.equals(((Apple) obj).color);
    }

    public static void main(String[] args) {
        Apple a1 = new Apple("green");
        Apple a2 = new Apple("red");

        //hashMap stores apple type and its quantity
        HashMap<Apple, Integer> m = new HashMap<Apple, Integer>();
        m.put(a1, 10);
        m.put(a2, 20);
        System.out.println(m.get(new Apple("green")));
    }
}
上面的代碼執行過程中,先是創建個兩個Apple,一個green apple和一個red apple,然後將這來兩個apple存儲在map中,存儲之後再試圖通過map的

get方法獲取到其中green apple的實例。讀者可以試着執行以上代碼,數據結果爲null。也就是說剛剛通過put方法放到map中的

green apple並沒有通過get方法獲取到。你可能懷疑是不是green apple並沒有被成功的保存到map中,但是,通過debug工具可以

看到,它已經被保存成功了。

造成以上問題的原因其實比較簡單,是因爲代碼中並沒有重寫hashcode方法。hashcodeequals的約定關係如下:

1、如果兩個對象相等,那麼他們一定有相同的哈希值(hash code)。

2、如果兩個對象的哈希值相等,那麼這兩個對象有可能相等也有可能不相等。(需要再通過equals來判斷)

如果你瞭解Map的工作原理,那麼你一定知道,它是通過把key值進行hash來定位對象的,這樣可以提供比線性存儲更好的性能。實際上,Map的底層

數據結構就是一個數組的數組(準確的說其實是一個鏈表+數組)。第一個數組的索引值是key的哈希碼。通過這個索引可以定位到第二個數組,第二個

數組通過使用equals方法進行線性搜索的方式來查找對象。


其實,一個哈希碼可以映射到一個桶(bucket)中,hashcode的作用就是先確定對象是屬於哪個桶的。如果多個對象有相同的哈希值,那麼他們可以放

在同一個桶中。如果有不同的哈希值,則需要放在不同的桶中。至於同一個桶中的各個對象之前如何區分就需要使用equals方法了。

hashcode方法的默認實現會爲每個對象返回一個不同的int類型的值。所以,上面的代碼中,第二個apple被創建出來時他將具有不同的哈希值。可以通

過重寫hashCode方法來解決。

public int hashCode(){
    return this.color.hashCode();   
}

所以,在判斷兩個對象是否相等時,不要只使用equals方法判斷。還要考慮其哈希碼是否相等。尤其是和hashMap等與hash相關的數據結構一起使用時。


(未完待續。。。。。。)

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