java hashmap(2)

另外开一篇 下面开始记hashmap的public,讲道理对方法实现的顺序感到不解,为什么喜欢用前面实现的方法去调用后面实现的方法呢,这样看到前面的方法时,总会调用还没实现的方法,对于可读性差了点哇

 

第一个方法是构造函数HashMap(int initialCapacity, float loadFactor)

做了一些输入合法性判断,然后给成员变量加载因子复制,给成员变量threshold赋比initialCapacity大的第一个2的幂

 

构造函数HashMap(int initialCapacity),调用第一个构造函数,传入initialCapacity和DEFAULT_LOAD_FACTOR

构造函数HashMap()只是把DEFAULT_LOAD_FACTOR赋给成员变量加载因子,这两个都是常规操作

 

构造函数HashMap(Map<? extends K, ? extends V> m)把默认的构造因子赋给成员变量构造因子,然后调用putMapEntries(m, false);

 

方法putMapEntries(Map<? extends K, ? extends V> m, boolean evict)

如果size<=0直接返回;如果table是null那么判断(size/加载因子) + 1是否大于最大容量,取二者较小者作为新的大小,然后新的大小跟阈值做比较,如果超过阈值则重新计算阈值;如果table不是null那么直接调用resize函数。然后遍历m的entrySet,调用putVal(hash(key), key, value, false, evict);

 

然后是两个常规方法,size()返回size, isEmpty返回size == 0

 

核心操作V get(Object key)

调用getNode方法,传入对象的hashcode和对象本身,如果返回null就返回null,否则返回getNode方法返回的对象的value

 

方法Node<K,V> getNode(int hash, Object key)

先声明一个node数组tab,一个node对象first, 一个node对象e,整型n,还有node的泛型k的对象。把成员变量table赋值给tab,把tab的长度赋值给n,然后一个暂时看不懂的操作first = tab[(n - 1) & hash](讲道理这hash是外面进来的,大概就是找到hash对应的桶吧)。

然后讲讲判断key的过程很有意思,先看看当前元素的hash跟目标的hash是否相同,如果相同就用==  || equals来判断目标的key和当前元素的key

然后对表判空,判长度,判桶是否空,如果里面有东西,先看看第一个元素是不是要拿的元素,如果是就返回,如果不是看看第一个元素是不是TreeNode的实例,如果是就调用第一个元素的((TreeNode<K,V>)first).getTreeNode(hash, key)方法,也就是对树状结构进行搜索;如果第一个元素不是TreeNode的实例,那么只是简单的线性遍历,不断遍历元素的.next属性即可。如果都做不到就返回null了

 

方法containsKey(Object key)

常规操作,调用getNode方法,传入对象的hashcode和对象本身,如果不为null就返回true

 

核心方法 V put(K key, V value)

返回putVal(hash(key), key, value, false, true)// 注释说把key和value关联起来,如果已经存在key,那么旧的值会被替换,然后返回旧的值,如果插入前不存在key就返回null

 

核心方法V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict)

