java集合详解(双列集合Map)——全网最通俗易懂的讲解

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集合的特点

  1. 特点
  • 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 的key可以为null, value也可以为null,注意key 为null, 只能有一个,value 为null ,可以多个.
    • TreeMap也通过Comparator或者Comparable维护了一个排序顺序。
  • Map接口最流行的几个实现类是 HashMap、LinkedHashMap、Hashtable 和 TreeMap。(HashMap、TreeMap最常用)
注意:Map接口中的集合都有两个泛型变量<K,V>,在使用时,要为两个泛型变量赋予数据类型。
两个泛型变量<K,V>的数据类型可以相同,也可以不同。
  1. 存储结构的理解:

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表容量:645) 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);
    	
  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值