在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ Java/ hashCode
Java 集合細(xì)節(jié)(四):保持 compareTo 和 equals 同步
Iterator
使用序列化實現(xiàn)對象的拷貝
fail-fast 機制
關(guān)鍵字 final
Vector
HashTable
Java 集合細(xì)節(jié)(一):請為集合指定初始容量
強制類型轉(zhuǎn)換
數(shù)組之一:認(rèn)識 JAVA 數(shù)組
Java 集合細(xì)節(jié)(三):subList 的缺陷
hashCode
ArrayList
數(shù)組之二
List 總結(jié)
LinkedList
Java 提高篇(九)—–實現(xiàn)多重繼承
Java 的四舍五入
關(guān)鍵字 static
理解 Java 的三大特性之多態(tài)
抽象類與接口
集合大家族
異常(二)
Java 集合細(xì)節(jié)(二):asList 的缺陷
Map 總結(jié)
TreeSet
equals() 方法總結(jié)
Java 提高篇(十)—–詳解匿名內(nèi)部類
HashMap
Stack
詳解內(nèi)部類
TreeMap
異常(一)
詳解 Java 定時任務(wù)
HashSet
字符串
理解 Java 的三大特性之繼承
理解 Java 的三大特性之封裝
代碼塊

hashCode

在前面三篇博文中 LZ 講解了(HashMap、HashSet、HashTable),在其中 LZ 不斷地講解他們的 put 和 get 方法,在這兩個方法中計算 key 的 hashCode 應(yīng)該是最重要也是最精華的部分,所以下面 LZ 揭開 hashCode 的“神秘”面紗。

hashCode 的作用

要想了解一個方法的內(nèi)在原理,我們首先需要明白它是干什么的,也就是這個方法的作用。在講解數(shù)組時(java 提高篇(十八)——數(shù)組),我們提到數(shù)組是java中效率最高的數(shù)據(jù)結(jié)構(gòu),但是“最高”是有前提的。第一我們需要知道所查詢數(shù)據(jù)的所在位置。第二:如果我們進行迭代查找時,數(shù)據(jù)量一定要小,對于大數(shù)據(jù)量而言一般推薦集合。

在 Java 集合中有兩類,一類是 List,一類是 Set 他們之間的區(qū)別就在于 List 集合中的元素師有序的,且可以重復(fù),而 Set 集合中元素是無序不可重復(fù)的。對于 List 好處理,但是對于 Set 而言我們要如何來保證元素不重復(fù)呢?通過迭代來 equals() 是否相等。數(shù)據(jù)量小還可以接受,當(dāng)我們的數(shù)據(jù)量大的時候效率可想而知(當(dāng)然我們可以利用算法進行優(yōu)化)。比如我們向 HashSet 插入 1000 數(shù)據(jù),難道我們真的要迭代 1000 次,調(diào)用 1000 次 equals() 方法嗎?hashCode 提供了解決方案。怎么實現(xiàn)?我們先看 hashCode 的源碼(Object)。


    public native int hashCode();

它是一個本地方法,它的實現(xiàn)與本地機器有關(guān),這里我們暫且認(rèn)為他返回的是對象存儲的物理位置(實際上不是,這里寫是便于理解)。當(dāng)我們向一個集合中添加某個元素,集合會首先調(diào)用 hashCode 方法,這樣就可以直接定位它所存儲的位置,若該處沒有其他元素,則直接保存。若該處已經(jīng)有元素存在,就調(diào)用 equals 方法來匹配這兩個元素是否相同,相同則不存,不同則散列到其他位置(具體情況請參考(Java 提高篇()—–HashMap))。這樣處理,當(dāng)我們存入大量元素時就可以大大減少調(diào)用 equals() 方法的次數(shù),極大地提高了效率。

所以 hashCode 在上面扮演的角色為尋域(尋找某個對象在集合中區(qū)域位置)。hashCode 可以將集合分成若干個區(qū)域,每個對象都可以計算出他們的 hash 碼,可以將 hash 碼分組,每個分組對應(yīng)著某個存儲區(qū)域,根據(jù)一個對象的 hash 碼就可以確定該對象所存儲區(qū)域,這樣就大大減少查詢匹配元素的數(shù)量,提高了查詢效率。

hashCode 對于一個對象的重要性

