一、Map家族
其中实线箭头是继承,虚线箭头是接口实现。
二、HashMap操作元素
数组和链表是两种最基本的线性数据结构,在java中的代表就是java数组和引用变量,HashMap就是用数组和链表来存储数据的,HashMap中默认有一个长度为16的数组,数组的每个元素中存储一个链表的头结点,具体如下图:
当执行Map map=new HashMap(); 时,map实例中初始化一个类型为Entry的空数组。
假如现在HashMap中的数据如上图所示,当再次向HashMap中put元素的时候,比如执行map.put(“key1”,”value1”); 会先计算出key1的hashCode,然后根据hashCode来计算存放该键值对(Entry)的数组的坐标位置,然后判断该位置是否已经存在元素,如果不存在,直接把新的元素保存在数组的对应位置即可,如果已经存在元素,判断存在元素与新元素key值是否相等,相等则用新的value替换就的value,否则需要把新元素的next指向旧的元素(当前链表的表头),然后把新元素保存在数组的对应位置,相当于新的元素来了之后插在最前面,把链表中的所有元素都向后挪一个位置,上图数组坐标为0的链表中Entry1插入的时候,就是把它自己的next指向Entry0,然后把自己存在数组中。
当根据key值获取HashMap中的元素时,会先计算key得hashCode,然后根据hashCode找出该元素存储链表所在的数组下标,遍历链表,找到key值相同的元素返回,这也是为什么HashMap在多次put相同key值不同value值时,之前的value会被最新的覆盖。
三、源码分析
1、jdk1.7
2、jdk1.8
- 常量
//支持可序列化
private static final long serialVersionUID = 362498820763181265L;
//默认的初始化容量16,使用位移,2进制,因为二进制效率更高
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
//最大容量2的30次方
static final int MAXIMUM_CAPACITY = 1 << 30;
//加载因子
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;
-
HashMap有2个参数影响它的性能:初始容量和加载因子。
加载因子:哈希表在其容量自动增加之前可以达到多满的一种尺度,它衡量的是一个散列表的空间的使用程度,加载因子越大表示散列表的装填程度越高,反之愈小。
表示当map集合中存储的元素个数达到当前数组大小的75%则需要进行扩容。
-
源码中默认的负载因子是设置成(0.75)在时间和空间成本之间提供了一个很好的权衡。更高的值减少了空间开销,但是增加了查找成本。
- 构造函数
- HashMap():构建一个空的HashMap,默认加载因子0.75,其他的属性默认
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
由于HashMap采用的是懒加载机制,也就是说在你执行new HashMap()的时候,构造方法并没有在构造出HashMap实例的同时也把HashMap实例里所需的数组给初始化出来,只有在第一次需要用到这个数组的时候才会去初始化它,就是在你往HashMap里面put元素的时候。
- HashMap(int):构建指定初始化容量的,默认加载因子0.75的HashMap
public HashMap(int initialCapacity) {
//此处通过把第二个参数负载因子使用默认值0.75f,然后调用有两个参数的构造方法
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
- HashMap(int, float):构建指定的初始化容量、指定的加载因子
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))
//如果负载因子小于等于0或者不是数字,则抛出异常
throw new IllegalArgumentException("Illegal load factor: " + loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}
- HashMap(Map<? extends K, ? extends V> m):有一个Map类型的参数的构造方法
public HashMap(Map<? extends K, ? extends V> m) {
//将默认的负载因子赋值给成员变量loadFactor
this.loadFactor = DEFAULT_LOAD_FACTOR;
//这个方法调用了HashMap的resize()扩容方法和putVal()存入数据方法
putMapEntries(m, false);
}
- 内部结构
- transient Node<K,V>[] table:单向链表的一个结点,是HashMap的一个静态的内部类
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;//该key的hashCode
final K key;//存储元素的key值
V value;//存储元素的value值
Node<K,V> next;//下一个Node节点的“指针”
Node(int hash, K key, V value, Node<K,V> next) {//Node构造方法
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }
//Node的哈希方法,是将键与值的哈希取一次异或,作为自身的哈希值
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
//首先判断参数中的对象o和自身地址是否相同,之后,再判断o是否是Map.Entry接口的实现类,若是的话,判断键与值是否分别相等,若相等,则返回true。
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}
当链表长度达到阀值,链表转化为红黑树。此时数组和红黑树中的元素是TreeNode<K,V>