Java-L09:Map,集合框架的另一部分

李文轩 2019-03-30
声明:这是本人学习极客时间的Java核心36讲的笔记,有侵权请联系我。


Java的集合框架,此处不包括 Map 和 java.util.concurrent 下的线程安全容器

Hashtable、HashMap、TreeMap

  • 都是 Map的实现,以键值对的形式存储和操作数据的容器类型

  • Hashtable是哈希表的实现,是同步的,性能开销比较大;具备无序特性;不支持 null键和值

  • HashMap也是哈希表的实现,不是同步的,所以比较常用;具备无序特性;支持null键和值;putget操作都达到常数时间的性能

  • TreeMap是基于红黑树的一种提供顺序访问的Map;getputremove之类的基本操作都是O(log(n))的时间复杂度。具体顺序由Comparator或键的自由顺序决定;所以一般需要排序对情况下是选择它。默认升序排序方式。当未实现Comparator或在实现中未对null情况进行判断时,key不能为null。

  • 同样是可以保证某种顺序,LinkedHashMap通常提供的是遍历顺序符合插入顺序,它的实现是通过为键值对维护一个双向链表


线程安全(来自评论区)

  • HashMap本身是不支持同步的;Hashtable因为通过阻塞状态的方式,所以在多线程下也会效率低下
  • 建议可以使用CollectionsynchronizedMap方法 或者 ConcurrentHashMap
    • ConcurrentHashMap类是基于lock实现锁分段
    • 将Map存放的数据分成一段一段的存储方式,然后给每一段数据分配一把锁,当一个线程占用锁访问其中一个段的数据时,其他段的数据也能被其他线程访问。
    • ConcurrentHashMap不仅保证了多线程运行环境下的数据访问安全性,而且性能上有长足的提升。

Map 整体结构

  • Hashtable比其他Map要不同,它是扩展了Dictionary类的

  • 其他Map都是扩展类AbstractMap的,里面包含类通用方法抽象。设计目的一句体现在不用接口上。

  • 大部分Map的使用场景都是,放入、访问或者删除,对顺序没有要求;HashMap的性能较好,它是一般情况的最好选择。

  • HashMap的性能依赖于哈希值的有效性:

    • equals相等,hashCode一定也要相等
    • 重写了hashCode也要重写equals
    • hashCode需要保持一致性,状态改变返回的哈希值仍然要一致
    • equals的对称、反射、传递等特性
    • compareTo的返回值需要和equals一致,否则会出香模凌两可的情况
  • LinkedHashMap通过特定构造函数,可以创建反映访问顺序的实例

  • 比如如果想建造一个空间敏感的资源池,我们希望在新资源加入时,同时释放最不常用的一个数据

import java.util.LinkedHashMap;
import java.util.Map;  
public class LinkedHashMapSample {
    public static void main(String[] args) {
        LinkedHashMap<String, String> accessOrderedMap = new LinkedHashMap<String, String>(16, 0.75F, true){
            @Override
            protected boolean removeEldestEntry(Map.Entry<String, String> eldest) { // 实现自定义删除策略,否则行为就和普遍 Map 没有区别
                return size() > 3;
            }
        };
        accessOrderedMap.put("Project1", "Valhalla");
        accessOrderedMap.put("Project2", "Panama");
        accessOrderedMap.put("Project3", "Loom");
        accessOrderedMap.forEach( (k,v) -> {
            System.out.println(k +":" + v);
        });
        // 模拟访问
        accessOrderedMap.get("Project2");
        accessOrderedMap.get("Project2");
        accessOrderedMap.get("Project3");
        System.out.println("Iterate over should be not affected:");
        accessOrderedMap.forEach( (k,v) -> {
            System.out.println(k +":" + v);
        });
        // 触发删除
        accessOrderedMap.put("Project4", "Mission Control");
        System.out.println("Oldest entry should be removed:");
        accessOrderedMap.forEach( (k,v) -> {// 遍历顺序不变
            System.out.println(k +":" + v);
        })
    }
}

  • 这段代码限制了资源池size为3,当“project4“加入时,同时删除了”project1“

HashMap源码

  • HashMap内部实现基本点

  • 可以看作一个数组和链表结合的复合结构,数组被分为桶(bucket)
  • 用哈希值决定了键值对属于哪个桶

  • 容量(capacity)和负载系数(load factor)。

    • 容量理论最大极限由 MAXIMUM_CAPACITY指定,数值为2的30次方
    • 在源码的putVal方法里可以看出,resize方法处理了几个问题:
      • 在表格是null的时候,resize方法会负责初始化
      • resize也负责扩容;新插入的键值对会检查++size > threshold;若true,则调用resize
    • 门阀值threshold等于(负载因子)*(容量),判断是否扩容的关键
    • 门阀值通常以倍数调整,当元素数量超过门阀值时,调整Map大小。
    • 而容量和负载因子决定了可用的桶的数量;若只使用一个桶,HashMap会退回链表的状态

  • 树化

    • 逻辑主要存在于putValtreeifyBin
    • 主要注意的是,如果桶里的元素数量大于TREEIFY_THRESHOLD
      • 如果容量小于MIN_TREEIFY_CAPACITY,只会进行简单扩容
      • 如果容量大于MIN_TREEIFY_CAPACITY,则是进行树化改造
    • 树化大主要原因是安全问题,若大部分键值对处于一个bin中,则会形成一个链表
      • 构造哈希冲突从而导致服务器CPU大量占用(链表查询为线性,严重影响存取)是比较简单的攻击手段

重写 hashCode 和 equals 方法

  • 如果我们要将自定义类当作键加入到HashMap中,如果不重写,结果可能和我们预想的不太一样

  • 如果我们没有重写自定义对象的hashCode方法,在作为键加入到HashMap里时调用hashCode方法的时候,程序将调用Object里的hashCode方法,即返回对象的内存地址。这并不是我们所期待的。

  • 重写可以调用Object.hash(Object...values)方法,或xx.hashCode()

  • 如果我们没有重写自定义对象的equals方法,即使我们用同样哈希值去找HashMap里的值的时候,get会返回null。因为我们没有重写equals的话,程序自动调用Object里的equals方法;这个固定方法是比较键的内存地址的,所以即使用同样的哈希值的不同对象,返回的依然是null。

  • 重写可以先检查是否是null对象和是否同一类型的对象。如果不是null和是同一类型,那么可以逐个对比两个对象里的成员变量

  • 两个equals返回true的对象一定有一样的hashCode,但是两个有相同hashCode的对象不一定equals返回true。因为hashCode其实作为一个对象存储的参数存在于对象中,有可能两个对象有相同的hashCode,但是他们的成员变量不一定都是相等;一定是所以成员变量相等和同一对象类型的两个对象才相等。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容