Java基础之Map

Map

  • 二元偶对象保存的最大父接口,独立父接口

    • 存储二元偶对象

      • public interface Map<K, V>
      • 保存形式:key=value
    • 接口对象实例化时要指定 keyvalue 的数据类型

      • 任意引用类型
      • 保存具有映射关系的数据
      • key 不允许重复,value 可以重复
        • key 只唯一对应一个 value
        • 不同 key 的 value 可以相同
        • key 重复时会将 value 值替换
  • 自定义 key 的类中必须重写 hashCode()equals() 方法

    • 在进行数据存储时使用 key 计算 hash 值得到存储位置

      • 数据保存时要判断重复会使用 equals()
    • 获取数据时同样将使用 key 计算 hash 值获取元素位置

      • 通过 equals() 进行数据比对
    • 实际常用类型就是 StringLongInteger

      • 尽量使用系统类
  • 核心意义:需要通过 key 获取 对应的 value

  • 建议实现子类都有两个标准构造函数

    • 无法强制执行
    • 无参构造函数:创建空映射
    • 具有Map类型单个参数的构造函数:创建具有相同键值的新映射,映射作为参数
      • 允许用户复制任何映射,生成所需类的等效映射
  • 开发使用目的

    • Collection 集合保存数据:遍历输出

    • Map 集合保存数据:通过 key 查找

  • 常用方法

    • V put(K key, V value);			// 保存(修改)数据,已存在 key 时更新对应的 value 并返回原 value
      V get(Object key);				// 获取指定 key 对应的 value,不存在返回 null
      default V getOrDefault(Object key, V defaultValue) {			// 获取指定key对应的 value,不存在返回指定默认值
          V v;
          return (((v = get(key)) != null) || containsKey(key))
              ? v
              : defaultValue;
      }
      Set<Map.Entry<K, V>> entrySet();								// 将 Map 集合转为 Set 集合,集合中保存 Map.Entry 对象
      boolean containsKey(Object key);								// 查询指定的 key 是否存在
      boolean containsValue(Object value);							// 查询指定 value 是否存在
      Set<K> keySet();												// Map 集合中的所有 key 转为 Set 集合
      Collection<V> values();											// Map 集合中所有 value 转为 Collection 集合
      V remove(Object key);											// 根据 key 删除映射关系
      int size();														// 获取元素个数
      boolean isEmpty();												// 判断集合是否为空,(size() == 0)
      void clear();													// 清除数据
      
      Map.of();														// JDk 1.9 之后追加扩充静态方法,用于填充数据
      /*
      使用时不允许出现重复 key,会有 IllegalArgumentException(非法参数) 异常
      不允许为 null,会有 NullPointerException(空指向) 异常
      非标准用法,Map 主要是实例化其子类对象使用
      */
      
  • 正常开发使用其实现子类进行接口对象实例化

    • 常用子类
      • HashMap
      • LinkedHashMap
      • Hashtable
      • TreeMap
  • 在这里插入图片描述

SortMap

  • 继承 Map<K, V> 接口
  • 根据其键的自然顺序排序的
    • 或在创建时提供的Comparator器排序
  • 此顺序在遍历已排序地图的集合视图
    • entrySetkeySetvalues方法返回时反映出来
  • 插入排序映射的所有键都必须实现 Comparable 接口
    • 或被指定的比较器接受
    • 所有键必须是相互可比较的
  • 有序映射实现Map接口:有序映射维护的排序(无论是否提供显式比较器)必须与 equals 一致
    • 因为 Map 接口根据 equals 操作定义
    • 但排序后的映射使用其 compareTocompare 方法执行键比较
  • 通用的排序地图实现类应提供四个标准构造函数
    • 无法强制执行此建议
    • 无参构造:创建空的排序映射,根据键的自然顺序排序
    • Comparator类型单参数构造函数:创建根据指定比较器排序的空排序映射
    • Map 类型单参数构造函数:创建一个具有与其参数相同的键值映射的新映射,并根据键的自然顺序进行排序
    • SortedMap 类型单参数构造函数:创建具有与输入排序映射相同的键值映射和相同顺序的新排序映射
