个人参考网上资料做的总结、如有错误请指点、虚心学习。
HashMap
HashMap是map接口实现类之一、他父类有AbstractMap类、实现接口有map、Cloneable、Serializable
而据网上的资料查找、jdk1.8、HashMap的底层原理是、数组+链表+红黑树结构
HashMap属性值
//默认初始容量、采用左位移计算、 1 = x; 4 = n; DEFAULT_INITIAL_CAPACITY = x * 2的n平方
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
// 最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
// 默认的加载因子(负载因子) 、用于扩容使用
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 作为判断、当某个桶数组中的节点元素长度大于8时转红黑树结构
static final int TREEIFY_THRESHOLD = 8;
// 作为判断、当某个节点小于6时,会转换为链表,前提是它当前是红黑树结构
static final int UNTREEIFY_THRESHOLD = 6;
// 当桶数组容量小于该值时,优先进行扩容,而不是树化
static final int MIN_TREEIFY_CAPACITY = 64;
// 存放元素的数组(桶)
transient Node<K,V>[] table;
// 作为迭代使用
transient Set<Map.Entry<K,V>> entrySet;
// 元素数量
transient int size;
// 统计map修改次数
transient int modCount;
// 要调整大小的下一个大小值(容量*负载系数)、当前 HashMap 所能容纳键值对数量的最大值,超过这个值,则需扩容
int threshold;
// 加载因子(负载因子)
final float loadFactor;
HashMap构造器
// initialCapacity :初始容量
// loadFactor: 负载因子
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0) // 小于0就抛出异常
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
// 如果初始容量大于、最大容量则将最大容量赋值给初始容量
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
// 检查loadFactor是否合法
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor; //将默认的DEFAULT_LOAD_FACTOR赋值给loadFactor变量
//对临界值进行初始化,tableSizeFor(initialCapacity)这个方法会返回大于initialCapacity值的,且离其最近的2次幂,例如initialCapacity为29,则返回的值是32**/
this.threshold = tableSizeFor(initialCapacity);
}
// 自定义初始容量initialCapacity
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR); // 调用另一个构造器
}
// 无参的构造
public HashMap() {
// 它这里做的事情就是将默认的默认负载因子赋值给loadFactor变量
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
// 将另一个map集合中的数据放入到当前map集合
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}
HashMap添加方法
HashMap的添加方法是put(key/value)的形式添加数据
// 添加元素方法
public V put(K key, V value) {
//hash(key):计算当前key的哈希值
return putVal(hash(key), key, value, false, true);
}
//计算hash值的方法
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
主要方法还是在这个方法中putVal()
//哈希值、 key键、value要放入的值、onlyIfAbsent如果为true,则不更改现有值
//execit如果为false,则表处于创建模式。
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
/* tab;数组桶的初始化变量、
p:上一个节点值、简单来说就是相同的hash值计算出来的一片区域的节点tab[i = (n - 1) & hash]
n:hashMap的长度、
i:数组桶表
*/
Node<K,V>[] tab; Node<K,V> p; int n, i;
// 这个判断作为第一次添加数据时、做数组桶容量table变量的初始
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length; // resize()初始化加扩容方法、最后初始化table的容量为16
/* 这个判断的作用相当于就是找到key的节点在tab数组桶中的位置、将当前索引的值赋值给p、然后用p来做判断、
是否为null、如果为null则将key|value放入到tab[i]中
*/
if ((p = tab[i = (n - 1) & hash]) == null)
// 存放数据
tab[i] = newNode(hash, key, value, null);
else {
// e:临时节点存放上一个节点的数据、此值表示如果不为null表示当前链表数据中有一样key存在
// K:存放上一个节点的key
Node<K,V> e; K k;
// 判断是否于上一个key相同
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p; // 如果不同、将上一个节点p的数据给e
// 判断上一个节点是否为红黑树节点
else if (p instanceof TreeNode)
// 如果是红黑树节点、则在红黑树节点后添加数据、中插入方法中也有判断是否有相同的key存在
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
// 遍历所有桶数据中链表的数据、 能到这个else里面就是key的hash值相同、key的内容不同、并且不是红黑树结构的一个判断里面
for (int binCount = 0; ; ++binCount) {
// p变量保留的是上一个节点、而p.next找的是下一个节点
if ((e = p.next) == null) { // 这个判断肯定成立、上一个节点的、下一个节点完全没有、
// 这个步骤做的是将新节点赋值给上一个节点的下一个节点
p.next = newNode(hash, key, value, null);
// 如果链表长度大于等于TREEIFY_THRESHOLD的默认值8的时候
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
// 进行树化操作
treeifyBin(tab, hash);
break; // 跳出循环
}
// 在这个判断就是判断在链表中、下一个节点的key的hash值相同key值相同就break跳出询循环
// 然后进行下面的if (e != null)其中值替换掉
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
/*
1、判断上一个节点中的key是否相等的
2、判断上一个节点是否为红黑树结构、是则将调用红黑树的插入方法putTreeVal()、
这个方法中也判断了是否有重复的key值、将这个重复的key的节点出来赋值给e
经过上面二种情况的判断、得出一个结果、如果e != null的情况下、证明有重复的key、要将旧的value
值替换当前新添加的值
*/
if (e != null) { // existing mapping for key
V oldValue = e.value; // 获取value值
if (!onlyIfAbsent || oldValue == null)
e.value = value; // 将新value值重新赋值给存在相同key的节点上的value值
afterNodeAccess(e);
return oldValue; // 返回被替换之前的值
}
}
// 修改次数++
++modCount;
// 实际长度++ 如果大于要调整大小的下一个大小值 则需要扩容
if (++size > threshold)
resize(); // 扩容
afterNodeInsertion(evict);
// 添加成功
return null;
}
小结:
- putVal方法大概做的事情
- 当table为null时、通过扩容方法来做到table的初始化
- 当添加的数据key的hash值相同时?或者是key的hash值相同但是key的值不同时?
- key的hash值相同时:会找到相同hash值key的这一块区域、会判断他的hash值和key值是否相等、结果为true、将新的value值赋值旧的value、因此来达到key不变、value替换的效果
- key的hash值相同但是key的值不同时:首先会找到由
tab[i = (n - 1) & hash]
相同hash值计算出来的一片区域、会判断他的hash值和key值是否相等、结果为false、走else的判断、此时已经拿到了相同hash值计算出来的一片区域、此时下一个节点为null、则将数据存放在下这片区域的下一个节点中、简单来说就是会将key的hash值相同但是key的值不同的数据放在相同hash值创建的这片区域的下一个节点数据中因此达到链表的形式
- 在做key的hash值相同但是key的值不同时、他会有一个检查链表长度是否大于等于TREEIFY_THRESHOLD属性值的、大于的话就将链表转为红黑树的结构
- 还一个就是如果hashMap的长度大于threshold(初始值为12)就要进行扩容操作
HashMap扩容机制
在添加数据的第一次添加数据的时候和每次添加数据的最后都一个判断、一个判断是判断table属性值是否为null还有在每次添加数据的最后都有一个判断集合的元素个数、是否超阈值(这个值就是作为判断是否需要扩容的值)第一次初始扩容将阈值设置为12、
resize()扩容方法
final Node<K,V>[] resize() {
// 将table桶数组赋值给oldTab、此时oldTab就相当于是一个旧桶数组
Node<K,V>[] oldTab = table;
// 获取一下桶数组的长度大小
int oldCap = (oldTab == null) ? 0 : oldTab.length;
// 获取一下阈值、简单来说就是用来做扩容的判断条件之一
int oldThr = threshold;
// 定义新的桶数组大小、和新阈值都为0
int newCap, newThr = 0;
// 如果旧桶数组长度大于0的就成立
if (oldCap > 0) {
// 判断旧桶长度是否大于等于MAXIMUM_CAPACITY、HashMap的最大容量
if (oldCap >= MAXIMUM_CAPACITY) {
//将Integer.MAX_VALUE各大的值赋值给负载系数(阈值) 、并将进行扩容操作
threshold = Integer.MAX_VALUE;
// 返回旧桶数组
return oldTab;
}
// 按旧容量和旧阈值的2倍计算新容量和新阈值的大小
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
// 如果旧阈值大于0
else if (oldThr > 0) // initial capacity was placed in threshold
// 将旧阈值赋值给新桶数组容量
newCap = oldThr;
else { // zero initial threshold signifies using defaults
// DEFAULT_INITIAL_CAPACITY默认值为16、将其赋值给newCap新桶数组长度大小
newCap = DEFAULT_INITIAL_CAPACITY;
// newThr = (容量大小*负载系数)——————————————
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
// 如果新阈值等等于0、按阈值计算公式进行计算给出新的阈值
if (newThr == 0) {
// 计算新的临界值(阈值) (容量大小*负载系数) 负载系数 = 加载因子 = loadFactor
float ft = (float)newCap * loadFactor;
// 判断新容量是否小于最大默认初始容量和、新的临界值是否小于最大默认初始容量
// 为true将ft新的临界值(阈值)赋值给newThr新阈值
// 为false将Integer.MAX_VALUE值赋值给newThr新阈值
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
// 将新的负载系数(阈值)赋值给threshold用于下次扩容
threshold = newThr;
//表示忽略该警告
@SuppressWarnings({"rawtypes","unchecked"})
// 创建新桶数组、桶的初始化在这里就完成了newCap=16
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
// 将新桶赋值给属性table
table = newTab;
// 下面做的判断是旧桶不为null的操作
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e; // 临时存放节点变量
// 判断桶数组中的元素不等于null
if ((e = oldTab[j]) != null) {
oldTab[j] = null; // 设置为null等待垃圾回收
if (e.next == null) // 判断下一个节点等等于null
// 把e放在新容量[e.hash & (newCap - 1)求出来的桶数组中]
newTab[e.hash & (newCap - 1)] = e;
// 判断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;
}
扩容小结:
- 扩容做的几个事情
- 计算出桶数组的新容量(newCap)为多少和新阈值(newThr )为多少
- 将其计算好的容量和阈值赋值给HashMap类属性、将其做到桶数组的初始化容量大小为:16、扩容形式为 2的n次幂的形式
- 将键值对节点重新映射到新的桶数组里。如果节点是 TreeNode 类型,则需要拆分红黑树。如果是普通节点,则节点按原顺序进行分组。
HashMap获取方法
Map<String, Object> map1 = new HashMap<>();
map1.put("通话","2");
map1.put("重地","4");
map1.get("通话");
// HashMapt从写的get方法
// 结果是会返回key对应的value值
public V get(Object key) {
Node<K,V> e;
// 主要的还是这个方法getNode()
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
// 这个方法的作用就是在桶数组中找到链表上的节点数据
// hash:key的hash值
// key:就是key(键)
final Node<K,V> getNode(int hash, Object key) {
/* tab:初始化桶的数据临时存放地点
first:头接节点
e:临时节点、用来判断是否有下一个节点
n:长度
k:临时存放的key
*/
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
/*
这个判断的作用是table(桶数组)不能为null、并且桶数组长度要大于0、并且计算出来的头节点不能为null
主要会找到通过key的hash值计算当前key在桶数组中的那一块位置、并将值赋值给first(头节点)
为真继续执行
为假返回null
*/
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
/*
这个判断的作用是头节点的hash值和传递过来的hash值一样并且、头节点的key值==传过来的key值或者、
传过来的key值不等于null并且key.equals(头节点的key)、
为真返头节点
为假继续执行下一个判断
*/
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 {
/*
这个判断的作用是头节点的下一个节点的hash值和传递过来的hash值一样并且、
头节点的下一个节点key值==传过来的key值或者、传过来的key值不等于null并且key.equals(头节点的下一个节点key)、
为真返头节点下一个节点
为假继续循环
*/
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null); // 如果下一个节点的下一个节点不为null就一直查找
}
}
return null;
}
获取方法小结:
- 首先的满足条件
table
桶数组不能为null、桶数组的长度必须大于0、还有通过key的hash计算出来的头节点不能为nullfirst = tab[(n - 1) & hash]) != null
- 判断头节点的hash值和传递过来的hash值一样并且、头节点的key值==传过来的key值或者、
传过来的key值不等于null并且key.equals(头节点的key)、如果为true就返回头节点 - 判断头节点 、是否有下一个节点、
- 判断头节点 、是否是红黑树、如果是则使用
getTreeNode(hash, key)
红黑树的查询方式 - 判断头节点的下一个节点是否还有下一个节点
- 总结
- 找到节点在桶数组的的位置
- 判断节点的节点是否为红黑树结构、不是则就按照遍历链表查找节点元素
HashMap删除方法
Map<String, Object> map1 = new HashMap<>();
map1.put("通话","2");
map1.put("重地","4");
map1.remove("重地");
// haahMap实现的删除方法
public V remove(Object key) {
Node<K,V> e;
// removeNode主要还是在这个方法里面
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) {
/*
tab:桶数组数据
p:临时桶存放桶中的数据
n:桶的长度
p:元素的索引下标
*/
Node<K,V>[] tab; Node<K,V> p; int n, index;
// 找到数据在桶中的位置
if ((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n - 1) & hash]) != null) {
/*
node:数据的临时变量
e:
K;key
v:value
*/
Node<K,V> node = null, e; K k; V v;
// 判断第一个节点的hash值和键值是否对应上
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
node = p; // 将p赋值给node作为最后返回出去
// 判断上一个节点的下一个节点是否不为null
else if ((e = p.next) != null) {
// 判断是否是红黑树
if (p instanceof TreeNode)
// 交给红黑树查找方法
node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
else { // 遍历链表
do {
// 下一个节点会不会相同判断
if (e.hash == hash &&
((k = e.key) == key ||
(key != null && key.equals(k)))) {
node = e; // 如果成功将下一个节点赋值给node
break; // 跳出循环
}
p = e;
} while ((e = e.next) != null);
}
}
// 删除操作 node、最后放的是要被删除的节点
if (node != null && (!matchValue || (v = node.value) == value ||
(value != null && value.equals(v)))) {
// 判断是否红黑树
if (node instanceof TreeNode)
((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
// 判断要被删除的节点是否是首节点
else if (node == p)
tab[index] = node.next; // 将要被删除的节点下的几点赋值到tab[index]这片区域中
else
p.next = node.next; // 将要被删除的节点的下一个节点赋值给首节点的下一个节点
++modCount; // 修改次数++
--size; // 长度--
afterNodeRemoval(node);
return node; // 返回被删除的节点
}
}
return null;
}
HashMap删除方法小结
- 找到节点在桶数组的位置
- 判断这个节点是否为红黑树结构、遍历此节点、
- 删除节点。
HashMap遍历方式
public void test(){
Map<String, Integer> map = new HashMap<>();
map.put("1",2);
map.put("2",3);
// 通过keySet获取所有的key、
Set<String> keys = map.keySet();
for (String key : keys) {
System.out.println("key="+key);
}
// 通过values获取所有的value、
Collection<Integer> values = map.values();
for (Integer value : values) {
System.out.println("value="+value);
}
// 通过key和value
Set<Map.Entry<String, Integer>> entrySet = map.entrySet();
for (Map.Entry<String, Integer> entry : entrySet) {
System.out.println(entry.getKey());
System.out.println(entry.getValue());
}
}
按道理来讲就是三种、获取key放入到set集合中、获取所有value值放入到Collection集合中、还有一个是获取key/value
获取key值方式解析
// 通过keySet获取所有的key、
Set<String> keys = map.keySet();
for (String key : keys) {
System.out.println("key="+key);
}
看到这个方式为什么可以获取到所有的键呢??
// 通过keySet获取所有的key、
// 第一步调用了map的keySet()方法、得到一个set集合
Set<String> keys = map.keySet();
// 第二步使用增强for遍历数据
for (String key : keys) {
System.out.println("key="+key);
}
第一步
//找到HashMap的实现
public Set<K> keySet() {
// keySet开始为null赋值给ks
Set<K> ks = keySet;
if (ks == null) { // 成立
ks = new KeySet(); // new了一个KeySet()对象给ks
keySet = ks; // 将开始赋值给keySet
}
return ks; // 返回ks 、此时ks = new KeySet();
}
// 键集合
final class KeySet extends AbstractSet<K> {
public final int size() { return size; }
public final void clear() { HashMap.this.clear(); }
public final Iterator<K> iterator() { return new KeyIterator(); }
public final boolean contains(Object o) { return containsKey(o); }
public final boolean remove(Object key) {
return removeNode(hash(key), key, null, false, true) != null;
}
public final Spliterator<K> spliterator() {
return new KeySpliterator<>(HashMap.this, 0, -1, 0, 0);
}
public final void forEach(Consumer<? super K> action) {
Node<K,V>[] tab;
if (action == null)
throw new NullPointerException();
if (size > 0 && (tab = table) != null) {
int mc = modCount;
for (int i = 0; i < tab.length; ++i) {
for (Node<K,V> e = tab[i]; e != null; e = e.next)
action.accept(e.key);
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
}
// 通过keySet获取所有的key、
// 第一步调用了map的keySet()方法、得到一个set集合
//Set<String> keys = map.keySet();
// 简单来说他这异步就做了这个事情
Set<String> keys = new KeySet();
// 第二步使用增强for遍历数据
for (String key : keys) {
System.out.println("key="+key);
}
第二步
// 第一步调用了map的keySet()方法、得到一个set集合
Set<String> keys = map.keySet();
// 第二步使用增强for遍历数据
for (String key : keys) {
System.out.println("key="+key);
}
而第二步就做了一个增强for的遍历、在map.keySet()看这个源码中会发现他这一步没有做什么操作、为什么他可以获取到key?
// 这是我们写的代码
for (String key : keys) {
System.out.println("key="+key);
}
// 而编译后的代码缺不一样
String key;
for(Iterator iterator = keys.iterator();
iterator.hasNext();
System.out.println("key="+key)){
key = (String)iterator.next();
}
// 会发现他在编译后会调用keys.iterator()方法 创建一个Iterator(迭代器)对象
// iterator.hasNext() 会作为循环的条件
// iterator.next() 找出来的key值
首先要我们要知道他找的是哪一个实现类中的迭代方法、
keys.iterator(); //keys是一个变量
// 第一步调用了map的keySet()方法、得到一个set集合
//Set<String> keys = map.keySet(); //map.keySet();做的事情就是下面这个事情
// 简单来说他这异步就做了这个事情
Set<String> keys = new KeySet(); // new KeySet();对象给keys变量
// 看回来
keys.iterator(); //keys是一个变量
// iterator(); 这个方法实现是KeySet()对象中实现的iterator()方法
KeySet对象
final class KeySet extends AbstractSet<K> {
public final int size() { return size; }
public final void clear() { HashMap.this.clear(); }
public final Iterator<K> iterator() { return new KeyIterator(); } // 调用的是这个
public final boolean contains(Object o) { return containsKey(o); }
public final boolean remove(Object key) {
return removeNode(hash(key), key, null, false, true) != null;
}
public final Spliterator<K> spliterator() {
return new KeySpliterator<>(HashMap.this, 0, -1, 0, 0);
}
public final void forEach(Consumer<? super K> action) {
Node<K,V>[] tab;
if (action == null)
throw new NullPointerException();
if (size > 0 && (tab = table) != null) {
int mc = modCount;
for (int i = 0; i < tab.length; ++i) {
for (Node<K,V> e = tab[i]; e != null; e = e.next)
action.accept(e.key);
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
}
// 键迭代器
final class KeyIterator extends HashIterator
implements Iterator<K> {
// iterator.next() 找出来的key值 调用的是这个方法nextNode()返回找到的节点
public final K next() { return nextNode().key; }
}
//HashIterator类
abstract class HashIterator {
// 存放下一个节点名的位置
Node<K,V> next; // next entry to return
// 当前节点
Node<K,V> current; // current entry
// 修改次数
int expectedModCount; // for fast-fail
// 桶索引
int index; // current slot
// 构造器做的事情就是找到第一个链表在桶数组的位置
HashIterator() {
expectedModCount = modCount;
Node<K,V>[] t = table;
current = next = null;
index = 0;
if (t != null && size > 0) { // advance to first entry
// 找到有元素的桶位置将元素赋值给next
do {} while (index < t.length && (next = t[index++]) == null);
}
}
// 判断节点不为null 就是iterator.hasNext() 会作为循环的条件
public final boolean hasNext() {
return next != null; // 节点不为null
}
// 查找链表中的节点元素
final Node<K,V> nextNode() {
Node<K,V>[] t; // 临时存放桶数组
Node<K,V> e = next; // 将构造器找的第一节点放在next、简单来说e就是临时存放上一个节点的位置
if (modCount != expectedModCount)
throw new ConcurrentModificationException(); // 并发异常
if (e == null)
throw new NoSuchElementException();
if ((next = (current = e).next) == null && (t = table) != null) {
//寻找下一个包含链表节点引用的桶位置将其赋值给next
do {} while (index < t.length && (next = t[index++]) == null);
}
return e; // 返回上一个节点
}
public final void remove() {...}
}
HashMap遍历小结
-
其实通过增强for遍历的方式都一样、只是代码编译过程中做了什么我们看不到的操作而已!!!
Map<String, Integer> map = new HashMap<>(); map.put("1",2); map.put("2",3); // 通过keySet获取所有的key、 Set<String> keys = map.keySet(); for (String key : keys) { System.out.println("key="+key); } // 他也可以写成这种方式、迭代器的方式 Iterator<String> iterator = map.keySet().iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); } // 而增强for编译后的代码却是创建迭代器对象、调用迭代器对象方法进行数据遍历的 String key; for(Iterator iterator = keys.iterator(); iterator.hasNext(); System.out.println("key="+key)){ key = (String)iterator.next(); }
-
主要实现方法在HashMap类中这个内部类HashIterator类这个类做的操作
- 构造器找到有数据的桶数组位置
- hasNext()方法作用是判断下一个节点不为null
- nextNode()方法作用是、找到桶数组下一个链表的位置将其赋值给next、最后返回上一个节点的数据
HashMap总结
-
HashMap它是Map接口类的实现类之一、
-
它的键、值可以存放为null的值、但是HashMap最多只允许一条记录的键为null,允许多条记录的值为null
-
它是一个线程不安全的集合
-
初始的信息有桶数组为16大小、threshold(阈值 = (容量大小*负载系数) )为12、loadFactor(负载系数)为0.75f、
-
什么时候扩容?
- 在第一次添加数据时扩容、进行默认初始桶数组容量大小、和threshold(阈值)大小
- 在size > threshold(阈值)、长度大于阈值进行扩容
- 扩容桶长度形式为 2的n次幂的形式
-
HashMap存储结构数组+链表+红黑树
-
链表数量(元素个数)大于8的情况下将链表转为红黑树结构、大于8说的就是0-7下标这中间有8的元素、所以说这个8就是说链表中元素的个数。
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash);
-
这里有个==注意点==:
-
在
treeifyBin(tab, hash);
方法中还有一个判断//满足这个条件会先进行扩容 if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
-
链表长度大于(默认值8)并且数组长度大于64进行将链表转红黑树、否则、则进行扩容操作
-
-
如何计算元素应该放在桶(bucket)数组的那个位置上???
p = tab[i = (n - 1) & hash] p = 桶[i = (桶长度大小-1) 与运算 key的hash值] // 通过(桶长度大小-1)和key的hash值做与运算来得到元素放在桶数组的什么位置上
-
key的hash值相同、但是key的值不同、他会把这个数据放在由hash值计算出来的这块区域中的下一个节点、简称尾插法
样式图
参考网址
https://segmentfault.com/a/1190000012926722