声明node数组tab = 成员变量table,int遍历n = tab的长度, int变量i = (n - 1) & hash]大概又是找到对应的hash桶。然后看桶里有没有东西,如果没有东西,那么就直接newNode作为桶的第一个元素;否则判断桶里第一个元素是不是要找的,如果是就把第一个元素赋值给e;如果第一个元素不是要找的,那么看看桶里的数据结构是树状还是线性,如果是树状调、用((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);否则一个for,for里e=p.next对桶进行线性遍历,如果找到,break跳出;当遍历到最后都没有找到,那么直接对最后一个元素.next进行newNode,然后看桶里的元素是否达到变成树状的阈值,如果到了调用treeifyBin(tab, hash),然后break跳出;跳出后判断e是否为null,如果不为null那么说明原来存在key,就把原来的value换成新的value然后返回旧的value。走到最后说明发生了结构变化,递增modCount和size,然后判断size是否超过阈值,超过就调用resize(), 返回null。

 

核心方法Node<K,V>[] resize()// 初始化table或者让table长度翻倍

声明node数组oldtab=table;判断oldtab是否为null,声明int变量oldCap,当oldtab为null时赋值为0,否则赋值为oldtab的长度;声明int变量oldThr赋值为当前阈值;声明int变量newCap, newThr = 0。如果原来的容量大于0,那么判断如果原来的容量大于了设定的最大容量,将阈值设为最大的int值,返回原来的table;如果原来的容量翻倍后还是小于最大容量且原来的容量大于默认的容量,新的阈值翻倍;如果原来的阈值大于0,那么新的容量直接设成原来的阈值;否则说明原来的阈值和容量都没有设定过,这是个新的table,那么就把新的容量赋值为默认的初始容量,新的阈值赋值为默认的初始容量 * 默认的加载因子。如果新的阈值为0,那么判断新的容量和加载因子乘以新的容量是否同时小于最大容量,如果是,则把新的阈值赋值为加载因子乘以新的容量,如果不是就设置成最大的int。

根据新的容量初始化node数组,然后判断旧数组是否为null,如果不是就开始进行拷贝工作 : for对oldcap进行循环,如果oldtab[i]不为空,也就是说这hash桶里有东西,那么用一个变量保存桶里的第一个元素,然后把oldtab[i]=null,如果就这一个元素,那么newTab[e.hash & (newCap - 1)] = e;否则如果桶里是树状结构存储,那么((TreeNode<K,V>)e).split(this, newTab, j, oldCap);如果是线性结构存储,那么进行循环遍历,如果元素的hash & olldcap == 0 那么这元素会被放到newTab[i]中,否则会被放到newTab[i + oldcap]中,等于是将原来桶里的元素拆分到了两个桶里。最后返回newTab

 

核心方法treeifyBin(Node<K,V>[] tab, int hash)

如果传入的tab为nul或长度没有达到树状化的阈值,那么简单的调用resize()即可;否则判断hash对应的桶里是否为null,如果不是null,那么把桶里原来的元素变成TreeNode实例的链表,再把原来的桶指向链表的头,用链表的头调用hd.treeify(tab);

 

方法putAll(Map<? extends K, ? extends V> m)

通过调用方法putMapEntries完成

 

方法V remove(Object key)

判断(e = removeNode(hash(key), key, null, false, true))是否为null,如果是null返回null,如果不是返回e.value

 

核心方法Node<K,V> removeNode(int hash, Object key, Object value, boolean matchValue, boolean movable)

声明node数组tab,赋值为table;p赋值为对应hash桶里的第一个元素;int类型n,赋值为tab的长度;int类型index,赋值为对应的hash桶的序号;上来第一步也是日常判空判合法

声明node类型node,e,key类型k,value类型v

判断p(桶里第一个元素)是否要找的,如果是就把p赋值给node;如果不是,就看桶里还有没有其他元素,如果有,先看是不是树状存储,如果是树状存储就node = ((TreeNode<K,V>)p).getTreeNode(hash, key);否则线性遍历桶内元素,找到要找的那个元素。

然后对找到的node判空,如果不为空就说明要移除的对象存在,然后还是先判断是否为树状,如果是就调用((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);;如果不是就判断第一个元素是不是要移除的,如果是就直接把第二个元素设成桶的第一个元素;如果不是第一个元素,那么就把移除元素的前一个元素指向移除元素的后一个元素

递增modCount递减size

 

方法clear()

递增modCount,遍历桶看是否为空,如果不为空直接把桶指向null,size置零

 

方法containsValue(Object value)

注释说判断map中是否包含要找的值,但比较奇怪的是直接对table使用node的next属性进行遍历,没有区分树状结构和线性结构

 

方法Set<K> keySet()

判断成员变量keySet是否为null,如果是则new一个keyset然后返回成员变量keySet,否则直接返回成员变量keySet

 

内部类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);
}

没什么太特别的东西,基本是调用haspmap里的方法,或者调用一些暂时还不了解的类 暂缓暂缓

说一下KeySet里的forEach(Consumer<? super K> action)方法,对传入参数action做了一个判空,记录下了modCount然后对table进行遍历,对于每一个桶以及桶内的元素都进行action.accept(e.key);,完了招行看modCount跟原来是不是一样,如果不一样说明多线程乱改了东西,抛异常

 

方法Collection<V> values()

对values进行判空,如果为空就new一个,然后直接返回values

 

内部类Values extends AbstractCollection<V>

public final int size()                 { return size; }
public final void clear()               { HashMap.this.clear(); }
public final Iterator<V> iterator()     { return new ValueIterator(); }
public final boolean contains(Object o) { return containsValue(o); }
public final Spliterator<V> spliterator() {
    return new ValueSpliterator<>(HashMap.this, 0, -1, 0, 0);
}

前面也都是一些常规操作forEach(Consumer<? super V> action)方法跟keySet的差不多

 

方法Set<Map.Entry<K,V>> entrySet()

判断成员变量entrySet是否为null,如果是就new一个EntrySet,然后返回entrySet

 

内部类EntrySet extends AbstractSet<Map.Entry<K,V>>

上来先是三个常规方法

