欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java8 HashMap鍵與Comparable接口小結(jié)

 更新時(shí)間:2022年01月29日 10:28:59   作者:mitsuhide1992  
這篇文章主要介紹了Java8 HashMap鍵與Comparable接口小結(jié),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教

Java8 HashMap鍵與Comparable接口

最容易使 HashMap 發(fā)生哈希沖突的方法是什么呢?我們可以創(chuàng)建一個(gè)類,讓它的哈希函數(shù)返回一個(gè)最糟糕的結(jié)果 —— 比如一個(gè)常數(shù)。

這也是我在面試的時(shí)候經(jīng)常問(wèn)面試者的問(wèn)題

哈希方法返回常數(shù)會(huì)造成什么結(jié)果?有很多次面試者會(huì)回答說(shuō) map 集合里會(huì)有且僅有一個(gè)元素,因?yàn)?map 中的舊元素總會(huì)被新的覆蓋。這個(gè)回答當(dāng)然是錯(cuò)誤的。

哈希沖突并不會(huì)導(dǎo)致 HashMap 覆蓋一個(gè)已經(jīng)存在于集合中的元素,這種情況只會(huì)在使用者試圖向集合中放入兩個(gè)元素,并且它們的鍵對(duì)于 equal() 方法是相等的時(shí)候才會(huì)發(fā)生。

鍵不相等但又會(huì)產(chǎn)生哈希沖突的不同元素最終會(huì)以某種數(shù)據(jù)結(jié)構(gòu)存儲(chǔ)在 HashMap 的同一個(gè)桶中(注意,在這種情況下,因?yàn)椴迦牒筒檎业牟僮鞫家馁M(fèi)更長(zhǎng)的時(shí)間,所以整體的性能就會(huì)受到影響)。

首先,我們用一個(gè)小程序來(lái)模擬哈希沖突。下面的寫(xiě)法可能比較夸張,因?yàn)樗斐傻臎_突比現(xiàn)實(shí)中多得多,但這個(gè)程序?qū)τ谧C實(shí)哈希沖突的條件還是很重要的。

我們使用一個(gè) Person 對(duì)象作為 map 的鍵,以字符串作為值。下面是 Person 的具體實(shí)現(xiàn),有一個(gè) firstName 字段,一個(gè) lastName 字段和一個(gè) ID 屬性,其中 ID 屬性以 UUID 對(duì)象表示。

public class Person {
? ? private String firstName;
? ? private String lastName;
? ? private UUID id;
? ? public Person(String firstName, String lastName, UUID id) {
? ? ? ? this.firstName = firstName;
? ? ? ? this.lastName = lastName;
? ? ? ? this.id = id;
? ? }
? ? @Override
? ? public int hashCode() {
? ? ? ? return 5;
? ? }
? ? @Override
? ? public boolean equals(Object obj) {
? ? ? ? // ... pertty good equals here taking into account the id field...
? ? }
? ? // ...
}

現(xiàn)在我們可以開(kāi)始制造一些沖突。 

private static final int LIMIT = 500_000;
private void fillAndSearch() {
? ? ?Person person = null;
? ? ?Map<Person, String> map = new HashMap<>();
? ? ?for (int i=0;i<LIMIT;i++) {
? ? ? ? UUID randomUUID = UUID.randomUUID();
? ? ? ? person = new Person("fn", "ln", randomUUID);
? ? ? ? map.put(person, "comment" + i);
? ? ?}
? ? ?long start = System.currentTimeMillis();
? ? ?map.get(person);
? ? ?long stop = System.currentTimeMillis();
? ? ?System.out.println(stop-start+" millis");
}

上面的代碼在一臺(tái)高性能計(jì)算機(jī)上運(yùn)行了兩個(gè)半小時(shí)。其中,最后的查找操作耗費(fèi)了大約 40 毫秒?,F(xiàn)在我們對(duì) Person 類進(jìn)行修改:使它實(shí)現(xiàn) Comparable 接口,并且添加了下面的方法:

