基本介绍

1. 用于存储Key-Value键值对的集合(每一个键值对也叫做一个Entry)(无顺序)。

2. 根据键的hashCode值存储数据,大多数情况下可以直接定位到它的值。

3. 键key为null的记录至多只允许一条,值value为null的记录可以有多条。

4. 非线程安全。

5. HashMap是由数组+链表+红黑树(JDK1.8后增加了红黑树部分,链表长度超过阈值(8)时会将链表转换为红黑树)实现的。

6. HashMap与Hashtable区别:
Hashtable是synchronized的。
Hashtable不可以接受为null的键值(key)和值(value)。

数组 + 链表 + 红黑树

1. 数组是用来确定桶的位置(寻址容易,插入和删除困难):
数组的查找效率比LinkedList大。
HashMap中数组扩容刚好是2的次幂,在做取模运算的效率高,而ArrayList的扩容机制是1.5倍扩容。 2. 链表是用来解决hash冲突问题,当出现hash值一样的情形,就在数组上的对应位置形成一条链表(寻址困难,插入和删除容易)(链地址法)。 3. 链表转为红黑树:
当元素小于8个当时候,此时做查询操作,链表结构已经能保证查询性能。
当元素大于8个的时候,此时需要红黑树来加快查询速度,但是新增节点的效率会变慢。 4. 当链表转为红黑树后,元素个数是6会退化为链表:
中间有个差值7可以防止链表和树之间频繁的转换。

key必须是不变的类

防止key发生变化,导致无法取出value。

自定义不可变类:

    不提供改变成员变量的方法,包括setter

    在getter方法中,不要直接返回对象本身,而是克隆对象,并返回对象的拷贝

public final class Immutable {

    private final int[] myArray;    //防止直接修改myArray内容

    public Immutable(int[] array) {
this.myArray = array.clone(); //如果直接将array赋值给myArray,是引用变量,array的内容变化则myArray的内容也会变化
} public int[] getMyArray() {
return myArray.clone();
}
}

HashMap的扩容机制

生成hash:

    异或运算(减少了碰撞的几率):(h = key.hashCode()) ^ (h >>> 16)

    原来的 hashCode:1111 1111 1111 1111 0100 1100 0000 1010
移位后的hashCode:0000 0000 0000 0000 1111 1111 1111 1111
进行异或运算结果:1111 1111 1111 1111 1011 0011 1111 0101 这样做的好处是,可以将hashcode高位和低位的值进行混合做异或运算,而且混合后,低位的信息中加入了高位的信息,
这样高位的信息被变相的保留了下来。掺杂的元素多了,那么生成的hash值的随机性会增大。 HashMap的扩容是申请一个容量为原数组大小两倍的新数组:
遍历旧数组,重新计算每个元素的索引位置,并复制到新数组中。
因为HashMap的哈希桶数组大小总是为2的幂次方,So重新计算后的索引位置要么在原来位置不变,要么就是“原位置+旧数组长度”。 扩充HashMap:
复制数组元素及确定索引位置时不需要重新计算hash值,
只需要判断原来的hash值新增的那个bit是1,还是0:
若为0,则索引未改变;
若为1,则索引变为“原索引+旧数组长度” threshold和loadFactor两个属性决定着是否扩容:
threshold=Length*loadFactor,Length表示table数组的长度(默认值为16),loadFactor为负载因子(默认值为0.75)
阀值threshold表示当table数组中存储的元素个数超过该阀值时,即需要扩容。

源码分析

