1.hashmap说明概述:
hashMap是基于哈希表的map接口的非同步实现,相对hashtable来说,是hashtable的轻量级的实现.
允许null值的出现,通过键值对来存储,主要通过get和put来操作数据的插入和查询
2.hashmap 数据结构
在java中,数据结构中有数组和链表来实现对数据的存储,
数组: 数组存储区间是连续的,占用内存严重,故空间复杂的很大.但数组的二分查找时间复杂度小,为O(1);数组的特点是:寻址容易,插入和删除困难;
链表:链表存储区间离散,占用内存比较宽松,故空间复杂度很小,但时间复杂度很大,达O(N).链表的特点是:寻址困难,插入和删除容易。
放
而hash表就是通过这两种的结合来实现的 链表加数组的一种 "链表散列 " 的结构
当同一个hash值的节点数不小于8的时候,将不再以单链表形式存储了,会被调整成一棵红黑树。这就是JDK1.8和1.7的最大区别3.源码理解
构造函数:
1. new HashMap<String, Object>(); 通常我们都是使用这个无参的构造器去初始化 hashmap
这个构造方法只是单纯的去设置下,当map下次需要扩容的时候会去扩大几倍,这里默认是 0.75
2. 第二个构造函数,
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);
}
这里是设置一些 扩容比例和初始大小
操作方法:
1.put
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
这里 1.7 是全部在put里面的,在1.8 是全部放在了putVal里面
以下是复制下别人的一些说明....虚...
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0) //先判断,table大小,如果table为null,或者没分配空间,就resize一次
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null) //如果首节点为null,就创建一个首节点。
注意到tab[i = (n - 1) & hash],(n-1)&hash才是真正的hash值,也就是存储在table的位置(index)。
tab[i] = newNode(hash, key, value, null);//创建一个新的节点 else {//到这儿了,说明碰撞了,那么就要开始处理碰撞了
Node<K,V> e; K k;//首先先去查找与待插入键值对key相同的Node,存储在e中,k是那个节点的key
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))//p这时候是指向table[i]的那个Node,这时候先判断下table[i]这个节点是不是和我们待插入节点有相同的hash、key值。如果是就e = p e = p;
else if (p instanceof TreeNode)//到这儿,说明第一个节点的hash、key值与我们带插入Node的hash、key值不吻合,那么要从这个节点之后的链表节点或者树节点中查找。由于之前提到过,1.8的HashMap存储碰撞节点时,有可能是用红黑树存储
那么先判断首节点p的类型,如果是TreeNode类型(Node的子类),那么就说明碰撞节点已经用红黑树存储,那么使用树的插入方法,如果新插入了树节点,那么e会等于null,用于后面的判断与处理 e =
((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else {//说明碰撞节点是单链表存储的
for (int binCount = 0; ++binCount) {//单链表逐个向后查找 if ((e = p.next) == null) {//e引用下一个节点,如果是null,表示没有找到同hash、key的节点
p.next = newNode(hash, key, value, null);//创建一个新的节点,放到冲突链表的最后
if (binCount >= TREEIFY_THRESHOLD - 1) // 注意到如果这时候冲突节点个数达到8个,那么就会treeifyBin(tab, hash)函数,看是否需要改变冲突节点的存储结构,这个treeifyBin首先回去判断当前hash表的长度,如果不足64的话,实际上就只进行resize,扩容table,如果已经达到64,那么才会将冲突项存储结构改为红黑树。
treeifyBin(tab, hash); break; }
if (e.hash == hash && ((k = e.key) == key ||
(key != null &&key.equals(k))))//如果找到了同hash、key的节点,那么直接退出循环
break;
p = e;//调整下p节点 } }
if (e != null) { //退出循环后,先看e是不是为null,为null表示添加了一个新节点,而不为null表示找到了一个与待插入同hash、同key的已存节点 V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)//注意到这时候要判断是不是要修改已插入节点的value值,两个条件任意满足即修改 e.value = value;
afterNodeAccess(e);//这个是空函数,可以由用户根据需要覆盖 return oldValue; } }
++modCount;//当插入了新节点,才会运行到这儿,由于插入了新节点,整个HashMap的结构调整次数+1
if (++size > threshold)//HashMap中节点数+1,如果大于threshold,那么要进行一次扩容 resize();
afterNodeInsertion(evict); //这个是空函数,可以由用户根据需要覆盖 return null;
有点乱,对照源码来看还是可以看的......未完待续...