@Override
public int compareTo(Person person) {
? ? return this.id.compareTo(person.id);
}

再一次運(yùn)行之前的程序,這一次在我的機(jī)器上它耗費(fèi)的時(shí)間少于 1 分鐘。其中,最終的查找操作耗費(fèi)的時(shí)間接近為 0 毫秒 —— 比之前提高了 150 倍!

就像前面說(shuō)的,Java 8 做了很多優(yōu)化,其中也包括HashMap 類。在 Java 7 中,兩個(gè)不同的元素,如果它們的鍵產(chǎn)生了沖突,那么會(huì)被存儲(chǔ)在同一個(gè)鏈表中。而從 Java 8 開(kāi)始,如果發(fā)生沖突的頻率大于某一個(gè)閾值(8),并且 map 的容量超過(guò)了另一個(gè)閾值(64),整個(gè)鏈表就會(huì)被轉(zhuǎn)換成一個(gè)二叉樹(shù)。

原來(lái)如此!所以,對(duì)于沒(méi)有實(shí)現(xiàn) Comparable 的鍵,最終的樹(shù)是不平衡的;而對(duì)于實(shí)現(xiàn)了 Comparable 的鍵,其二叉樹(shù)就會(huì)是高度平衡的。事實(shí)是這樣嗎?不是。HashMap 內(nèi)部是紅黑樹(shù),也就是說(shuō)它總是平衡的。我通過(guò)反射機(jī)制,查看了最終的樹(shù)結(jié)構(gòu)。對(duì)于擁有 50000 個(gè)元素(不敢讓數(shù)字更大了)的 HashMap 來(lái)說(shuō),兩種不同的情況下(實(shí)現(xiàn)或是不實(shí)現(xiàn) Comparable 接口)樹(shù)的高度都是 19 。

那么,為什么之前的實(shí)驗(yàn)結(jié)果會(huì)有那么大的差別呢?原因在于,當(dāng) HashMap 想要為一個(gè)鍵找到對(duì)應(yīng)的位置時(shí),它會(huì)首先檢查新鍵和當(dāng)前檢索到的鍵之間是否可以比較(也就是實(shí)現(xiàn)了 Comparable 接口)。如果不能比較,它就會(huì)通過(guò)調(diào)用 tieBreakOrder(Object a,Object b) 方法來(lái)對(duì)它們進(jìn)行比較。這個(gè)方法首先會(huì)比較兩個(gè)鍵對(duì)象的類名,如果相等再調(diào)用 System.identityHashCode 方法進(jìn)行比較。這整個(gè)過(guò)程對(duì)于我們要插入的 500000 個(gè)元素來(lái)說(shuō)是很耗時(shí)的。另一種情況是,如果鍵對(duì)象是可比較的,整個(gè)流程就會(huì)簡(jiǎn)化很多。因?yàn)殒I對(duì)象自身定義了如何與其它鍵對(duì)象進(jìn)行比較,就沒(méi)有必要再調(diào)用其他的方法,所以整個(gè)插入或查找的過(guò)程就會(huì)快很多。值得一提的是,在兩個(gè)可比的鍵相等時(shí)(compareTo 方法返回 0)的情況下,仍然會(huì)調(diào)用 tieBreakOrder 方法。

總而言之,在 Java 8 的 HashMap 里,如果一個(gè)桶里存放了大量的元素,它在達(dá)到閾值時(shí)就會(huì)被轉(zhuǎn)換為一棵紅黑樹(shù),對(duì)于實(shí)現(xiàn)了 Comparable 接口的鍵來(lái)說(shuō),插入或刪除的操作會(huì)比沒(méi)有實(shí)現(xiàn) Comparable 接口的鍵更簡(jiǎn)單。

