首页 > 其他 > 详细

HashMap源代码阅读理解

时间:2019-01-20 21:17:44      阅读:168      评论:0      收藏:0      [点我收藏+]

前言:
这个实现跟数组和链表相关。应用了他们各自的特点,进行优化。因为之前的很多工作经验都是.NET的,所以之前都研究了.NET的东西。在写了一阵子java之后,熟悉了java,就开始写一些java的东西。
做一门语言,了解底层的实现一直是我的追求。毕竟了解了他们的实现,你才会了解到到底什么样的情况下,该用什么样的集合。
数组的优势是,方便查找,但是新增超过加载因子的阈值的话(这部分是hashmap初始化的时候赋值进去的,如果不赋值,则会给默认值),就需要重新分配。链表查找比较麻烦,顺子链子一直找,找到位置才截止,有多少就得找多少。
数组的时间复杂度为O(1),链表的时间复杂度为O(n)。因为数组是根据下标来找的,而数组的大小是确定的。而链表的大小是不确定的,是根据有多少元素进行多少次查找,找到为止。
于是就会有人说hash碰撞,什么是hash碰撞,根据key(这里的key是根据散列值计算出来的)找数组的时候,发现数组的下标已经有值了,有值的情况下,会走链表的对比。所以会有碰撞的情况下,时间复杂度为O(n)的说法。
不过看了源代码的收获还是蛮大的,很多写法值得借鉴。因为里面的很多变量都是一个英文字母之类的,因此可读性比较差。但是我想,为了性能考虑的话,这一切都是值得的。毕竟源代码的性能高于可阅读性。所以就造成了我们阅读源代码的时候有点吃力。
文章比较枯燥,很多都是个人见解。如果哪里错了,望指出来共同进步。后面有时间了,也会写一些其他的集合类型的源码理解。因为这对于代码的优化也有借鉴作用,谢谢大家!
正文:
先看下构造函数部分,用来初始化的部分,其实初始化也很重要。容量和加载因子最好设置在合适的范围。过小的话,会不断的resize。过大的话又显得太过于浪费。因此根据需求来初始化一个合适的值,显得还是很重要的

无初始化参数
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // 加载因子默认0.75f
}

根据m初始化
public HashMap(Map<? extends K, ? extends V> m) {//初始化并加入m的内容
this.loadFactor = DEFAULT_LOAD_FACTOR; // 加载因子默认0.75f
putMapEntries(m, false);//@1
}

public HashMap(int initialCapacity, float loadFactor) { //初始化hashmap的容量和加载因子
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); //根据容量和加载因子得到阈值 @2
}
看完了初始化,再来看看put
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);//@3 @4
}
再来看看取值
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;//@5
}

 

--------------------------------------------------------------------------------方法说明--------------------------------------------------------------------------------------
/**
* @1把m赋值到初始化的hashMap里
*/
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
int s = m.size();
if (s > 0) {
if (table == null) { //初始化的时候,哈希桶是空的,所以走这段逻辑
float ft = ((float)s / loadFactor) + 1.0F; //如果我们传的m的size是6,6/0.75 + 1 = 9 为什么+1 我个人的理解就是先+1,看看是否超过了阈值,如果超过了阈值,就扩容,不加1的话,如果正好等于阈值就不会扩容。如果我们这一步不加1,那么tableSizeFor(8)求出来的就是8,那么正好等于阈值。但是没进行扩容。
int t = ((ft < (float)MAXIMUM_CAPACITY) ?(int)ft : MAXIMUM_CAPACITY);//MAXIMUM_CAPACITY是最大的容量,为2的30次方,因此这里的t就是9
       if (t > threshold)//threshold是容量阈值,超过这个阈值后就会resize() 这个时候是0
        threshold = tableSizeFor(t);//求出阈值@2
      }
else if (s > threshold) //如果不是初始化的时候,比较size是否大于阈值,大于则重新分配
resize();
for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
K key = e.getKey();
V value = e.getValue();
putVal(hash(key), key, value, false, evict);
}
}
}