NavigableMap
  • 继承 SortMap<K, V>

    • 使用导航方法扩展的 SortedMap ,返回给定搜索目标的最接近匹配项
  • /**
    * @param key 键
    * @return 最大键 < 、<=、>=、> 指定 key 的 Map.Entry 对象,没有返回null
    * @throws ClassCastException  指定的键无法与地图中当前的键进行比较
    * @throws NullPointerException 如果指定的键为空并且此映射不允许空键
    */
    Map.Entry<K,V> lowerEntry(K key);
    Map.Entry<K,V> floorEntry(K key);
    Map.Entry<K,V> ceilingEntry(K key);
    Map.Entry<K,V> higherEntry(K key);
    
    /**
    * @param key 键
    * @return 最大键 < 、<=、>=、> 指定 key 的 key,没有返回null
    * @throws ClassCastException 指定的键无法与地图中当前的键进行比较
    * @throws NullPointerException 指定的键为空并且此映射不允许空键
    */
    K lowerKey(K key);
    K floorKey(K key);
    K ceilingKey(K key);
    K higherKey(K key);
    
    
    /**
    * @return 第一个(最后一个)映射对象,如果映射为空,返回null
    */
    Map.Entry<K,V> firstEntry();
    Map.Entry<K,V> lastEntry();
    
    /**
    * 获取并删除第一个(最后一个)映射对象
    * @return 被删除的第一个(最后一个)映射对象,如果映射为空,返回null
    */
    Map.Entry<K,V> pollFirstEntry();
    Map.Entry<K,V> pollLastEntry();
    
    /**
    * 返回映射的逆序视图
    * 等价于Collections.reverseOrder(comparator())的排序
    * @return 逆序视图
    */
    NavigableMap<K,V> descendingMap();
    
    /**
    * 迭代器按升序返回键
    * 元素移除通过Iterator.remove、 Set.remove、 removeAll、 retainAll和clear
    * 不支持add或addAll操作。
    * @return 键的可导航集视图
    */
    NavigableSet<K> navigableKeySet();
    
    /**
    * 迭代器按降序返回键
    * 元素移除通过Iterator.remove、 Set.remove、 removeAll、 retainAll和clear
    * @return 键的反向可导航集视图
    */
    NavigableSet<K> descendingKeySet();
    
    /**
    * 返回映射部分的视图,键范围 fromKey ~ toKey
    * fromKey和toKey相等,返回的映射为空,除非fromInclusive和toInclusive都为真
    * @param fromKey  指定键范围左端点
    * @param fromInclusive 低端点包含在返回的视图中,则为true
    * @param toKey 指定键范围右端点
    * @param toInclusive 返回的视图中包含高端端点,则为true
    * @return   部分的视图,其键范围从fromKey到toKey
    * @throws IllegalArgumentException 尝试插入超出范围的键或构造端点位于范围之外的子映射时抛出
    * @throws ClassCastException fromKey 和 toKey 无法相互比较
    * @throws NullPointerException fromKey 或 toKey 为 null 且此映射不允许 null 键
    * @throws IllegalArgumentException fromKey大于toKey;或 fromKey、 toKey 在 Map 指定范围之外
    */
    NavigableMap<K,V> subMap(K fromKey, boolean fromInclusive,
                             K toKey,   boolean toInclusive);
    
    /**
    * 部分的视图,键小于(大于) toKey,inclusive 为 true 时等于
    * @param toKey 指定key
    * @param inclusive 为rue时等于
    * @return 键小于(大于) toKey 的映射视图
    */
    NavigableMap<K,V> headMap(K toKey, boolean inclusive);
    NavigableMap<K,V> tailMap(K fromKey, boolean inclusive);
    
    /**
    * 部分视图,键范围 [fromKey, toKey)、 小于 toKey 、 大于 toKey
    * @param fromKey 最小 key
    * @param toKey 指定 key
    * @return 返回区间 [fromKey, toKey)、 小于 toKey、 大于 toKey 的映射视图
    */
    SortedMap<K,V> subMap(K fromKey, K toKey);
    SortedMap<K,V> headMap(K toKey);
    SortedMap<K,V> tailMap(K fromKey);
    