通常,如果一個(gè)桶不會(huì)發(fā)生那么多次沖突的話,這整個(gè)機(jī)制不會(huì)帶來(lái)多大的性能提升,但起碼現(xiàn)在我們對(duì) HashMap 的內(nèi)部原理有了更多了解。

容器(LinkedList、HashMap、Compare)

HashSet的底層是HashMap,TreeSet的底層是TreeMap。

TreeSet中存放自定義類時(shí)應(yīng)該先指定比較的規(guī)則(compare)

1.內(nèi)部比較器|自然排序

要當(dāng)前比較的類型實(shí)現(xiàn)一個(gè)借口Comparable接口,重寫(xiě)compareTo方法,方法的內(nèi)部制定比較規(guī)則, 硬編碼習(xí)慣,不夠靈活,每次修改源代碼。

public class Student implements Comparable<Student>{ 
    .........
    //重寫(xiě)compareTo方法
    @Override
    public int compareTo(Student o) {
        return this.stuName.length()-o.stuName.length();
    }
}

2.外部比較器|自定義排序

使用任何一個(gè)實(shí)現(xiàn)類實(shí)現(xiàn)一個(gè)接口Comparator,重寫(xiě)compare方法,方法的內(nèi)部制定比較規(guī)則。

public class CompareDemo {
    public static void main(String[] args) {
        Comparator<Student> comparator=new Home();
        //lambda表達(dá)式
        comparator=(Student o1, Student o2)->{return o1.getStuName().length()-o2.getStuName().length();};
        //指定使用參數(shù)比較器
        //TreeSet(Comparator<? super E> comparator)
        TreeSet treeSet=new TreeSet(comparator);
        treeSet.add(new Student("張三爸",40,158));
        treeSet.add(new Student("張三",20,168));
        treeSet.add(new Student("張三爺爺",60,178));
        System.out.println(treeSet);
    }
}
class Home implements Comparator<Student>{
    @Override
    public int compare(Student o1, Student o2) {
        return o1.getStuName().length()-o2.getStuName().length();
    }
}

HashMap應(yīng)該注意的事項(xiàng) :

在這里插入圖片描述

手寫(xiě)HashMap應(yīng)該注意對(duì)key值進(jìn)行比較,如果key相同則新的value覆蓋舊的value。table是上圖中橫向的數(shù)組,默認(rèn)初始值為16,計(jì)算key在數(shù)組中的位置時(shí)使用:key.hashcode%table.length,這種方式效率較低,一般使用 key.hashcode&(table.length-1)。

