文章目录
Map集合概述
1. Map与Collection
Collection中的集合,元素是孤立存在的(理解为单身),向集合中存储元素采用一个个元素的方式存储。
Map中的集合,元素是成对存在的(理解为夫妻)。每个元素由键与值两部分组成,通过键可以找对所对应的值。
Collection中的集合称为单列集合,Map中的集合称为双列集合。
需要注意的是,Map中的集合不能包含重复的键,值可以重复;每个键只能对应一个值。
Map中常用的集合为HashMap集合、LinkedHashMap集合。
现实生活中,我们常会看到这样的一种集合:IP地址与主机名,身份证号与个人,系统用户名与系统用户对象等,这种一一对应的关系,就叫做映射。Java提供了专门的集合类用来存放这种对象关系的对象,即 java.util.Map 接口。
我们通过查看 Map 接口描述,发现 Map 接口下的集合与 Collection 接口下的集合,它们存储数据的形式不同,如下图。
2.Map集合的概述与特点
2. 1 Map概念
Map 接口是 Java 集合框架中用于表示键值对的数据结构接口。它提供了一种通过键来查找值的方式,并且键和值都可以是任意的 Java 对象。
在开始之前,让我们先了解一些基本的Map概念:
- 键(Key):每个键必须是唯一的,用于查找和访问值。
- 值(Value):与键相关联的数据。
- 键值对(Entry):表示键和值的组合。
- 映射(Mapping):键和值之间的关系。
2.2 Map集合的特点
- 特点
- Map不是collection的子接口或者实现类。
Map接口与Collection接口并列存在
,用于保存具有映射关系的数据,键值对的结构:key-value
- Map 中的 key 和 value 都可以是
任何引用类型的数据
,会封装到HashMap$Node·
对象中 - Map中的健值对使用Entry类维护,Map的每个 Entry 都持有两个对象,也就是一个键(key)一个值(value)。
key是唯一的,无序且不能重复的
,当key重复时,不会创建多个键值对,只会进行覆盖
,key 和 value 之间存在单向一对一
关系,即通过指定的 key 总能找到 唯一的、确定的 value-
- Map 中的
key 用Set来存放
,无序且不允许重复,即同一个 Map 对象所对应 的类,须重写hashCode()和equals()方法。value可以重复
- Map 中的
- Map 的
key可以为null, value也可以为null
,注意key 为null, 只能有一个
,value 为null ,可以多个.
-
- TreeMap也通过Comparator或者Comparable维护了一个排序顺序。
- Map接口最流行的几个实现类是 HashMap、LinkedHashMap、Hashtable 和 TreeMap。(HashMap、TreeMap最常用)
注意:Map接口中的集合都有两个泛型变量<K,V>,在使用时,要为两个泛型变量赋予数据类型。
两个泛型变量<K,V>的数据类型可以相同,也可以不同。
- 存储结构的理解:
Map 中的 key 和 value 都可以是
任何引用类型的数据
,会封装到HashMap$Node·
对象中
Map中的key:无序的、不可重复的,使用Set存储所的key —> key所在的类要重写equals()和hashCode() (以HashMap为例)
Map中的value:无序的、可重复的,使用Collection存储所的value —>value所在的类要重写equals()
一个键值对:key-value构成了一个Entry对象。
Map中的entry:无序的、不可重复的,使用Set存储所的entry
图解分析:
2.3 Map集合架构图
通过查看Map接口描述,发现Map接口下的集合与Collection接口下的集合,它们存储数据的形式不同,如下图。
map接口与Collection接口没有关联,因为其特殊的数据存储结构导致不能使用Collection接口的方法,只能参照Collection接口定义自己的抽象方法并由子类实现
map接口:键值对集合接口 存储key-value形式的数据并提供相应方法
通过查看Map接口描述,看到Map有多个子类,这里我们主要讲解常用的HashMap集合、LinkedHashMap集合。
HashMap<K,V>:存储数据采用的哈希表结构,元素的存取顺序不能保证一致。由于要保证键的唯一、不重复,需要重写键的hashCode()方法、equals()方法。
LinkedHashMap<K,V>:HashMap下有个子类LinkedHashMap,存储数据采用的哈希表结构+链表结构。通过链表结构可以保证元素的存取顺序一致;通过哈希表结构可以保证的键的唯一、不重复,需要重写键的hashCode()方法、equals()方法。
2.4 Map常用的子类
- HashMap:基于哈希表的实现,提供了快速的插入和查找性能。但不保证元素的顺序。
- TreeMap:基于红黑树的实现,元素按键的自然顺序或自定义顺序排序。
- LinkedHashMap:基于哈希表和双向链表的实现,保持了元素的插入顺序。
- HashTable:早期的哈希表实现,线程安全,不推荐使用。
- ConcurrentHashMap:线程安全的哈希表实现,适用于多线程环境。
双列集合框架:Map
(1).常用实现类结构
|----Map:双列数据,存储key-value对的数据 ---类似于高中的函数:y = f(x)
* |----HashMap:作为Map的主要实现类;线程不安全的,效率高;存储null的key和value
* |----LinkedHashMap:保证在遍历map元素时,可以照添加的顺序实现遍历。
* 原因:在原的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素。
* 对于频繁的遍历操作,此类执行效率高于HashMap。
* |----TreeMap:保证照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或定制排序
* 底层使用红黑树
* |----Hashtable:作为古老的实现类;线程安全的,效率低;不能存储null的key和value
* |----Properties:常用来处理配置文件。key和value都是String类型
*
*
* HashMap的底层:数组+链表 (jdk7及之前)
* 数组+链表+红黑树 (jdk 8)
(2) HashMap在jdk7中实现原理:
HashMap map = new HashMap():
* 在实例化以后,底层创建了长度是16的一维数组Entry[] table。
* ...可能已经执行过多次put...
* map.put(key1,value1):
* 首先,调用key1所在类的hashCode()计算key1哈希值,此哈希值经过某种算法计算以后,得到在Entry数组中的存放位置。
* 如果此位置上的数据为空,此时的key1-value1添加成功。 ----情况1
* 如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据(以链表形式存在)),
* 比较key1和已经存在的一个或多个数据的哈希值:
* 如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功。----情况2
* 如果key1的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同,
* 继续比较:调用key1所在类的equals(key2)方法,比较:
* 如果equals()返回false:此时key1-value1添加成功。----情况3
* 如果equals()返回true:使用value1替换value2。
*
* 补充:关于情况2和情况3:此时key1-value1和原来的数据以链表的方式存储。
*
* 在不断的添加过程中,会涉及到扩容问题,当超出临界值(且要存放的位置非空)时,扩容。
* 默认的扩容方式:扩容为原来容量的2倍,并将原的数据复制过来。
(3) HashMap在jdk8中相较于jdk7在底层实现方面的不同:
1. new HashMap():底层没创建一个长度为16的数组
2. jdk 8底层的数组是:Node[],而非Entry[]
3. 首次调用put()方法时,底层创建长度为16的数组
4. jdk7底层结构只:数组+链表。jdk8中底层结构:数组+链表+红黑树。
4.1 形成链表时,七上八下(jdk7:新的元素指向旧的元素。jdk8:旧的元素指向新的元素)
4.2 当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 8 且当前数组的长度 > 64时,
此时此索引位置上的所数据改为使用红黑树存储。
(4) HashMap底层典型属性的属性的说明:
DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16
DEFAULT_LOAD_FACTOR:HashMap的默认加载因子:0.75
threshold:扩容的临界值,=容量*填充因子:16 * 0.75 => 12
TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树:8
MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量:64
(5) LinkedHashMap的底层实现原理(了解)
LinkedHashMap底层使用的结构与HashMap相同,因为LinkedHashMap继承于HashMap.
区别就在于:LinkedHashMap内部提供了Entry,替换HashMap中的Node.
(6) TreeMap的使用
向TreeMap中添加key-value,要求key必须是由同一个类创建的对象
因为要照key进行排序:自然排序 、定制排序
2.4 Map源码
Map源码
以下按照空格分为:获取集合大小和判空、增-删-获取(默认获取)-查存在-清空、获取键集/值集/键值集、从Object继承的方法、replace方法、Java8新方法
public interface Map<K, V> {
int size();
boolean isEmpty();
V put(K key, V value);
void putAll(Map<? extends K, ? extends V> m);
default V putIfAbsent(K key, V value) {
V v = get(key);
if (v == null) {
v = put(key, value);
}
return v;
}
V remove(Object key);
default boolean remove(Object key, Object value) {
Object curValue = get(key);
if (!Objects.equals(curValue, value) || (curValue == null && !containsKey(key))) {
return false;
}
remove(key);
return true;
}
V get(Object key);
default V getOrDefault(Object key, V defaultValue) {
V v;
return (((v = get(key)) != null) || containsKey(key)) ? v : defaultValue;
}
boolean containsKey(Object key);
boolean containsValue(Object value);
void clear();
Set<K> keySet();
Collection<V> values();
Set<Map.Entry<K, V>> entrySet();
boolean equals(Object o);
int hashCode();
default V replace(K key, V value) {
V curValue;
if (((curValue = get(key)) != null) || containsKey(key)) {
curValue = put(key, value);
}
return curValue;
}
default boolean replace(K key, V oldValue, V newValue) {
Object curValue = get(key);
if (!Objects.equals(curValue, oldValue) || (curValue == null && !containsKey(key))) {
return false;
}
put(key, newValue);
return true;
}
default void forEach(BiConsumer<? super K, ? super V> action) {
Objects.requireNonNull(action);
for (Map.Entry<K, V> entry : entrySet()) {
K k;
V v;
try {
k = entry.getKey();
v = entry.getValue();
} catch (IllegalStateException ise) {
throw new ConcurrentModificationException(ise);
}
action.accept(k, v);
}
}
default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
Objects.requireNonNull(function);
for (Map.Entry<K, V> entry : entrySet()) {
K k;
V v;
try {
k = entry.getKey();
v = entry.getValue();
} catch (IllegalStateException ise) {
throw new ConcurrentModificationException(ise);
}
v = function.apply(k, v);
try {
entry.setValue(v);
} catch (IllegalStateException ise) {
throw new ConcurrentModificationException(ise);
}
}
}
default V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
Objects.requireNonNull(mappingFunction);
V v;
if ((v = get(key)) == null) {
V newValue;
if ((newValue = mappingFunction.apply(key)) !
put(key, newValue);
return newValue;
}
}
return v;
}
default V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
V oldValue;
if ((oldValue = get(key)) != null) {
V newValue = remappingFunction.apply(key, oldValue);
if (newValue != null) {
put(key, newValue);
return newValue;
} else {
remove(key);
return null;
}
} else {
return null;
}
}
default V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
V oldValue = get(key);
V newValue = remappingFunction.apply(key, oldValue);