AbstractMap

  • 实现 Map<K, V>接口
  • 实现不可修改 Map:只需要扩展此类并实现 entrySet 方法
    • 方法返 Map 映射的集合视图
    • 集合不应该支持 addremove 方法
    • 迭代器也不应该支持 remove 方法
  • 实现可修改的映射:必须额外覆盖 put 方法
    • 否则抛出 UnsupportedOperationException
    • entrySet().iterator() 返回的迭代器必须额外实现 remove 方法
  • 通常应提供 void(无参数)和 map 构造函数
WeakHashMap
  • 继承AbstractMap,实现Map接口

    • 基于哈希表的实现,带有弱键;支持空值和空键
  • 键不常用时将被自动删除

    • 给定键的映射的存在不会阻止该键被垃圾收集器丢弃,使其可终结,最终确定,然后回收
    • 当键被丢弃时,其条目被有效地从映射中删除
  • 具有与HashMap类相似的性能特征

    • 具有相同的初始容量和负载因子的效率参数。
  • 类不同步的。可使用Collections.synchronizedMap方法构造同步WeakHashMap

IdentityHashMap
  • 继承AbstractMap,实现Map接口

    • 使用哈希表实现Map接口,比较键(和值)时使用引用相等代替对象相等
      • 当且仅当k1==k2,k1、k2被认为相等
    • 普通Map实现(如HashMap
      • 当且仅当k1 == null ? k2 == null : k1.equals(k2)时,k1、k2被认为相等
  • 不是通用的Map实现,实现Map接口,却故意违反了Map的一般原则

    • 在比较对象时使用equals方法
    • 仅在需要引用相等语义的极少数情况下使用
    • 典型用途是保留拓扑的对象图转换
  • 提供所有可选的映射操作,允许空值和空键,不保证顺序

HashMap
  • 继承AbstractMap,实现Map接口
    • 散列表,存储的键值对(key-value)映射
    • 不支持线程同步,不保证映射的顺序
    • 所有此类返回的迭代器都是快速失败的
  • 根据键的 HashCode 值存储数据,具有很快的访问速度
  • 允许空值和空键
    • 最多允许一个键为 null
LinkedHashMap
  • HashMap 的子类,强调实现 Map 接口
    • 添加数据时同样会替换相同 key 的 value 值
    • 初始容量和负载因子定义与HashMap完全相同
      • 初始容量过高的影响没有HashMap严重,因为此类的迭代时间不受容量的影响
  • 底层维护双向链表结构
    • 在 Map 集合中保存顺序为添加顺序
TreeMap
  • 继承 AbstractMap,实现 NavigableMap 接口

    • 基于红黑树的NavigableMap实现
      • 此实现不同步
    • iterator方法返回的迭代器是快速失败的
  • 保存数据时已经进行了大小比较

    • 默认自然升序排列
    • 可自定义排序规则
      • 保存的数据是有序的

常用子类

HashMap

介绍

Map 接口最常用子类

  • 主要特点:无序存储

    • 没有方法同步处理
  • HashMap 实例化 Map 接口,可以保存 null

    • key 不允许重复,且无序
  • 底层数据结构:数组 + 链表 + 红黑树

    • JDK7:数组 + 链表
    • JDK8:数组 + 链表 + 红黑树
      • 为适应大数据时代的海量数据存储,保存元素数量超过阈值时转为红黑树
  • 迭代时间与实例的容量加上大小成正比

    • 追求迭代性能时不要将初始容量设置得太高(或负载因子太低)
  • 两个影响性能的参数

    • 初始容量:容量是哈希表中的桶数,初始容量是哈希表创建时的容量
    • 负载因子:哈希表在扩容前允许的最大容量比例
    • 阈值:容量 * 负载因子
      • 当哈希表数据量超过阈值,对哈希表进行二倍扩容
        • 扩容后会重新哈希,即重建内部数据结构
    • 一般规则,默认负载因子 (0.75) 在时间和空间成本之间提供了良好的折中
      • 较高的值会减少空间开销,但增加查找成本
      • 设置初始容量时,应考虑映射中的预期条目数及其负载因子,尽量减少重新哈希操作的次数
  • 在这里插入图片描述

  • 维护 Node 数据类型数组存放数据,默认为 null

    • transient Node<K,V>[] table;

      • 首次使用时初始化,根据需要调整大小
      • 长度始终是 2 的幂
    • 使用 Node 类型实际存放数据

      • Map 实际也是单列集合,集合中存放的是 Node 对象
  • 关键参数

    • static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;  		// 默认初始容量 16;必须是 2 的冥
      static final int MAXIMUM_CAPACITY = 1 << 30;				// 最大容量,必须是 2 <= 1 << 30 的幂
      static final float DEFAULT_LOAD_FACTOR = 0.75f;				// 默认加载因子
      static final int TREEIFY_THRESHOLD = 8;						// 树化阈值,进行树化判断
      static final int UNTREEIFY_THRESHOLD = 6;					// 取消树化阈值,从红黑树转为链表
      static final int MIN_TREEIFY_CAPACITY = 64;					// 树化最小容量
      
  • 四种构造

    • public HashMap() {
          this.loadFactor = DEFAULT_LOAD_FACTOR;					// 无参构造,使用默认加载因子
      }
      
      public HashMap(int initialCapacity) {
          this(initialCapacity, DEFAULT_LOAD_FACTOR);				// 初始化容量,使用默认加载因子
      }
      
      public HashMap(int initialCapacity, float loadFactor) {		// 指定初始化容量和加载因子
          if (initialCapacity < 0)								// 初始容量 < 0 ,抛出非法参数异常
              throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);
          if (initialCapacity > MAXIMUM_CAPACITY) 				// 容量超过最大值
              initialCapacity = MAXIMUM_CAPACITY;
          if (loadFactor <= 0 || Float.isNaN(loadFactor))			// 加载因子非法参数
              throw new IllegalArgumentException("Illegal load factor: " +
                                                 loadFactor);
          this.loadFactor = loadFactor;							// 使用执行的加载因子
          this.threshold = tableSizeFor(initialCapacity);			// (容量 * 负载因子) = 返回给定目标容量的 2 次方
      }
      
      public HashMap(Map<? extends K, ? extends V> m) {			// 以Map为参数构造
          this.loadFactor = DEFAULT_LOAD_FACTOR;					// 使用默认加载因子	
          putMapEntries(m, false);								// 将 Map 数据添加到新的 HashMap 中
      }
      