class MyHashMap{
    private Node [] table;
    private int size;
    public void put(Object key, Object value) {
        if(table==null){
            table=new Node[16];
        }
        int hash=getHash(key);
        Node node=table[hash];//獲取他在數(shù)組上的位置
        Node newNode=new Node(hash,key,value,null);
        if(node==null){
            table[hash]=newNode;
            size++;
        }else{
            while(true){
                //進(jìn)行key值的比較
                if(node.getKey().equals(key)){
                    node.setValue(value);
                    break;
                }
                if(node==null){
                    break;
                }
                node=node.getNext();
            }
            node.setNext(newNode);
            size++;
        }
    }
    public int getHash(Object key){
        return key.hashCode()&(table.length-1);
    }
    @Override
    public String toString() {
        StringBuilder sbr=new StringBuilder("{");
        for(Node node:table){
            while(node!=null){
                sbr.append(node.getKey()+"--->"+node.getValue()+",");
                node=node.getNext();
            }
        }
        sbr.setCharAt(sbr.length()-1,'}');
        return sbr.toString();
    }
}
  • Properties:用來(lái)代替hashMap,實(shí)現(xiàn)了Map接口,線程安全的
  • HashTable:可以代替hashMap,線程安全

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • Java中四種線程池的使用示例詳解

    Java中四種線程池的使用示例詳解

    這篇文章主要給大家介紹了關(guān)于Java中四種線程池的使用方法,四種線程池分別包括FixedThreadPool、CachedThreadPool、ScheduledThreadPool以及SingleThreadExecutor,文中給出了詳細(xì)的示例代碼供大家參考,需要的朋友們下面來(lái)一起看看吧。
    2017-08-08
  • 最簡(jiǎn)單的MyBatis Plus的多表聯(lián)接、分頁(yè)查詢實(shí)現(xiàn)方法

    最簡(jiǎn)單的MyBatis Plus的多表聯(lián)接、分頁(yè)查詢實(shí)現(xiàn)方法

    這篇文章主要介紹了最簡(jiǎn)單的MyBatis Plus的多表聯(lián)接、分頁(yè)查詢實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-11-11
  • 三道java新手入門面試題,通往自由的道路--多線程

    三道java新手入門面試題,通往自由的道路--多線程

    這篇文章主要為大家分享了最有價(jià)值的3道多線程面試題,涵蓋內(nèi)容全面,包括數(shù)據(jù)結(jié)構(gòu)和算法相關(guān)的題目、經(jīng)典面試編程題等,對(duì)hashCode方法的設(shè)計(jì)、垃圾收集的堆和代進(jìn)行剖析,感興趣的小伙伴們可以參考一下
    2021-07-07
  • Mybatis延遲加載原理和延遲加載配置詳解

    Mybatis延遲加載原理和延遲加載配置詳解

    這篇文章主要介紹了Mybatis延遲加載原理和延遲加載配置詳解,MyBatis中的延遲加載,也稱為懶加載,是指在進(jìn)行表的關(guān)聯(lián)查詢時(shí),按照設(shè)置延遲規(guī)則推遲對(duì)關(guān)聯(lián)對(duì)象的select查詢,需要的朋友可以參考下
    2023-10-10
  • JavaEE通過(guò)response實(shí)現(xiàn)請(qǐng)求重定向

    JavaEE通過(guò)response實(shí)現(xiàn)請(qǐng)求重定向

    這篇文章主要介紹了JavaEE通過(guò)response實(shí)現(xiàn)請(qǐng)求重定向的方法,非常的簡(jiǎn)單實(shí)用,有需要的朋友可以參考下
    2014-10-10
  • java Socket UDP實(shí)例詳解

    java Socket UDP實(shí)例詳解

    這篇文章主要介紹了java Socket UDP實(shí)例詳解的相關(guān)資料,需要的朋友可以參考下
    2017-02-02
  • 簡(jiǎn)單談?wù)凧ava垃圾回收

    簡(jiǎn)單談?wù)凧ava垃圾回收

    本文是看了James Gosling的<<Java程序設(shè)計(jì)語(yǔ)言>>后結(jié)合自己的一些項(xiàng)目經(jīng)驗(yàn),簡(jiǎn)單總結(jié)下關(guān)于java的垃圾回收問(wèn)題的看法,有需要的小伙伴可以參考下
    2016-05-05
  • java編程實(shí)現(xiàn)求解八枚銀幣代碼分享

    java編程實(shí)現(xiàn)求解八枚銀幣代碼分享

    這篇文章主要介紹了java編程實(shí)現(xiàn)求解八枚銀幣代碼分享,具有一定參考價(jià)值,需要的朋友可以了解下。
    2017-11-11
  • Java并發(fā)編程之volatile與JMM多線程內(nèi)存模型

    Java并發(fā)編程之volatile與JMM多線程內(nèi)存模型

    這篇文章主要介紹了Java并發(fā)volatile與JMM多線程內(nèi)存模型,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-05-05
  • 如何在java文件中設(shè)置文字顏色:setTextColor()

    如何在java文件中設(shè)置文字顏色:setTextColor()

    這篇文章主要介紹了如何在java文件中設(shè)置文字顏色:setTextColor(),文末補(bǔ)充介紹了在java代碼中設(shè)置字體顏色方法總結(jié),結(jié)合實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-09-09

最新評(píng)論