hashCode 重要么?不重要,對于 List 集合、數(shù)組而言,他就是一個累贅,但是對于 HashMap、HashSet、HashTable 而言,它變得異常重要。所以在使用 HashMap、HashSet、HashTable 時一定要注意 hashCode。對于一個對象而言,其 hashCode 過程就是一個簡單的 Hash 算法的實現(xiàn),其實現(xiàn)過程對你實現(xiàn)對象的存取過程起到非常重要的作用。

在前面 LZ 提到了 HashMap 和 HashTable 兩種數(shù)據(jù)結(jié)構(gòu),雖然他們存在若干個區(qū)別,但是他們的實現(xiàn)原理是相同的,這里我以 HashTable 為例闡述 hashCode 對于一個對象的重要性。

一個對象勢必會存在若干個屬性,如何選擇屬性來進行散列考驗著一個人的設(shè)計能力。如果我們將所有屬性進行散列,這必定會是一個糟糕的設(shè)計,因為對象的 hashCode 方法無時無刻不是在被調(diào)用,如果太多的屬性參與散列,那么需要的操作數(shù)時間將會大大增加,這將嚴(yán)重影響程序的性能。但是如果較少屬相參與散列,散列的多樣性會削弱,會產(chǎn)生大量的散列“沖突”,除了不能夠很好的利用空間外,在某種程度也會影響對象的查詢效率。其實這兩者是一個矛盾體,散列的多樣性會帶來性能的降低。

那么如何對對象的 hashCode 進行設(shè)計,LZ 也沒有經(jīng)驗。從網(wǎng)上查到了這樣一種解決方案:設(shè)置一個緩存標(biāo)識來緩存當(dāng)前的散列碼,只有當(dāng)參與散列的對象改變時才會重新計算,否則調(diào)用緩存的 hashCode,這樣就可以從很大程度上提高性能。

在 HashTable 計算某個對象在 table[] 數(shù)組中的索引位置,其代碼如下:


    int index = (hash & 0x7FFFFFFF) % tab.length;

為什么要 &0x7FFFFFFF?因為某些對象的 hashCode 可能會為負(fù)值,與 0x7FFFFFFF 進行與運算可以確保 index 為一個正數(shù)。通過這步我可以直接定位某個對象的位置,所以從理論上來說我們是完全可以利用 hashCode 直接定位對象的散列表中的位置,但是為什么會存在一個 key-value 的鍵值對,利用 key 的 hashCode 來存入數(shù)據(jù)而不是直接存放 value 呢?這就關(guān)系 HashTable 性能問題的最重要的問題:Hash 沖突!

我們知道沖突的產(chǎn)生是由于不同的對象產(chǎn)生了相同的散列碼,假如我們設(shè)計對象的散列碼可以確保 99.999999999% 的不重復(fù),但是有一種絕對且?guī)缀醪豢赡苡龅降臎_突你是絕對避免不了的。我們知道 hashcode 返回的是 int,它的值只可能在 int 范圍內(nèi)。如果我們存放的數(shù)據(jù)超過了 int 的范圍呢?這樣就必定會產(chǎn)生兩個相同的 index,這時在 index 位置處會存儲兩個對象,我們就可以利用 key 本身來進行判斷。所以具有相索引的對象,在該 index 位置處存在多個對象,我們必須依靠 key 的 hashCode和key 本身來進行區(qū)分。

hashCode 與 equals

在 Java 中 hashCode 的實現(xiàn)總是伴隨著 equals,他們是緊密配合的,你要是自己設(shè)計了其中一個,就要設(shè)計另外一個。當(dāng)然在多數(shù)情況下,這兩個方法是不用我們考慮的,直接使用默認(rèn)方法就可以幫助我們解決很多問題。但是在有些情況,我們必須要自己動手來實現(xiàn)它,才能確保程序更好的運作。

對于 equals,我們必須遵循如下規(guī)則:

對稱性:如果 x.equals(y) 返回是“true”,那么 y.equals(x) 也應(yīng)該返回是“true”。

反射性:x.equals(x) 必須返回是“true”。

類推性:如果 x.equals(y) 返回是“true”,而且 y.equals(z) 返回是“true”,那么 z.equals(x) 也應(yīng)該返回是“true”。

一致性:如果 x.equals(y) 返回是“true”,只要x和y內(nèi)容一直不變,不管你重復(fù) x.equals(y) 多少次,返回都是“true”。

任何情況下,x.equals(null),永遠返回是“false”;x.equals(和x不同類型的對象)永遠返回是“false”。