Map.Entry
  • HashMap 类中定义内部类 Node ,实现 Map.Entry 接口

    • static class Node<K, V> implements Map,Entry{}

      • 存储数据时将数据保存在 Node 节点
    • keyvalue 封装在 Map.Entry 接口子类 Node

      • public static interface Map.Entry<K, V>{}
    • 利用 Node 节点的只有 链表 和 二叉树

      • 时间复杂度为 nlog(n)
  • 接口操作方法

    • 获取 key:public K getKey()
    • 获取 value:public V getValue()
  • 在 JDK 1.9 之前开发版本中不考虑创建 Map.Entry 对象

    • 正常开发不需要关心 Map.Entry 对象
    • JDK 1.9 后 Map 接口追加新方法创建 Map.Entry 对象
      • pbulic static <K,V> Map<K,V> entry(K k, V v)
  • Map.Entry 主要作用:作为 key、value 的包装类型使用

    • 大多将 key、value 包装为 Mao.Entry 对象使用
  • 在这里插入图片描述

添加元素
执行流程
  1. 获取元素哈希值

    • 通过 hashCode 方法
  2. 对哈希值进行计算得出元素在哈希表中存放的位置

    • static final int hash(Object key) {			// 哈希su
          int h;
          return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);		// key 的 hash值 按位异或 自身无符号右移16位
      }
      
  3. 该位置没有元素,直接存放

    • 判断哈希表容量是否达到阈值
      • 满足条件后进行扩容
  4. 该位置已存在元素,进行相等判断(通过 equals 方法)

    • 相等时不再添加
    • 不相等时以链表方式追加到最后
      • 追加后进行链表树化判断
        • 满足条件后开启树化
    /**
    * HashMap 中实际添加元素的方法
    * @param hash 哈希值
    * @param key  key
    * @param value value
    * @param onlyIfAbsent 为 true 不更改现有value值
    * @param evict 为 false 则处于创造模式
    * @return
    */
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)             // 哈希表未初始化
            n = (tab = resize()).length;                                // 进行哈希表扩容并获取长度                                                
        if ((p = tab[i = (n - 1) & hash]) == null)                      // 获取该位置的哈希值并判断是否为空
            tab[i] = newNode(hash, key, value, null);                   // 为空时直接创建新节点放到该位置
        else {
            Node<K,V> e; K k;
            // 该位置哈希值和当前元素哈希值相等、且 key 和当前元素 key 相等
            if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))       
                e = p;                                                                          // 直接使用该位置节点
            else if (p instanceof TreeNode)                                                     // 判断该位置节点是否是 树节点
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);                 // 转为树节点并进行树化元素添加
            else {                                                                              
                // 该节点不是树节点,且和当前元素哈希值、key都不相等
                for (int binCount = 0; ; ++binCount) {                                          // 遍历该位置所有节点元素
                    if ((e = p.next) == null) {                                                 
                        // 判断是否是该位置链表中最后一个节点,执行到此时 e == null
                        p.next = newNode(hash, key, value, null);                               // 创建新节点作为下一个节点
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st                    // 判断该位置链表是否达到树化标准
                            treeifyBin(tab, hash);                                              // 进行树化                             
                        break;                                                                  // 退出遍历
                    }
                    // 判断所有节点中有与该元素 哈希值、key 相等时退出遍历 
                    if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
                        break;                                                                  
                    p = e;                                                                      // 更新当前节点
                }
            }
            if (e != null) {								// 已存在对应的 key-value;否则此时 e 应该为 null
                V oldValue = e.value;						// 获取此时节点 value 值 
                if (!onlyIfAbsent || oldValue == null)		// 判断是否允许修改元素 或 value 是否为 bull    
                    e.value = value;                        // onlyIfAbsent 为 true 或 此节点 value 值为 null 赋值当前元素 value 
                afterNodeAccess(e);
                return oldValue;    						// 返回原 value 值;未插入新元素,仅更新 key 对应的 value值,不更新修改次数
            }
        }
        ++modCount;                                         // 更新修改次数
        if (++size > threshold)                             // 判断长度是否达到扩容阈值                        
            resize();                                       // 进行扩容
        afterNodeInsertion(evict);
        return null;                                        // 新添加元素时返回原value 为 null
    }
    
Hash 冲突
  • HashMap 进行数据操作时出现 Hash 冲突(码值相同)处理
    • Hash 冲突时为保证程序正常在冲突位置将冲突的内容转为链表保存
      • key 相等:转为链表
        • 长度达到 8 且哈希表容量达到 64 时进行树化
      • key 相等:更新对应的 value 值并返回原 value 值
重复判断

利用 Object 提供的方法完成

  • 对象编码:public int hashCode()
  • 对象比较:public boolean equals(Object obj)
  1. 通过 hashCode() 计算哈希码

    • HashMap 中通过 hash() 方法对哈希码进行计算
  2. 匹配计算得到的哈希码

    • 都不相等:无重复元素直接添加
    • 有相等哈希码:通过 equals() 方法比较元素是否相等

Java 中真正重复元素判断处理就是利用 hashCode() equals()方法

扩容
扩容逻辑
  • HashMap 初始化容量 16
    • 默认常量:DEFAULT_INITIAL_CAPACITY = 16
  • 超过当前容量的扩充阈值时进行扩容
    • 加载因子
      • DEFAULT_LOAD_FACTORY = 0.75
      • 即超过当前容量的 75% 时进行扩容
    • 阈值:当前容量 * 阈值 = 12
      • 超过阈值进行自动扩容,二倍扩容
  • 添加元素后不满足树化所有条件时继续采用数组扩容机制
