文章目录
Map
-
二元偶对象保存的最大父接口,独立父接口
-
存储二元偶对象
public interface Map<K, V>
- 保存形式:
key=value
-
接口对象实例化时要指定
key
、value
的数据类型- 任意引用类型
- 保存具有映射关系的数据
- key 不允许重复,value 可以重复
- key 只唯一对应一个 value
- 不同 key 的 value 可以相同
- key 重复时会将 value 值替换
-
-
自定义
key
的类中必须重写hashCode()
和equals()
方法-
在进行数据存储时使用
key
计算hash
值得到存储位置- 数据保存时要判断重复会使用
equals()
- 数据保存时要判断重复会使用
-
获取数据时同样将使用
key
计算hash
值获取元素位置- 通过
equals()
进行数据比对
- 通过
-
实际常用类型就是
String
、Long
、Integer
等- 尽量使用系统类
-
-
核心意义:需要通过
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
器排序
- 或在创建时提供的
- 此顺序在遍历已排序地图的集合视图
- 由
entrySet
、keySet
和values
方法返回时反映出来
- 由
- 插入排序映射的所有键都必须实现
Comparable
接口- 或被指定的比较器接受
- 所有键必须是相互可比较的
- 有序映射实现Map接口:有序映射维护的排序(无论是否提供显式比较器)必须与
equals
一致- 因为
Map
接口根据equals
操作定义 - 但排序后的映射使用其
compareTo
或compare
方法执行键比较
- 因为
- 通用的排序地图实现类应提供四个标准构造函数
- 无法强制执行此建议
- 无参构造:创建空的排序映射,根据键的自然顺序排序
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
映射的集合视图 - 集合不应该支持
add
或remove
方法 - 迭代器也不应该支持
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
节点
- 存储数据时将数据保存在
-
key
、value
封装在Map.Entry
接口子类Node
public static interface Map.Entry<K, V>{}
-
利用
Node
节点的只有 链表 和 二叉树- 时间复杂度为
n
和log(n)
- 时间复杂度为
-
-
接口操作方法
- 获取 key:
public K getKey()
- 获取 value:
public V getValue()
- 获取 key:
-
在 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
对象使用
- 大多将 key、value 包装为
-
添加元素
执行流程
-
获取元素哈希值
- 通过
hashCode
方法
- 通过
-
对哈希值进行计算得出元素在哈希表中存放的位置
-
static final int hash(Object key) { // 哈希su int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); // key 的 hash值 按位异或 自身无符号右移16位 }
-
-
该位置没有元素,直接存放
- 判断哈希表容量是否达到阈值
- 满足条件后进行扩容
- 判断哈希表容量是否达到阈值
-
该位置已存在元素,进行相等判断(通过
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 值
- key 相等:转为链表
重复判断
利用 Object
提供的方法完成
- 对象编码:
public int hashCode()
- 对象比较:
public boolean equals(Object obj)
-
通过
hashCode()
计算哈希码HashMap
中通过hash()
方法对哈希码进行计算
-
匹配计算得到的哈希码
- 都不相等:无重复元素直接添加
- 有相等哈希码:通过
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-binding
,fail-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
操作定义,但排序的映射使用compareTo
或compare
方法对所有键比较
-
Hashtable
- 从 JDK 1.0 提供,最早的一批动态数组实现类
Java 2
重构Hashtable
实现Map
接口-
因此
Hashtable
集成到了集合框架中 -
和
HashMap
类相似,但支持同步
-
- 继承了
Dictionary<K, V>
类,实现了Map
接口
key
、value
都不允许为 null- 否则出现
NullPointerException
异常
- 否则出现
- 其他使用方法和
HashMap
相同
HashMap 与 Hashtable 区别
HashMap
方法为异步方法,属于线程不安全,允许保存 null 值Hashtable
方法都为同步方法,线程安全,key、value都不允许为null
Map 遍历
Iterator
-
集合标准输出:
Iterator
接口Map
集合没有直接返回Iterator
接口对象的方法
-
Map
集合可以迭代输出的,但常用于实现数据通过key
查找 -
Collection
和Map
存储结构-
Map
实际保存的是Map.Entry
接口对象- 接口对象中保存
key
、value
- 即 实际上 Map 依旧是单值保存
- 接口对象中保存
-
在
Map
中提供方法将全部的Map
转为Set
集合public Set<Map.Entry<K,V>> entrySet()
-
-
Map
中使用Iterator
迭代遍历-
通过
entrySet()
方法将Map
集合转为Set
集合 -
iterator()
方法获取集合的Iterator
接口实例- 或直接
foreach
遍历集合
- 或直接
-
利用
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()
:将key
和value
对象全部取出,性能消耗可以预计keySet()
:根据key
值查询对应的value
值key
值是简单的结构(如 1,2,3…):性能消耗比entrySet()
方法低- 随着
key
值得复杂度提高,entrySet()
性能会更好
- 随着
- 只遍历
key
的时候使用keySet()
- 只遍历
value
的是使用values()
- 遍历
key-value
的时候使用entrySet()
是比较合理的选择