java中的Map集合是一个由键-值(key-value)组合而成的,Map作为一个接口,jdk解释为将键映射到值的对象。一个映射不能包含重复的键;每个键最多只能映射到一个值。
Map.entry是其内部的一个接口,由于Map没有实现Iterable接口,就不能使用iterator来遍历集合,所以通过entrySet方法转换为Set,这个Set里存放每个元素都是一个 Map.Entry< K , V >再来遍历这个Set即可。
一.HashMap介绍
HashMap 是基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作,并允许使用 null 值和 null 键。(除了非同步和允许使用 null 之外,HashMap 类与 Hashtable 大致相同。)此类不保证映射的顺序,特别是它不保证该顺序恒久不变。
public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
可以看出HashMap继承了AbstractMap,实现了Map,Cloneable,Serializable接口,这里没有实现同步,所以它是线程不安全的,这里可以在创建是通过Collections的集合工具类方法来实现同步。
Map m = Collections.synchronizedMap(new HashMap(...));
HashMap的构造器:
// 默认构造函数。
HashMap()
// 指定“容量大小”的构造函数
HashMap(int capacity)
// 指定“容量大小”和“加载因子”的构造函数
HashMap(int capacity, float loadFactor){
if (initialCapacity < 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;
//threshold 临界值 当实际大小超过临界值时,会进行扩容threshold = 加载因子*容量
threshold = initialCapacity;
init();
}
// 包含“子Map”的构造函数
HashMap(Map<? extends K, ? extends V> map){
inflateTable(threshold);
putAllForCreate(m);
}
二.HashMap结构
hashMap是一个散列表,通过“拉链法”来解决hash冲突
它是基于数组和链表实现的,从上面构造器的方法可以看到容量大小(capacity)和加载因子(loadFactor)两个参数对hashMap非常重要,容量指的是哈希表中桶的数量,初始容量是创建哈希表时的容量,加载因子是哈希表在其容量自动增加之前可以达到多满的一种尺度,它衡量的是一个散列表的空间的使用程度,负载因子越大表示散列表的装填程度越高,反之愈小。对于使用链表法的散列表来说,查找一个元素的平均时间是O(1+a),因此如果负载因子越大,对空间的利用更充分,然而后果是查找效率的降低;如果负载因子太小,那么散列表的数据将过于稀疏,对空间造成严重浪费。系统默认负载因子为0.75,这个值应该是比较均衡的值。若哈希表中元素超过了加载因子与当前容量的乘积时,则要对该哈希表进行 rehash 操作(即重建内部数据结构),从而哈希表将具有大约两倍的桶数。
在一个长度为16的数组中,每个数组的元素都是存储一条链表的头节点,hashmap通过对元素的key的hash值对数组的长度取模来得到这个元素应该位于数组的那个位置,hash(key)% len,比如12%16=12,28%16=12,这样12和28都会位于数组下标为12的链表中。那这个链表又如何实现的呢?
HashMap数据存储数组: