[Java][集合][ Map系列 ] [ HashMap]

简介

HashMap是基于哈希表的 Map 接口的实现。
提供键值对数据存储的功能。
常用于键值对映射存储的数据结构应用场景。

  • 继承关系
类 HashMap<K,V>
java.lang.Object
    继承者 java.util.AbstractMap<K,V>
    继承者 java.util.HashMap<K,V>
类型参数:
        K - 此映射所维护的键的类型
        V - 所映射值的类型
        所有已实现的接口:
        Serializable, Cloneable, Map<K,V>
        直接已知子类:
        LinkedHashMap, PrinterStateReasons

原理

其实就是数组+链表结合,通过hash表机制,即通过哈希函数得到存储数组的索引,index = f_hash(key),通过数组索引直接拿到value值。
但哈希函数无法保证不同的key的换算到唯一的索引,可能重复,这便是哈希冲突。
解决办法是在数组索引位置下挂一个链表,存储多个键值对,如果发现数组索引位置下有多个值,即有链表,则按着链表再查一次。
所以,一般是O(1)复杂度的查找效率。

它包括几个重要的成员变量:table, size, threshold, loadFactor, modCount。
  table是一个Entry[]数组类型,而Entry实际上就是一个单向链表。哈希表的”key-value键值对”都是存储在Entry数组中的。
  size是HashMap的大小,它是HashMap保存的键值对的数量。
  threshold是HashMap的阈值,用于判断是否需要调整HashMap的容量。threshold的值=”容量*加载因子”,当HashMap中存储数据的数量达到threshold时,就需要将HashMap的容量加倍。
  loadFactor就是加载因子。
  modCount是用来实现fail-fast机制的。

详细参考:
https://www.cnblogs.com/chengxiao/p/6059914.html

看图更清晰:
哈希表-数组+链表

  • HashMap.get()方法的实现
    key(hashcode)–>hash–>indexFor–>最终索引位置,找到对应位置table[i],再查看是否有链表,遍历链表,通过key的equals方法比对查找对应的记录。

HashMap 的两个重要参数

这是影响性能的两个关键参数。容量 和加载因子。
容量是哈希表中桶的数量,初始容量只是哈希表在创建时的容量。
加载因子 是哈希表在其容量自动增加之前可以达到多满的一种尺度。


数组结构的每一个格格称为桶

capacity 容量
capacity译为容量。capacity就是指HashMap中桶的数量。不就是存储数组大小嘛

size 元素数量总和
size表示HashMap中存放KV的数量(为链表和树中的KV的总和)。

loadFactor 装载因子
装载因子用来衡量HashMap满的程度。loadFactor的默认值为0.75f。
计算HashMap的实时装载因子的方法为:size/capacity,而不是占用桶的数量去除以capacity。
假设数组索引下挂的链表越多,每个链表越长,说明键值对总数越多,则size越大,完全可能超过数组大小,超越容量,从而size/capacity = loadFactor会大于1。
均匀稀疏的哈希表, loadFactor越小,平均查找性能越趋于O(1),显然,空间性能就不表现为越不饱和。

参考:
https://blog.csdn.net/fan2012huan/article/details/51087722

多线程下的使用

  • Collection子类的迭代器都是支持fail-fast(快速失败)的,但都不是完全保证的
    由所有此类的“collection 视图方法”所返回的迭代器都是快速失败 的:在迭代器创建之后,如果从结构上对映射进行修改,除非通过迭代器本身的 remove 方法,其他任何时间任何方式的修改,迭代器都将抛出 ConcurrentModificationException。因此,面对并发的修改,迭代器很快就会完全失败,而不冒在将来不确定的时间发生任意不确定行为的风险。
    注意,迭代器的快速失败行为不能得到保证,一般来说,存在非同步的并发修改时,不可能作出任何坚决的保证。快速失败迭代器尽最大努力抛出 ConcurrentModificationException。因此,编写依赖于此异常的程序的做法是错误的,正确做法是:迭代器的快速失败行为应该仅用于检测程序错误。

同步修改的几种方式

  • 1、使用包装类
    Map m = Collections.synchronizedMap(new HashMap(…));
  • 2、自定义同步,保证读写同步
  • 3、使用java.util.concurrent包下的适用替代类ConcurrentHashMap

HaspMap的使用,遍历等

HashMap<String, String> hashMap = new HashMap<>();

    //1、键值对存取操作
    //允许使用 null 值和 null 键
    hashMap.put("1", "Jack");
    hashMap.put(null, "Jack");
    hashMap.put("2", null);
    hashMap.put(null, null);

    System.out.println("hashMap.containsKey(null)=" + hashMap.containsKey(null));
    System.out.println("hashMap.get(null)" + hashMap.get(null));
    System.out.println("hashMap.get(2)" + hashMap.get("2"));
//遍历键值对, 单独遍历键、值,使用map.keySet(),map.values();
    Map map = hashMap;
    String integ = null;
    String key;
    Iterator iter = map.entrySet().iterator();
    while(iter.hasNext()) {
        Map.Entry entry = (Map.Entry)iter.next();
        key = (String)entry.getKey();
        integ = (String)entry.getValue();
        System.out.println("[" + key + "," + integ + "]");
    }

克隆 与 序列化

使用注意

  • 允许使用 null 值和 null 键
  • 此类不保证映射的顺序,特别是它不保证该顺序恒久不变
  • 传入的Key类型,必须实现equals()和hashCode()方法,如果重写equals()方法需同时重写hashCode方法

猜你喜欢

转载自blog.csdn.net/Hendy_Raw/article/details/82182958