對于 hashCode,我們應(yīng)該遵循如下規(guī)則:

  1. 在一個應(yīng)用程序執(zhí)行期間,如果一個對象的 equals 方法做比較所用到的信息沒有被修改的話,則對該對象調(diào)用 hashCode 方法多次,它必須始終如一地返回同一個整數(shù)。

  2. 如果兩個對象根據(jù) equals(Object o) 方法是相等的,則調(diào)用這兩個對象中任一對象的 hashCode 方法必須產(chǎn)生相同的整數(shù)結(jié)果。

  3. 如果兩個對象根據(jù) equals(Object o) 方法是不相等的,則調(diào)用這兩個對象中任一個對象的 hashCode 方法,不要求產(chǎn)生不同的整數(shù)結(jié)果。但如果能不同,則可能提高散列表的性能。

至于兩者之間的關(guān)聯(lián)關(guān)系,我們只需要記住如下即可:

如果 x.equals(y) 返回“true”,那么 x 和 y 的 hashCode() 必須相等。

如果 x.equals(y) 返回“false”,那么 x 和 y 的 hashCode() 有可能相等,也有可能不等。

理清了上面的關(guān)系我們就知道他們兩者是如何配合起來工作的。先看下圖:

http://wiki.jikexueyuan.com/project/java-enhancement/images/26-1.png" alt="fig.1" />

整個處理流程是:

1、判斷兩個對象的 hashcode 是否相等,若不等,則認(rèn)為兩個對象不等,完畢,若相等,則比較 equals。

2、若兩個對象的 equals 不等,則可以認(rèn)為兩個對象不等,否則認(rèn)為他們相等。

實例:

    public class Person {
        private int age;
        private int sex;    //0:男,1:女
        private String name;

        private final int PRIME = 37;

        Person(int age ,int sex ,String name){
            this.age = age;
            this.sex = sex;
            this.name = name;
        }

        /** 省略getter、setter方法 **/

        @Override
        public int hashCode() {
            System.out.println("調(diào)用hashCode方法...........");

            int hashResult = 1;
            hashResult = (hashResult + Integer.valueOf(age).hashCode() + Integer.valueOf(sex).hashCode()) * PRIME;
            hashResult = PRIME * hashResult + ((name == null) ? 0 : name.hashCode()); 
            System.out.println("name:"+name +" hashCode:" + hashResult);

            return hashResult;
        }

        /**
         * 重寫hashCode()
         */
        public boolean equals(Object obj) {
            System.out.println("調(diào)用equals方法...........");

            if(obj == null){
                return false;
            }
            if(obj.getClass() != this.getClass()){
                return false;
            }
            if(this == obj){
                return true;
            }

            Person person = (Person) obj;

            if(getAge() != person.getAge() || getSex()!=   person.getSex()){
                return false;
            }

            if(getName() != null){
                if(!getName().equals(person.getName())){
                    return false;
                }
            }
            else if(person != null){
                return false;
            }
            return true;
        }
    }

該 Bean 為一個標(biāo)準(zhǔn)的 Java Bean,重新實現(xiàn)了 hashCode 方法和 equals 方法。


    public class Main extends JPanel {

        public static void main(String[] args) {
            Set<Person> set = new HashSet<Person>();

            Person p1 = new Person(11, 1, "張三");
            Person p2 = new Person(12, 1, "李四");
            Person p3 = new Person(11, 1, "張三");
            Person p4 = new Person(11, 1, "李四");

            //只驗證p1、p3
            System.out.println("p1 == p3? :" + (p1 == p3));
            System.out.println("p1.equals(p3)?:"+p1.equals(p3));
            System.out.println("-----------------------分割線--------------------------");
            set.add(p1);
            set.add(p2);
            set.add(p3);
            set.add(p4);
            System.out.println("set.size()="+set.size());
        }
    }

運行結(jié)果如下:

http://wiki.jikexueyuan.com/project/java-enhancement/images/26-2.png" alt="fig.2" />

從上圖可以看出,程序調(diào)用四次 hashCode 方法,一次 equals 方法,其 set 的長度只有 3。add 方法運行流程完全符合他們兩者之間的處理流程。

更多請關(guān)注:

>>>>>>>>>Java提高篇(十三)——equals()

>>>>>>>>>Java提高篇(二三)——HashMap

>>>>>>>>>Java提高篇(二四)——HashSet

>>>>>>>>>Java提高篇(二五)——HashTable

上一篇:Vector下一篇:異常(二)