ZT文萃

          本博不原創(chuàng),轉(zhuǎn)帖自己感興趣那些事人物,什么入眼貼什么,隨心所欲。
          posts - 93, comments - 5, trackbacks - 0, articles - 0
            BlogJava :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理

          談?wù)刪ashCode

          Posted on 2014-05-04 06:23 ZT文萃 閱讀(280) 評論(0)  編輯  收藏 所屬分類: 中間件
          下文轉(zhuǎn)帖自:
          http://www.cnblogs.com/chenssy/p/3651218.html
          版權(quán)歸作者所有。

          hashCode的作用

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

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

          public native int hashCode();

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

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

          hashCode對于一個對象的重要性

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

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

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

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

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

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

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

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

          hashCode與equals

                在Java中hashCode的實(shí)現(xiàn)總是伴隨著equals,他們是緊密配合的,你要是自己設(shè)計(jì)了其中一個,就要設(shè)計(jì)另外一個。當(dāng)然在多數(shù)情況下,這兩個 方法是不用我們考慮的,直接使用默認(rèn)方法就可以幫助我們解決很多問題。但是在有些情況,我們必須要自己動手來實(shí)現(xiàn)它,才能確保程序更好的運(yù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),永遠(yuǎn)返回是“false”;x.equals(和x不同類型的對象)永遠(yuǎn)返回是“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)系我們就知道他們兩者是如何配合起來工作的。先看下圖:

          2014040701_thumb2

                整個處理流程是:

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

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

                實(shí)例:

          復(fù)制代碼
          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;     } }
          復(fù)制代碼

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

          復(fù)制代碼
          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, "李四");          //只驗(yàn)證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());     } }
          復(fù)制代碼

                 運(yùn)行結(jié)果如下:

          2014040702_thumb

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

          主站蜘蛛池模板: 中西区| 太康县| 托克逊县| 合江县| 平江县| 开化县| 收藏| 盐源县| 高阳县| 宁波市| 射洪县| 龙里县| 铜川市| 寻甸| 和静县| 日喀则市| 疏勒县| 凤山县| 绵竹市| 新田县| 扬中市| 阿克| 彭州市| 于都县| 清原| 开江县| 镇平县| 清水河县| 威宁| 定陶县| 阳泉市| 平果县| 军事| 垫江县| 景泰县| 武穴市| 吴忠市| 阿拉善右旗| 平凉市| 宜兰市| 天镇县|