public final int size()                 { return size; }
public final void clear()               { HashMap.this.clear(); }
public final Iterator<Map.Entry<K,V>> iterator() {
    return new EntryIterator();
}

 

然后是内部类方法boolean contains(Object o)

如果o不是Map.Entry的实例返回false;将o转型为Map.Entry对象,获取对象的key,然后用HashMap的getNode方法获取,看获取的的结果跟强转的结果是否相同,如果是就返回true,否则返回false

 

内部类方法boolean remove(Object o)

同样对传入对象检测类型,强转成Map.Entry然后调用hashmap的removeNode方法,返回removeNode结果是否为null;

 

暂时看不懂的内部类方法

public final Spliterator<Map.Entry<K,V>> spliterator() {
    return new EntrySpliterator<>(HashMap.this, 0, -1, 0, 0);
}

内部类方法forEach(Consumer<? super Map.Entry<K,V>> action)

逻辑跟之前的forEach一样

内部类完

 

以下是Overrides of JDK8 Map extension methods

 

方法V getOrDefault(Object key, V defaultValue)

如果根据key来getNode为null,返回defaultValue,否则返回getNode的结果

 

方法V putIfAbsent(K key, V value)

调用putVal方法,将onlyIfAbsent设为true

 

方法boolean remove(Object key, Object value)

调用removeNode方法

 

方法V replace(K key, V value)

往getNode传入key,如果存在对应节点,就把value换成传入的value

 

方法replace(K key, V oldValue, V newValue)

往getNode传入key,然后判断value和oldValue是否相等,如果相等就换成新的value

 

 

方法V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction)//注释说如果计算时发生了结构变化就抛出ConcurrentModificationException异常

先对几种table为空或没有元素的情况进行判断,如果是就resize();

先分树状和线性两种情况遍历,看是否存在传入key,如果对应key和对应的value都存在,返回key对应的value;

记录modCount来判断是否发生了结构变化,V v = mappingFunction.apply(key);根据key计算value,如果已经存在传入的key只是值为null,那么简单的设置一下value,返回;如果桶内是树状结构,用桶内的第一个元素调用putTreeVal(this, tab, hash, key, v);;如果是线性结构,将新的元素放在桶内第一个元素,原来的第一个元素设为新元素的下一个元素,然后计算是否超过阈值要树状化;递增size和modCount

 

方法V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)

如果原来不存在传入的key或者key对应的value是null,返回null;记录modCount,调用V v = remappingFunction.apply(key, oldValue);如果新计算出的value不是null,替换原来的value;如果计算出的value是null,调用removeNode;


方法V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)

同样分成树状和线性两种情况找到对应的key;记录modCount,调用V v = remappingFunction.apply(key, oldValue);如果原来就存在对应的key和不为null的value,将value换成新算出来的value;否则removeNode;

如果原来不存在key,且新算出的value不是null,如果是树状,第一个元素调用t.putTreeVal(this, tab, hash, key, v);线性就放桶内第一个元素,原来的第一个元素放新元素后面;检查阈值是否树状化

递增modCount和size

 

方法V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction)

同样是起手先分树状和线性两种情况找到key对应的对象,不解为什么这么多次写同样的代码;如果原来key和对应的value都不是null,记录modCount,调用v = remappingFunction.apply(old.value, value);如果key存在但value为null,value赋值为传入的参数value;如果新的value值不为null,覆盖旧的value,返回;如果新的value也是null,removeNode

如果原来key没有对应的元素,但传入的value不为null,分两种情况,将插入的key和value插入到桶中

 

重写方法void forEach(BiConsumer<? super K, ? super V> action)

记录modCount,然后对每个桶内第一个元素调用action.accept(e.key, e.value);

 

重写方法void replaceAll(BiFunction<? super K, ? super V, ? extends V> function)

大体跟上面一样,只是最关键的一句换成了e.value = function.apply(e.key, e.value);

 

重写方法Object clone()

讲了个道理没有看懂哦

先是调用hashmap.super的clone方法,得到一个hashmap对象,然后对象调用

result.reinitialize();
result.putMapEntries(this, false);

就是先将里面的东西清空,然后把现在这hashmap的元素一个一个的放进去

 

方法loadFactor()简单的返回加载因子而已,序列化时会用到

 

方法int capacity()

table不为null时返回table.length;阈值大于0时返回阈值;返回默认的初始容量,序列化时会用到

 

方法void writeObject(java.io.ObjectOutputStream s)

序列化方法之一,后面再讲,现在的修为不足以看懂

 

方法void readObject(java.io.ObjectInputStream s)

这……今天先到这里吧

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值