/**@2
* 返回大于输入参数且最近的2的整数次幂的数
* 这里网上查的都是大于,但是我发现,如果传入的是2的幂次方,那么就会得出2的幂次方这个数,比如8 16等,所以我个人的理解是大于等于,不知道是不是我哪里理解错了
*/
static final int tableSizeFor(int cap) {
int n = cap - 1;//9-1=8 防止9是2的幂次方的数,如果9是2的幂次方 则会
n |= n >>> 1;//无符号右移,无论正负,高位补0 0000 1000|=0000 0100=0000 1100=12
n |= n >>> 2;//0000 1100|=0000 0011=0000 1111=15
n |= n >>> 4;//0000 1111|=0000 0000=0000 1111=15
n |= n >>> 8;//0000 1111|=0000 0000 =0000 1111=15
n |= n >>> 16;//0000 1111|=0000 0000=0000 1111 =15
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;//return 16
}
/**
*@3
*java里的hashMap用的是数组和链表的结合,数组寻址比较容易(下标易找),但是插入和删除不方便(需要重新排列)。链表查找不方便(需要顺着链子找),但是插入和删除比较方便(把next和pre指针变动下就可以了)
*数组中存着指针,指向链表中的元素(哈希桶),而我们hash函数就负责定位到下标。
*从这里我们就可以看出 如果我们没有遇到哈希碰撞,那么时间复杂度就是O(1) 如果遇到碰撞了,时间复杂度就是O(n)
*/
static final int hash(Object key) {//hash叫做散列,就是把任意长度的输入,通过散列算法,变成固定长度的输出(散列值)
int h;// 散列会有碰撞,比如我现在只有0和1两个数字,要变成1位,那么输入的组合为00 01 10 11 输出的组合为0 1,所以不同的对象就会有相同的散列。这个时候同性相斥,就打起来了
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);//返回散列值
}

//**@4
* Implements Map.put and related methods
*
* @param hash 散列值
* @param key hashMap的key
* @param value 需要put的hashMap的value
* @param onlyIfAbsent true存在key的话,就忽略不覆盖,false,相同key,覆盖原来值
* @param evict if false, the table is in creation mode.
* @return previous value, or null if none
*/
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)//如果哈希桶是空的,分配空间
n = (tab = resize()).length;//重新分配后的length
if ((p = tab[i = (n - 1) & hash]) == null)//根据最大的索引值和hash来求得下标,看是否会有碰撞
tab[i] = newNode(hash, key, value, null);//没有碰撞,把值放进去
else {//如果发生了碰撞
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))//如果跟放在链表第一个的对象相同的key和相同的value,则赋值给e
e = p;
else if (p instanceof TreeNode)//如果值是treenode类型,则走treenode的puttreevalue方法
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {//走链表,看看下个元素是否为空,为空则放到下个元素里面去,注意,走到最后e是空的
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))//如果遇到相同的key和value(代表放进去了)则break
break;
p = e;//走链表,一个个顺下去
}
}
if (e != null) { //这个key相应的value已经存在情况下,上面if else第一种情况
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)//如果遇到相同的值,是否替换原值
e.value = value;
afterNodeAccess(e);//把e移到链表的最后一个
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}}

/**@5
* Implements Map.get and related methods
*
* @param hash hash for key
* @param key the key
* @return the node, or null if none
*/
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) {//我们之前putvalue的方法讲过的,(n-1)&hash是用来计算hash桶的下标的,找到下标
if (first.hash == hash &&
((k = first.key) == key || (key != null && key.equals(k))))//这个putvalue也有相应的方法和解释
return first;//返回
if ((e = first.next) != null) {//查看下一个
if (first instanceof TreeNode)//如果不是linkedHashMap,而是TreeNode,
return ((TreeNode<K,V>)first).getTreeNode(hash, key);//返回TreddNode的计算方式的值
do {//顺着链子找到相应的对象,返回
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}

 

HashMap源代码阅读理解

原文:https://www.cnblogs.com/Garin/p/10180670.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!