一、Map 集合概述
1. 什么是 Map?
Map 是一种键-值对(key-value)集合,Map 集合中的每一个元素都包含一个键对象和一个值对象。其中,键对象不允许重复,而值对象可以重复,并且值对象还可以是 Map 类型的,就像数组中的元素还可以是数组一样。
如果需要存储几百万个学生,而且经常需要使用学号来搜索某个学生,那么这个需求有效的数据结构就是Map。
![1629788940349](https://gitee.com/coder-kaho/myNotes/raw/master/Java_Container/images/1629788940349.png)
2. Map 和 Collection 的区别
-
Map 与 Collection 在集合框架中属并列存在。
-
Map 存储的是键值对,而 Collection 只存储值(也就是Collection是单列集合, Map 是双列集合)
-
Map 存储元素使用 put 方法,Collection 使用 add 方法。
-
Map 集合没有直接迭代元素的方法,而是先转成 Set 集合(entrySet),再通过迭代获取元素
-
Map 集合中键要保证唯一性,值是可以重复的。
3. Map 的结构体系
![1630048100635](https://gitee.com/coder-kaho/myNotes/raw/master/Java_Container/images/1630048100635.png)
4. Map 的基础功能
5. Map 集合常用子类
-
HashMap
- 无序。
- 底层是 散列表(数组+链表)+红黑树 数据结构。
- 线程是不同步的,可以存入 null 键,null 值。
- 要保证键的唯一性,需要覆盖 hashCode 方法和 equals 方法。
-
LinkedHashMap
- 底层是 散列表+双向链表。可以 Map 集合进行增删提高效率。
- 插入顺序是有序的。
- 允许为 null,不同步。
-
HashTable
- 底层是 散列表。
- 同步(线程安全),但效率较低。
- key 和 value 都不允许为 null。
-
TreeMap
- 底层是 红黑树。方法的复杂度大都是 log(n)。
- 非同步。
- TreeMap 实现了 NavigableMap 接口,而 NavigableMap 接口继承了 SortedMap 接口,致使 TreeMap 是有序的。
- 使用 Comparator 或者 Comparable 来比较 key 是否相等与排序问题。
-
ConcurrentHashMap
- 底层是 散列表+红黑树。
- 同步(线程安全),支持高并发的访问和更新。
- key 和 value 都不允许为 null。
- 检索操作不用加锁,get 方法是非阻塞的。
二、HashMap
类图:
由类注释可以归纳出 HashMap 的特点:
- 允许为 null, 但 null 作为键只能有一个,null 作为值可以有多个
- 非同步,无序
- 底层是 散列表+(红黑树)
- 初始容量和装载因子都会影响迭代性能
JDK1.8 之前 HashMap 由 数组+链表 组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突)。 JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间。
HashMap
默认的初始化大小为 16。之后每次扩充,容量变为原来的 2 倍。并且, HashMap
总是使用 2 的幂作为哈希表的大小。
1. HashMap 的属性
成员属性有如下几个:
属性详解:
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
// 序列号
private static final long serialVersionUID = 362498820763181265L;
// table数组默认的初始容量是16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
// table数组最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
// 默认的填充因子
/**
*默认的负载因子是0.75f,也就是75% 负载因子的作用就是计算扩容阈值用,比如说使用
*无参构造方法创建的HashMap 对象,他初始长度默认是16 阈值 = 当前长度 * 0.75 就
*能算出阈值,当当前长度大于等于阈值的时候HashMap就会进行自动扩容
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 当桶(bucket)上的结点数大于这个值时会转成红黑树
static final int TREEIFY_THRESHOLD = 8;
// 当桶(bucket)上的结点数小于这个值时树转链表
static final int UNTREEIFY_THRESHOLD = 6;
// 桶中结构转化为红黑树对应的table的最小大小
static final int MIN_TREEIFY_CAPACITY = 64;
// 存储元素的数组,总是2的幂次倍
transient Node<k,v>[] table;
// 存放具体元素的集
transient Set<map.entry<k,v>> entrySet;
// 存放元素的个数,注意这个不等于数组的长度。
transient int size;
// 每次扩容和更改map结构的计数器
transient int modCount;
// 临界值 当实际大小(容量*填充因子)超过临界值时,会进行扩容
/**
* threshold就是在此Load factor和length(数组长度)对应下允许的最大元素数目,超过这个数目就重新resize(扩容),扩容后的HashMap容量是之前容量的两倍。
*/
int threshold;
// 加载因子
final float loadFactor;
}
HashMap 中还有一个内部类 Node,用于存放散列表下的链表结构中的节点元素:
2. HashMap 的构造器
HashMap(int, float)
/**
* Constructs an empty <tt>HashMap</tt> with the specified initial
* capacity and load factor.
* 用确定的初始化容量和装载因子构造一个HashMap
*
* @param initialCapacity the initial capacity 初始化容量
* @param loadFactor the load factor 装载因子
* @throws IllegalArgumentException if the initial capacity is negative
* or the load factor is nonpositive
*/
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0) //初始容量不能为0
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY) //容量大于最大允许容量(2^30)
initialCapacity = MAXIMUM_CAPACITY; //赋为最大允许容量
if (loadFactor <= 0 || Float.isNaN(loadFactor)) //装载因子不能非法
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor; //赋为默认装载因子
this.threshold = tableSizeFor(initialCapacity);
}
其中最后一行调用的 tableSizeFor()
方法如下:
为啥是将2的整数幂的数赋给threshold?
threshold
这个成员变量是阈值,决定了是否要将散列表再散列。它的值应该是: capacity * loadfactor
其实这里仅仅是一个初始化,当创建哈希表的时候,它会重新赋值的:
另外几个构造器:
/**
* Constructs an empty <tt>HashMap</tt> with the specified initial
* capacity and the default load factor (0.75).
* 指定容量大小的构造函数,装载因子为默认值0.75
* @param initialCapacity the initial capacity.
* @throws IllegalArgumentException if the initial capacity is negative.
*/
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
/**
* Constructs an empty <tt>HashMap</tt> with the default initial capacity
* (16) and the default load factor (0.75).
* 默认构造函数,容量为16,装载因子为0.75
*/
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
/**
* Constructs a new <tt>HashMap</tt> with the same mappings as the
* specified <tt>Map</tt>. The <tt>HashMap</tt> is created with
* default load factor (0.75) and an initial capacity sufficient to
* hold the mappings in the specified <tt>Map</tt>.
* 包含另一个“Map”的构造函数
* @param m the map whose mappings are to be placed in this map
* @throws NullPointerException if the specified map is null
*/
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}
3. put 方法
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
//以key计算哈希值,传入key、value还有两个参数
}
HashMap 只提供了 put
用于添加元素,putVal
方法只是给 put
方法调用的一个方法,并没有提供给用户使用。
对 putVal 方法添加元素的分析如下&#