最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

為什么不建議使用Java自定義Object作為HashMap的key

 更新時間:2022年06月30日 10:10:22   作者:??架構(gòu)悟道????  
這篇文章主要介紹了為什么不建議使用Java自定義Object作為HashMap的key,文章圍繞主題展開詳細的內(nèi)容介紹,具有一定的參考價值,感興趣的小伙伴可以參考一下

前言

此前部門內(nèi)的一個線上系統(tǒng)上線后內(nèi)存一路飆高、一段時間后直接占滿。協(xié)助開發(fā)人員去分析定位,發(fā)現(xiàn)內(nèi)存中某個Object的量遠遠超出了預期的范圍,很明顯出現(xiàn)內(nèi)存泄漏了。

結(jié)合代碼分析發(fā)現(xiàn),泄漏的這個對象,主要存在一個全局HashMap中,是作為HashMap的Key值。第一反應就是這里key對應類沒有去覆寫equals()和hashCode()方法,但對照代碼仔細一看卻發(fā)現(xiàn)其實已經(jīng)按要求提供了自定義的equals和hashCode方法了。進一步走讀業(yè)務實現(xiàn)邏輯,才發(fā)現(xiàn)了其中的玄機。

踩坑歷程回顧

鑒于項目代碼相對保密,這里舉個簡單的DEMO來輔助說明下。

場景: 內(nèi)存中構(gòu)建一個HashMap<User, List<Post>>映射集,用于存儲每個用戶最近的發(fā)帖信息(只是個例子,實際工作中如果遇到這種用戶發(fā)帖緩存的場景,一般都是用的集中緩存,而不是單機緩存)。

用戶信息User類定義如下:

@Data
public class User {
    // 用戶名稱
    private String userName;
    // 賬號ID
    private String accountId;
    // 用戶上次登錄時間,每次登錄的時候會自動更新DB對應時間
    private long lastLoginTime;
    // 其他字段,忽略
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return lastLoginTime == user.lastLoginTime &&
                Objects.equals(userName, user.userName) &&
                Objects.equals(accountId, user.accountId);
    }
    @Override
    public int hashCode() {
        return Objects.hash(userName, accountId, lastLoginTime);
    }
}

實際使用的時候,用戶發(fā)帖之后,會將這個帖子信息添加到用戶對應的緩存中。

/**
 *  將發(fā)帖信息加入到用戶緩存中
 *
 * @param currentUser 當前用戶
 * @param postContent 帖子信息
 */
public void addCache(User currentUser, Post postContent) {
    cache.computeIfAbsent(currentUser, k -> new ArrayList<>()).add(postContent);
}

當實際運行的時候,會發(fā)現(xiàn)問題就來了,Map中的記錄越來越多,遠超系統(tǒng)內(nèi)實際的用戶數(shù)量。為什么呢?仔細看下User類就可以知道了!

原來編碼的時候直接用IDE工具自動生成的equals和hashCode方法,里面將lastLoginTime也納入計算邏輯了。這樣每次用戶重新登錄之后,對應hashCode值也就變了,這樣發(fā)帖的時候判斷用戶是不存在Map中的,就會再往map中插入一條,隨著時間的推移,內(nèi)存中數(shù)據(jù)就會越來越多,導致內(nèi)存泄漏。

這么一看,其實問題很簡單。但是實際編碼的時候,很多人往往又會忽略這些細節(jié)、或者當時可能沒有這個場景,后面維護的人新增了點邏輯,就會出問題 —— 說白了,就是埋了個坑給后面的人踩上了。

hashCode覆寫的講究

hashCode,即一個Object的散列碼。HashCode的作用:

  • 對于List、數(shù)組等集合而言,HashCode用途不大;
  • 對于HashMap\HashTable\HashSet等集合而言,HashCode有很重要的價值。

HashCode在上述HashMap等容器中主要是用于尋域,即尋找某個對象在集合中的區(qū)域位置,用于提升查詢效率。

一個Object對象往往會存在多個屬性字段,而選擇什么屬性來計算hashCode值,具有一定的考驗:

  • 如果選擇的字段太多,而HashCode()在程序執(zhí)行中調(diào)用的非常頻繁,勢必會影響計算性能;
  • 如果選擇的太少,計算出來的HashCode勢必很容易就會出現(xiàn)重復了。

為什么hashCode和equals要同時覆寫

這就與HashMap的底層實現(xiàn)邏輯有關(guān)系了。

對于JDK1.8+版本中,HashMap底層的數(shù)據(jù)結(jié)構(gòu)形如下圖所示,使用數(shù)組+鏈表或者紅黑樹的結(jié)構(gòu)形式:

給定key進行查詢的時候,分為2步:

  • 調(diào)用key對象的hashCode()方法,獲取hashCode值,然后換算為對應數(shù)組的下標,找到對應下標位置;
  • 根據(jù)hashCode找到的數(shù)組下標可能會同時對應多個key(所謂的hash碰撞,不同元素產(chǎn)生了相同的hashCode值),這個時候使用key對象提供的equals()方法,進行逐個元素比對,直到找到相同的元素,返回其所對應的值。

根據(jù)上面的介紹,可以概括為:

  • hashCode負責大概定位,先定位到對應片區(qū)
  • equals負責在定位的片區(qū)內(nèi),精確找到預期的那一個

這里也就明白了為什么hashCode()和equals()需要同時覆寫。

數(shù)據(jù)退出機制的兜底

其實,說到這里,全局Map出現(xiàn)內(nèi)存泄漏,還有一點就是編碼實現(xiàn)的時候缺少對數(shù)據(jù)退出機制的考慮。 參考下redis之類的依賴內(nèi)存的緩存中間件,都有一個繞不開的兜底策略,即數(shù)據(jù)淘汰機制。

