zoukankan      html  css  js  c++  java
  • HashMap源代码阅读理解

    前言:
    这个实现跟数组和链表相关。应用了他们各自的特点,进行优化。
    数组的优势是,方便查找,但是新增超过加载因子的阈值的话(这部分是hashmap初始化的时候赋值进去的,如果不赋值,则会给默认值),就需要重新分配。链表查找比较麻烦,顺子链子一直找,找到位置才截止,有多少就得找多少。
    数组的时间复杂度为O(1),链表的时间复杂度为O(n)。因为数组是根据下标来找的,而数组的大小是确定的。而链表的大小是不确定的,是根据有多少元素进行多少次查找,找到为止。
    于是就会有人说hash碰撞,什么是hash碰撞,根据key(这里的key是根据散列值计算出来的)找数组的时候,发现数组的下标已经有值了,有值的情况下,会走链表的对比。所以会有碰撞的情况下,时间复杂度为O(n)的说法。
    文章比较枯燥,很多都是个人见解。如果哪里错了,望指出来共同进步。谢谢大家!
    正文:
    先看下构造函数部分,用来初始化的部分,其实初始化也很重要。容量和加载因子最好设置在合适的范围。过小的话,会不断的resize。过大的话又显得太过于浪费。因此根据需求来初始化一个合适的值,显得还是很重要的

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

    根据m初始化

    1 public HashMap(Map<? extends K, ? extends V> m) {//初始化并加入m的内容
    2 this.loadFactor = DEFAULT_LOAD_FACTOR; // 加载因子默认0.75f
    3 putMapEntries(m, false);//@1
    4 }
     1 public HashMap(int initialCapacity, float loadFactor) { //初始化hashmap的容量和加载因子
     2 if (initialCapacity < 0)
     3 throw new IllegalArgumentException("Illegal initial capacity: " +
     4 initialCapacity);
     5 if (initialCapacity > MAXIMUM_CAPACITY)
     6 initialCapacity = MAXIMUM_CAPACITY;
     7 if (loadFactor <= 0 || Float.isNaN(loadFactor))
     8 throw new IllegalArgumentException("Illegal load factor: " +
     9 loadFactor);
    10 this.loadFactor = loadFactor;
    11 this.threshold = tableSizeFor(initialCapacity); //根据容量和加载因子得到阈值 @2
    12 }

    看完了初始化,再来看看put

    1 public V put(K key, V value) {
    2 return putVal(hash(key), key, value, false, true);//@3 @4
    3 }

    再来看看取值

    1 public V get(Object key) {
    2 Node<K,V> e;
    3 return (e = getNode(hash(key), key)) == null ? null : e.value;//@5
    4 }

    --------------------------------------------------------------------------------方法说明--------------------------------------------------------------------------------------

     1 /**
     2 * @1把m赋值到初始化的hashMap里
     3 */
     4 final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
     5 int s = m.size(); 
     6 if (s > 0) {
     7 if (table == null) { //初始化的时候,哈希桶是空的,所以走这段逻辑
     8 float ft = ((float)s / loadFactor) + 1.0F; //如果我们传的m的size是6,6/0.75 + 1 = 9 为什么+1 我个人的理解就是先+1,看看是否超过了阈值,如果超过了阈值,就扩容,不加1的话,如果正好等于阈值就不会扩容。如果我们这一步不加1,那么tableSizeFor(8)求出来的就是8,那么正好等于阈值。但是没进行扩容。
     9 int t = ((ft < (float)MAXIMUM_CAPACITY) ?(int)ft : MAXIMUM_CAPACITY);//MAXIMUM_CAPACITY是最大的容量,为2的30次方,因此这里的t就是9
    10        if (t > threshold)//threshold是容量阈值,超过这个阈值后就会resize() 这个时候是0
    11         threshold = tableSizeFor(t);//求出阈值@2
    12       }
    13 else if (s > threshold) //如果不是初始化的时候,比较size是否大于阈值,大于则重新分配
    14 resize();
    15 for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
    16 K key = e.getKey();
    17 V value = e.getValue();
    18 putVal(hash(key), key, value, false, evict);
    19 }
    20 }
    21 }
     1 /**@2
     2 * 返回大于输入参数且最近的2的整数次幂的数
     3 * 这里网上查的都是大于,但是我发现,如果传入的是2的幂次方,那么就会得出2的幂次方这个数,比如8 16等,所以我个人的理解是大于等于,不知道是不是我哪里理解错了
     4 */
     5 static final int tableSizeFor(int cap) {
     6 int n = cap - 1;//9-1=8 防止9是2的幂次方的数,如果9是2的幂次方 则会
     7 n |= n >>> 1;//无符号右移,无论正负,高位补0 0000 1000|=0000 0100=0000 1100=12 
     8 n |= n >>> 2;//0000 1100|=0000 0011=0000 1111=15
     9 n |= n >>> 4;//0000 1111|=0000 0000=0000 1111=15
    10 n |= n >>> 8;//0000 1111|=0000 0000 =0000 1111=15
    11 n |= n >>> 16;//0000 1111|=0000 0000=0000 1111 =15
    12 return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;//return 16
    13 }
     1 /**
     2 *@3
     3 *java里的hashMap用的是数组和链表的结合,数组寻址比较容易(下标易找),但是插入和删除不方便(需要重新排列)。链表查找不方便(需要顺着链子找),但是插入和删除比较方便(把next和pre指针变动下就可以了)
     4 *数组中存着指针,指向链表中的元素(哈希桶),而我们hash函数就负责定位到下标。
     5 *从这里我们就可以看出 如果我们没有遇到哈希碰撞,那么时间复杂度就是O(1) 如果遇到碰撞了,时间复杂度就是O(n)
     6 */
     7 static final int hash(Object key) {//hash叫做散列,就是把任意长度的输入,通过散列算法,变成固定长度的输出(散列值)
     8 int h;// 散列会有碰撞,比如我现在只有0和1两个数字,要变成1位,那么输入的组合为00 01 10 11 输出的组合为0 1,所以不同的对象就会有相同的散列。这个时候同性相斥,就打起来了
     9 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);//返回散列值
    10 }
     1 //**@4
     2 * Implements Map.put and related methods
     3 *
     4 * @param hash 散列值
     5 * @param key hashMap的key
     6 * @param value 需要put的hashMap的value
     7 * @param onlyIfAbsent true存在key的话,就忽略不覆盖,false,相同key,覆盖原来值
     8 * @param evict if false, the table is in creation mode.
     9 * @return previous value, or null if none
    10 */
    11 final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
    12 boolean evict) {
    13 Node<K,V>[] tab; Node<K,V> p; int n, i;
    14 if ((tab = table) == null || (n = tab.length) == 0)//如果哈希桶是空的,分配空间
    15 n = (tab = resize()).length;//重新分配后的length
    16 if ((p = tab[i = (n - 1) & hash]) == null)//根据最大的索引值和hash来求得下标,看是否会有碰撞
    17 tab[i] = newNode(hash, key, value, null);//没有碰撞,把值放进去
    18 else {//如果发生了碰撞
    19 Node<K,V> e; K k;
    20 if (p.hash == hash &&
    21 ((k = p.key) == key || (key != null && key.equals(k))))//如果跟放在链表第一个的对象相同的key和相同的value,则赋值给e
    22 e = p;
    23 else if (p instanceof TreeNode)//如果值是treenode类型,则走treenode的puttreevalue方法
    24 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
    25 else {
    26 for (int binCount = 0; ; ++binCount) {
    27 if ((e = p.next) == null) {//走链表,看看下个元素是否为空,为空则放到下个元素里面去,注意,走到最后e是空的
    28 p.next = newNode(hash, key, value, null);
    29 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
    30 treeifyBin(tab, hash);//如果大于8,就从链表转变为红黑二叉树
    31 break;
    32 }
    33 if (e.hash == hash &&
    34 ((k = e.key) == key || (key != null && key.equals(k))))//如果遇到相同的key和value(代表放进去了)则break
    35 break;
    36 p = e;//走链表,一个个顺下去
    37 }
    38 }
    39 if (e != null) { //这个key相应的value已经存在情况下,上面if else第一种情况
    40 V oldValue = e.value;
    41 if (!onlyIfAbsent || oldValue == null)//如果遇到相同的值,是否替换原值
    42 e.value = value;
    43 afterNodeAccess(e);//把e移到链表的最后一个
    44 return oldValue;
    45 }
    46 }
    47 ++modCount;
    48 if (++size > threshold)
    49 resize();
    50 afterNodeInsertion(evict);
    51 return null;
    52 }}
     1 /**@5
     2 * Implements Map.get and related methods
     3 *
     4 * @param hash hash for key
     5 * @param key the key
     6 * @return the node, or null if none
     7 */
     8 final Node<K,V> getNode(int hash, Object key) {
     9 Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
    10 if ((tab = table) != null && (n = tab.length) > 0 &&
    11 (first = tab[(n - 1) & hash]) != null) {//我们之前putvalue的方法讲过的,(n-1)&hash是用来计算hash桶的下标的,找到下标
    12 if (first.hash == hash && 
    13 ((k = first.key) == key || (key != null && key.equals(k))))//这个putvalue也有相应的方法和解释
    14 return first;//返回
    15 if ((e = first.next) != null) {//查看下一个
    16 if (first instanceof TreeNode)//如果不是linkedHashMap,而是TreeNode,
    17 return ((TreeNode<K,V>)first).getTreeNode(hash, key);//返回TreddNode的计算方式的值
    18 do {//顺着链子找到相应的对象,返回
    19 if (e.hash == hash &&
    20 ((k = e.key) == key || (key != null && key.equals(k))))
    21 return e;
    22 } while ((e = e.next) != null);
    23 }
    24 }
    25 return null;
    26 }

  • 相关阅读:
    [kuangbin带你飞]专题十二 基础DP1 E
    hdu 1203 I NEED A OFFER! (01背包)
    hdu 2602 Bone Collector (01背包)
    hdu 4513 吉哥系列故事——完美队形II (manacher)
    hdu 2203 亲和串 (KMP)
    hdu 1686 Oulipo (KMP)
    hdu 1251 统计难题 (字典树)
    hdu 2846 Repository (字典树)
    hdu 1711 Number Sequence (KMP)
    poj 3461 Oulipo(KMP)
  • 原文地址:https://www.cnblogs.com/Garin/p/10180670.html
Copyright © 2011-2022 走看看