树化逻辑
  • JDK 1.8 后若一条链表元素个数到达默认值 8 且数据表 table 容量到默认值 64 会树化

    • TREEIFY_THRESHOLD = 8:默认树化阈值

    • MIN_TREEIFY_CAPACITY = 64:树化最小容量

    • 树化判断条件

      // 哈希表为空 或 表长度小于 64 时仍进行扩容
      if(tab == null || tab.length < MIN_TREEIFY_CAPACITY((64)){
          resize();
      }
      
  • 当单条链表元素个数小于等于 6 时取消树化

    • static final int UNTREEIFY_THRESHOLD = 6;					// 取消树化阈值,从红黑树转为链表
      
常用方法
方法列表
方法描述
public void clear()删除 hashMap 中的所有键/值对
public Object clone()复制一份 hashMap
public boolean isEmpty()判断 hashMap 是否为空
public int size()计算 hashMap 中键/值对的数量
public V put(K key, V value)将键/值对添加到 hashMap 中
public boolean containsValue(Object value)将所有键/值对添加到 hashMap 中
public V putIfAbsent(K key, V value)如果 hashMap 中不存在指定的键,则将指定的键/值对插入到 hashMap 中
public V remove(Object key)删除 hashMap 中指定键 key 的映射关系
containsKey()检查 hashMap 中是否存在指定的 key 对应的映射关系
public boolean containsValue(Object value)检查 hashMap 中是否存在指定的 value 对应的映射关系
public V replace(K key, V value)替换 hashMap 中是指定的 key 对应的 value
public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function)将 hashMap 中的所有映射关系替换成给定的函数所执行的结果(改变值,键不变)
public V get(Object key)获取指定 key 对应对 value
public V getOrDefault(Object key, V defaultValue)获取指定 key 对应对 value,如果找不到 key ,则返回设置的默认值
public void forEach(BiConsumer<? super K, ? super V> action)对 hashMap 中的每个映射执行指定的操作
public Set<Map.Entry<K,V>> entrySet()返回 hashMap 中所有映射项的集合集合视图
public Set<K> keySet()返回 hashMap 中所有 key 组成的集合视图
public Collection<V> values()返回 hashMap 中存在的所有 value 值
public V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction)key 存在时将原value 和 新value 进行计算,否则添加键值对
public V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)对 hashMap 中指定 key 的值进行重新计算
public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction)对 hashMap 中指定 key 的值进,如果不存在 key,则添加到 hasMap 中(存在无操作)
public V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)对 hashMap 中指定 key 的值进行重新计算,前提是该 key 存在于 hashMap 中
entrySet()
  • 将 Map 转为 Set 集合
    • 集合存放 Map.Entry 对象
  • 配合 for-each 循环遍历迭代 HashMap
// entrySet() 方法可以与 。