public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {

    private static final long serialVersionUID = 362498820763181265L;

    //默认的初始容量是16,必须是2的幂。
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 //最大容量(必须是2的幂且小于2的30次方,传入容量过大将被这个值替换)
static final int MAXIMUM_CAPACITY = 1 << 30; //默认加载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f; //阈值:用来确定何时链表转为红黑树
static final int TREEIFY_THRESHOLD = 8; //用来确定何时红黑树转变为链表
static final int UNTREEIFY_THRESHOLD = 6; //当想要将解决hash冲突的链表转变为红黑树时,需要判断此时数组的容量,
//若是数组容量太小(64),则不进行链表转为红黑树的操作,而是利用resize()函数对HashMap扩容
static final int MIN_TREEIFY_CAPACITY = 64; static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next; Node(int hash, K key, V value, Node<K,V> next) {
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; } public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
} public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
} 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;
}
} //减少了碰撞(key的hash值相等)的几率
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
} /**
* 数组Table的每一个元素不单纯只是一个Entry对象,它还是一个链表的头节点:
* 每一个Entry对象通过Next指针指向下一个Entry节点。
* 当新来的Entry映射到冲突数组位置时,只需要插入对应的链表位置即可。
*/
transient Node<K,V>[] table; transient Set<Map.Entry<K,V>> entrySet; //实际存储的key-value键值对的个数
transient int size; //HashMap发生结构性变化的次数(value值的覆盖不属于结构性变化)
transient int modCount; //threshold的值="容量*加载因子",当HashMap中存储数据的数量达到threshold时,就需要将HashMap的容量加倍。
int threshold; //加载因子实际大小
final float loadFactor; public HashMap(int initialCapacity, 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;
this.threshold = tableSizeFor(initialCapacity);
} public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
} public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
} public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
} public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
} final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
//判断table是否已初始化,否则进行初始化table操作
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//根据hash值确定节点在数组中的插入的位置,即计算索引存储的位置,若该位置无元素则直接进行插入
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
//节点若已经存在元素,即待插入位置存在元素
Node<K,V> e; K k;
//判断已存在的元素与待插入元素的hash值和key值是否相等,如果相等则将此节点赋值给e,便于后续覆盖value。
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//判断该元素是否为红黑树节点
else if (p instanceof TreeNode)
//红黑树节点则调用putTreeVal()函数进行插入
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
//若该元素是链表,且为链表头节点,则从此节点开始向后寻找合适的插入位置
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
//找到插入位置后,新建节点插入
p.next = newNode(hash, key, value, null);
//若链表上节点超过TREEIFY_THRESHOLD - 1,即链表长度为8,将链表转变为红黑树
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
//判断已存在的元素与待插入元素的hash值和key值是否相等,如果相等则将此节点赋值给e,便于后续覆盖value。
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
//当key存在时,覆盖value
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
//若存在key节点,则更新此节点的value,并返回旧的value。
return oldValue;
}
}
//记录修改次数
++modCount;
//判断是否需要扩容
if (++size > threshold)
resize();
afterNodeInsertion(evict);
//若不存在key节点(插入新key),则返回null
return null;
} public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
} final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
} // HashMap = null; 释放HashMap对象,里面所引用的对象也释放了。
// HashMap.clear(); 释放HashMap对象储存的所有对象。
public void clear() {
Node<K,V>[] tab;
modCount++;
if ((tab = table) != null && size > 0) {
size = 0;
for (int i = 0; i < tab.length; ++i)
tab[i] = null;
}
} public V remove(Object key) {
Node<K,V> e;
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
} final Node<K,V> removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable) {
Node<K,V>[] tab; Node<K,V> p; int n, index;
//判断表是否为空,以及p节点根据键的hash值对应到数组的索引初是否有节点
if ((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n - 1) & hash]) != null) {
Node<K,V> node = null, e; K k; V v;
//若是需要删除的节点就是该头节点,则让node引用指向它
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
node = p;
//结点在当前p所指向的头节点的链表或红黑树中,需要遍历查找
else if ((e = p.next) != null) {
//若头节点是红黑树节点,则调用红黑树本身的遍历方法getTreeNode,获取待删除的结点
if (p instanceof TreeNode)
node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
//否则就是普通链表,则使用do while循环遍历查找待删除结点
else {
do {
if (e.hash == hash &&
((k = e.key) == key ||
(key != null && key.equals(k)))) {
node = e;
break;
}
p = e;
} while ((e = e.next) != null);
}
}
if (node != null && (!matchValue || (v = node.value) == value ||
(value != null && value.equals(v)))) {
//若是红黑树结点的删除,则直接调用红黑树的removeTreeNode方法进行删除
if (node instanceof TreeNode)
((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
//若待删除结点是一个头节点,则用它的next节点顶替它作为头节点存放在table[index]中,以此达到删除的目的
else if (node == p)
tab[index] = node.next;
//若待删除结点为普通链表中的一个结点,则用该节点的前一个节点直接跳过该待删除节点,指向它的next结点
else
p.next = node.next;
//记录修改次数
++modCount;
--size;
afterNodeRemoval(node);
//若removeNode方法删除成功则返回被删除的结点
return node;
}
}
//若没有删除成功则返回null
return null;
} final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
}

最新文章

  1. 使用“Cocos引擎”创建的cpp工程如何在VS中调试Cocos2d-x源码
  2. go排序
  3. android开发学习之Level List篇
  4. 回车键转tab键
  5. 杂记- 3W互联网的圈子,大数据敏捷BI与微软BI的前端痛点
  6. 利用 Oracle EM 企业管理器 进行oracle SQL的优化(自动生成索引)
  7. 转:理解 PHP 中的 Streams
  8. sublime text There are no packages 解决!
  9. jQuery+Ajax+PHP+Mysql实现分页显示数据
  10. 关于EventHandler的使用
  11. 当git上文件大小写重命名的修改时(git大小写敏感/默认不敏感),如何提交
  12. 利用arcserver 自带tomcat实现上传shapefile、cad等文件,然后用soe解析。
  13. 区间求小于等于k的数字个数 hdu4177
  14. LeetCode(83): 删除排序链表中的重复元素
  15. LINUXJI积算器bc
  16. Android MediaCodec 状态(States)转换分析
  17. Sql Ado.net 学习笔记之连接字符串
  18. WGS84投影的WKID说明
  19. 学JS的心路历程Day28 - PixiJS -基础(二)
  20. yii 日期插件

热门文章

  1. 分组取前N记录(转)
  2. 8、Python MySQL - mysql-connector 驱动
  3. java.lang.SecurityException: class &quot;javax.servlet.ServletRegistration&quot;&#39;s signer information does not match signer information of other classes in the same package
  4. zabbix 发送邮件到企业微信
  5. Vue学习笔记【25】——Vue组件(组件间传值)
  6. vs code自动生成html代码
  7. Delphi 实现最近打开文件记录菜单
  8. sublime text3 nodejs控制台输出结果中文乱码
  9. Spring定时器的使用方法
  10. __iomem作用