對于業(yè)務類編碼實現(xiàn)的時候,如果使用Map等容器類來實現(xiàn)全局緩存的時候,應該要結(jié)合實際部署情況,確定內(nèi)存中允許的最大數(shù)據(jù)條數(shù),并提供超出指定容量時的處理策略。比如我們可以基于LinkedHashMap來定制一個基于LRU策略的緩存Map,來保證內(nèi)存數(shù)據(jù)量不會無限制增長,這樣即使代碼出問題也只是這一個功能點出問題,不至于讓整個進程宕機。

public class FixedLengthLinkedHashMap<K, V> extends LinkedHashMap<K, V> {
    private static final long serialVersionUID = 1287190405215174569L;
    private int maxEntries;

    public FixedLengthLinkedHashMap(int maxEntries, boolean accessOrder) {
        super(16, 0.75f, accessOrder);
        this.maxEntries = maxEntries;
    }
    /**
     *  自定義數(shù)據(jù)淘汰觸發(fā)條件,在每次put操作的時候會調(diào)用此方法來判斷下
     */
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        return size() > maxEntries;
    }
}

總結(jié)

梳理下幾個要點:

  • 最好不要使用Object作為HashMap的Key
  • 如果不得已必須要使用,除了要覆寫equals和hashCode方法
  • 覆寫的equals和hashCode方法中一定不能有頻繁易變更的字段
  • 內(nèi)存緩存使用的Map,最好對Map的數(shù)據(jù)記錄條數(shù)做一個強制約束,提供下數(shù)據(jù)淘汰策略。

到此這篇關(guān)于為什么不建議使用Java自定義Object作為HashMap的key的文章就介紹到這了,更多相關(guān)Java HashMap的key內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Mybatis中resultMap的Colum和property屬性詳解

    Mybatis中resultMap的Colum和property屬性詳解

    這篇文章主要介紹了Mybatis中resultMap的Colum和property屬性,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教。
    2022-01-01
  • java對象轉(zhuǎn)化成String類型的四種方法小結(jié)

    java對象轉(zhuǎn)化成String類型的四種方法小結(jié)

    在java項目的實際開發(fā)和應用中,常常需要用到將對象轉(zhuǎn)為String這一基本功能。本文就詳細的介紹幾種方法,感興趣的可以了解一下
    2021-08-08
  • 解決idea2024版本創(chuàng)建項目時沒有java?8的版本選擇

    解決idea2024版本創(chuàng)建項目時沒有java?8的版本選擇

    這篇文章主要介紹了在使用IntelliJ?IDEA創(chuàng)建Spring?Boot項目時遇到的問題,包括Java版本選擇受限和項目結(jié)構(gòu)不完整,文中通過圖文介紹的非常詳細,需要的朋友可以參考下
    2025-03-03
  • Java中this和super的區(qū)別及this能否調(diào)用到父類使用

    Java中this和super的區(qū)別及this能否調(diào)用到父類使用

    這篇文章主要介紹了Java中this和super的區(qū)別及this能否調(diào)用到父類使用,this和super都是Java中常見的關(guān)鍵字,下文關(guān)于兩者區(qū)別介紹,需要的小伙伴可以參考一下
    2022-05-05
  • Spring Boot集成Spring Cache過程詳解

    Spring Boot集成Spring Cache過程詳解

    這篇文章主要介紹了Spring Boot集成Spring Cache過程詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2019-10-10
  • Java SPI的簡單小實例

    Java SPI的簡單小實例

    這篇文章主要介紹了Java SPI的簡單小實例,文中講解非常細致,代碼幫助大家更好的理解和學習,感興趣的朋友可以了解下
    2020-07-07
  • Hadoop源碼分析二安裝配置過程詳解

    Hadoop源碼分析二安裝配置過程詳解

    本篇是Hadoop源碼分析系列文章第二篇,主要介紹Hadoop安裝配置的詳細過程,后續(xù)本系列文章會持續(xù)更新,有需要的朋友可以借鑒參考下
    2021-09-09
  • spring boot容器啟動流程

    spring boot容器啟動流程

    spring cloud是基于spring boot快速搭建的,今天咱們就看看spring boot容器啟動流程,需要的朋友跟隨腳本之家小編一起學習吧
    2018-01-01
  • JAVA利用接口實現(xiàn)多繼承問題的代碼實操演示

    JAVA利用接口實現(xiàn)多繼承問題的代碼實操演示

    Java語言并不支持多繼承,這是由于多繼承會帶來許多復雜的問題,例如"菱形問題"等,下面這篇文章主要給大家介紹了關(guān)于JAVA利用接口實現(xiàn)多繼承問題的相關(guān)資料,需要的朋友可以參考下
    2024-03-03
  • SpringBoot使用Feign調(diào)用其他服務接口

    SpringBoot使用Feign調(diào)用其他服務接口

    這篇文章主要介紹了SpringBoot使用Feign調(diào)用其他服務接口,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-03-03

最新評論

宁乡县| 平舆县| 息烽县| 龙泉市| 永城市| 天峨县| 定安县| 湟源县| 松桃| 原平市| 阜南县| 永春县| 台东县| 金堂县| 枣阳市| 通河县| 博爱县| 高碑店市| 苏尼特左旗| 当雄县| 深州市| 武宁县| 禹城市| 徐水县| 东乌珠穆沁旗| 五大连池市| 通海县| 合作市| 长寿区| 赤壁市| 芦溪县| 承德市| 衢州市| 天台县| 鄄城县| 通化县| 射洪县| 南川市| 合山市| 渭南市| 水富县|