import java.util.HashMap;
import java.util.Map.Entry;
class Main {
    public static void main(String[] args) {
        HashMap<String, Integer> numbers = new HashMap<>();
        numbers.put("One", 1);
        numbers.put("Two", 2);
        numbers.put("Three", 3);

        // 访问 HashMap 中的每一个映射项
        System.out.print("Entries: ");
        // 遍历 Set 集合
        for(Entry<String, Integer> entry : numbers.entrySet()) {
            System.out.print(entry);
            System.out.print(", ");
        }
    }
}
运行结果:
Entries: One=1, Two=2, Three=3, 
merge()
  • 添加键值对
    • 当 key 已存在时将原 value 和 新value 进行自定义计算,返回计算后的 value 值
    • key 不存在时直接添加并返回 value
/**
* 指定 key 若存在对应的 value 则将 原value 和 新value 进行自定义计算
* 不存在 value 添加新的键值对
* BiFunction: 表示接受两个参数并产生结果的函数,Function的二元专业化。
*             是一个功能接口,其功能方法是apply(Object, Object)
* @param key key
* @param value value
* @param remappingFunction  重新计算值(如果存在)的函数
* @return 与指定键关联的新值,如果没有值与该键关联,则为 null
*/
public V merge(K key, V value,  BiFunction<? super V, ? super V, ? extends V> remappingFunction){}


import java.util.HashMap;
class Main {
    public static void main(String[] args) {
        HashMap<String, String> countries = new HashMap<>();
        countries.put("Washington", "America");
        // 合并 key为 Washington 的映射:使用 / 拼接两个 value
        String returnedValue = countries.merge("Washington", "USA", (oldValue, newValue) -> oldValue + "/" + newValue);
        System.out.println("Washington: " + returnedValue);				// Washington: America/USA
        // 输出更新后的HashMap
        System.out.println("Updated HashMap: " + countries);    		// Updated HashMap: {Washington=America/USA}
    }
}

LinkedHashMap

  • 继承HashMap,实现 Map 接口

    • 允许 null 元素,不同步
    • 基本操作(添加、包含和删除)恒定时间性能
    • 此类迭代器方法返回的迭代器是快速失败的
    • 此类 spliterator 方法返回的拆分器是 late-bindingfail-fast
  • 具有可预测的迭代顺序,维护一个双向链表

    • 该列表贯穿其所有条目,定义了迭代顺序
    • 保存键插入顺序
      • 将键重新插入到地图中,插入顺序不会受到影响
    • 由于维护链表性能可能略低于HashMap
      • 例外:LinkedHashMap 迭代时间与映射大小成正比,不管容量如何
        • HashMap 的迭代时间与其容量成正比
  • 特殊的constructor创建哈希映射

    • 迭代顺序是数据最后一次访问的顺序,从最近最少访问到最近访问(访问顺序)

      • 非常适合构建 LRU 缓存
    • // 此链接哈希映射的迭代排序方法:访问顺序为true ,插入顺序为false
      final boolean accessOrder;
      /**
      * 构造一个具有指定初始容量、加载因子和排序模式的空实例
      * @param  initialCapacity 初始容量
      * @param  loadFactor      负载系数
      * @param  accessOrder     排序模式:true 访问顺序,false:插入顺序
      * @throws IllegalArgumentException 如果初始容量为负或负载因子为非正
      */
      public LinkedHashMap(int initialCapacity,
                           float loadFactor,
                           boolean accessOrder) {
          super(initialCapacity, loadFactor);
          this.accessOrder = accessOrder;
      }
      
  • 初始容量和负载因子定义与HashMap完全相同

    • 初始容量过高的影响没有HashMap严重,因为此类的迭代时间不受容量的影响

TreeMap

  • 继承 AbstractMap,实现 NavigableMap 接口

    • 基于红黑树的NavigableMap实现
    • 此实现不同步
    • Hashtable是原始的java.util的一部分
      • Dictionary具体实现
    • iterator方法返回的迭代器是快速失败的
  • 根据键的自然顺序排序,或者由创建时提供的Comparator比较器排序

    • 取决于使用的构造函数
    • 保存数据时已经进行了大小比较
  • 树形映射维护的顺序,像任何排序映射一样

    • 此排序映射要正确实现Map接口,则必须与equals保持一致

    • 因为Map接口根据equals操作定义,但排序的映射使用 compareTocompare 方法对所有键比较

Hashtable

  • 从 JDK 1.0 提供,最早的一批动态数组实现类
    • Java 2 重构 Hashtable 实现 Map 接口
      • 因此Hashtable集成到了集合框架中

      • HashMap类相似,但支持同步

    • 继承了 Dictionary<K, V> 类,实现了 Map 接口
  • keyvalue 都不允许为 null
    • 否则出现 NullPointerException 异常
  • 其他使用方法和 HashMap 相同
HashMap 与 Hashtable 区别
  • HashMap 方法为异步方法,属于线程不安全,允许保存 null 值
  • Hashtable方法都为同步方法,线程安全,key、value都不允许为null

Map 遍历

Iterator
  • 集合标准输出:Iterator 接口

    • Map 集合没有直接返回 Iterator 接口对象的方法
  • Map 集合可以迭代输出的,但常用于实现数据通过 key 查找

  • CollectionMap 存储结构

    • Map 实际保存的是 Map.Entry 接口对象

      • 接口对象中保存 keyvalue
      • 实际上 Map 依旧是单值保存
    • Map 中提供方法将全部的 Map 转为 Set 集合

      • public Set<Map.Entry<K,V>> entrySet()
  • Map 中使用 Iterator 迭代遍历

    1. 通过 entrySet() 方法将 Map 集合转为 Set 集合

    2. iterator() 方法获取集合的 Iterator 接口实例

      • 或直接 foreach 遍历集合
    3. 利用 Iterator 进行迭代输出获取每一组的 Map.Entry 对象

      • 通过 getKey()getValue() 方法获取数据
示例
public class Test{
    public static void main(String[] args) {
        Map<String, String> map = new HashMap<String, String>();
        map.put("1", "value1");
        map.put("2", "value2");
        map.put("3", "value3");

        // 1、keySet() + for-each 遍历
        for (String key : map.keySet()) {										// 获取全部 key 转为 set 集合,通过 增强for 遍历 key
            System.out.println("key= "+ key + " and value= " + map.get(key));	// 通过 key 获取对应的 value
        }

        // 2、entrySet() + Interable 遍历
        Iterator<Map.Entry<String, String>> it = map.entrySet().iterator();  // 获取 Map 的全部 Entry 对象并获取 Iterator 对象
        while (it.hasNext()) {												 // 判断元素存在时进行下一步操作
            Map.Entry<String, String> entry = it.next();					 // 获取 Map.Entry 对象
            System.out.println("key= " + entry.getKey() + ",value= " + entry.getValue());	// 通过 Entry 对象获取 K、V
        }

        //	3、entrySet() + for-each 遍历;推荐,尤其是容量大时
        for (Map.Entry<String, String> entry : map.entrySet()) {			 				 // 将 Map 转为 Set 集合,增强for 遍历
            System.out.println("key= " + entry.getKey() + ",value= " + entry.getValue());	// 获取 Entry 中的 K、V
        }

        // 4、values() 遍历,获取 Map 中所有 values 值,无法获取 key
        for (String v : map.values()) {
            System.out.println("value= " + v);
        }
    }
}
  • 遍历 HashMap
    • entrySet():将keyvalue 对象全部取出,性能消耗可以预计
    • keySet():根据 key 值查询对应的 value
      • key 值是简单的结构(如 1,2,3…):性能消耗比 entrySet() 方法低
        • 随着 key 值得复杂度提高,entrySet()性能会更好
  • 只遍历 key 的时候使用 keySet()
  • 只遍历 value 的是使用 values()
  • 遍历 key-value 的时候使用 entrySet() 是比较合理的选择
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值