HashMap中Put()和Get()方法原理

JDK1.7:


get()方法
  • 首先判断输入的key是否为空,如果为空,从hashmap数组下标为0的位置获取值返回
  • 如果不为空,根据key的值,从hashmap数组中获取对应的entry对象,判断这个对象是否为空,为空返回null,不为空返回对应的value值, 获取value的方法中key为空和不为空时的方法里都先判断数组中的元素是否为0 ,如果不为0,才继续查找
// hashmap的get方法
public V get(Object key) {
	// 判断key 是否为null
      if (key == null)
      	// 如果为null 获取key为null的值
          return getForNullKey();
      // 获取对应key为要查询的key的entry
      Entry<K,V> entry = getEntry(key);
      // 判断是否获取到entry,如果没有,返回null,如果不为null,返回对应entry的value值
      return null == entry ? null : entry.getValue();
  }

  // 当key为null时获取value的值
  private V getForNullKey() {
  	// 判断hashmap中总的entry的数量,如果为0,说明hashmap中还没有值,返回null
      if (size == 0) {
          return null;
      }
      // 如果size 不为0 , 获取entry[] 数组中 下标为0的位置的链表
      for (Entry<K,V> e = table[0]; e != null; e = e.next) {
      	// 如果有entry对应的key的值为null ,返回对应的value
          if (e.key == null)
              return e.value;
      }
    	// 如果没有,返回空
      return null;
  }

  // 如果key不为null,获取key对应的value
  final Entry<K,V> getEntry(Object key) {
  	// 如果key不为null,判断hashmap中entry的数量是否为0 如果为0 返回null
      if (size == 0) {
          return null;
      }

      // 获取key的value值,如果key为null,返回hash值为0,反之,计算key对应的hash值
      int hash = (key == null) ? 0 : hash(key);
      // 遍历指定下标的entry数组元素链表
      for (Entry<K,V> e = table[indexFor(hash, table.length)];
           e != null;
           e = e.next) {
          Object k;
      	// 判断key的hash值与entry中的hash值是否相同,并且key通过== 和 equal 比较,
      	// 都为true时,返回这个 entry 对象 
          if (e.hash == hash &&
              ((k = e.key) == key || (key != null && key.equals(k))))
              return e;
      }
      // 如果指定下标key中的entry没有满足条件的,返回null
      return null;
  }

  // 计算 hash值
  final int hash(Object k) {
      int h = hashSeed;
      if (0 != h && k instanceof String) {
          return sun.misc.Hashing.stringHash32((String) k);
      }
      h ^= k.hashCode();
      h ^= (h >>> 20) ^ (h >>> 12);
      return h ^ (h >>> 7) ^ (h >>> 4);
  }

  // 通过hash值以及数组长度的位运算,获取entry的下标
  static int indexFor(int h, int length) {
      // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
      return h & (length-1);
  }
put()方法
  • 调用put方法的时候首先判断hashmap数组是否为空数组,

  • 如果为空,进行初始化,判断key的值是否是null,

  • 如果是null,把对应的value值存进数组中下标为0的位置,

  • 计算key的hash值,并计算出下标,

  • 遍历下标对应的链表,匹配hash值和key的值,

  • 如果存在,则覆盖,返回旧值

  • 如果不存在,新添加一个,返回null

  • 如果扩容,是先扩容之后再把数据添加进新扩容的数组中
    如果数组中元素的容量超过阈值,会触发扩容,
    扩容是先把源数组放进一个临时数组中,获取老数组的长度,通过老数组长度乘2获取新数组长度,并创建新数组,
    把临时数组中的数据通过重新计算下表,存进扩容后的数组中.

	// hashmap中put的方法
    public V put(K key, V value) {
    // 判断 entry[] 数组是否为空数组 如果为空 初始化entry数组
    if (table == EMPTY_TABLE) {
    	// 初始化hashmap
        inflateTable(threshold);
    }
    // 如果key 为null 把这个value对应的entry放进table[0]位置中
    if (key == null)
        return putForNullKey(value);
    // 计算 key的hash值
    int hash = hash(key);
    // 计算 key对应的 entry所在数组的下标
    int i = indexFor(hash, table.length);
    // 获取上面计算的下标的链表
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
        // 如果存在hash值相同,并且key相同的entry,对value进行覆盖,并返回覆盖区的value
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }

    // 如果链表中没有,则添加
    modCount++;
    addEntry(hash, key, value, i);
    return null;
	}


	// 初始化 hashmap 
    private void inflateTable(int toSize) {
        // 根据初始化的值,获取对应的小的大于这个值的 2 的n次方的值,也就是hashmap的容量
        int capacity = roundUpToPowerOf2(toSize);
        // 通过容量与扩容因子的相乘,获取最大不触发扩容的容量
        threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
        // 创建数组
        table = new Entry[capacity];
        initHashSeedAsNeeded(capacity);
    }

    // 获取当前数值最接近并且大于当前数值的最小2的n次方
	private static int roundUpToPowerOf2(int number) {
        // Integer.highestOneBit((number - 1) << 1) 获取当前数值减去1后向左位移1位,并且第二位往后都为0的值
        return number >= MAXIMUM_CAPACITY
                ? MAXIMUM_CAPACITY
                : (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;
    }

    // 当key为null的时候,对应value存放的位置
    private V putForNullKey(V value) {
    	// 获取table[0]的entry, 遍历这个链表
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
        	// 如果存在key为null的entry,把value进行覆盖,并返回覆盖前的value
            if (e.key == null) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        // 如果对应key为null的entry不存在,则在table[0]位置添加一个key为null的entry,并modcount加1
        modCount++;
        // 添加entry
        addEntry(0, null, value, 0);
        // 返回null
        return null;
    }
    // 添加 entry 参数是key的hash值,key,value,下标
    void addEntry(int hash, K key, V value, int bucketIndex) {
    	// 判断hashmap中所有entry的数量是否大于扩容临界值并且指定下标处的entry[]数组元素不为null 触发扩容
        if ((size >= threshold) && (null != table[bucketIndex])) {
        	// 扩容后的容量是原先的两倍
            resize(2 * table.length);
            // 获取key的hash值
            hash = (null != key) ? hash(key) : 0;
            // 重新计算下标
            bucketIndex = indexFor(hash, table.length);
        }
        // 创建entry
        createEntry(hash, key, value, bucketIndex);
    }

    // 创建entry的方法
    void createEntry(int hash, K key, V value, int bucketIndex) {
    	// 获取指定下标的entry
        Entry<K,V> e = table[bucketIndex];
        // 新创建的entry作为原先链表的最顶端,覆盖创建前的entry
        table[bucketIndex] = new Entry<>(hash, key, value, e);
        // 数组内总的entry加1
        size++;
    }

    // 扩容算法
    void resize(int newCapacity) {
    	// 首先把老的数组复制到一个临时数组中
        Entry[] oldTable = table;
        // 保存老的数组的长度
        int oldCapacity = oldTable.length;
        // 判断 老的数组长度是否等于最大值  如果等于, 扩容阙值为integer的最大值
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }
        // 如果不相等,根据新的数组长度创建数组
        Entry[] newTable = new Entry[newCapacity];
        // 移动老的数组中的数据到新的数组
        transfer(newTable, initHashSeedAsNeeded(newCapacity));
        // 把table的引用指向新的数组
        table = newTable;
        // 获取扩容的阙值
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
    }

    // 移到老的数组中的数据到新的数组里面
    void transfer(Entry[] newTable, boolean rehash) {
    	// 获取新的数组的长度
        int newCapacity = newTable.length;
        // 遍历老的entry数组
        for (Entry<K,V> e : table) {
        	// 判断entry不为null
            while(null != e) {
            	// 获取链表中的entry
                Entry<K,V> next = e.next;

                if (rehash) {
                	// 获取hash值
                    e.hash = null == e.key ? 0 : hash(e.key);
                }
                // 通过hash与新的数组长度,获取key在新的数组中的下标
                int i = indexFor(e.hash, newCapacity);
                // 当前entry添加到数组之前,先原来的entry存进当前entry下
                e.next = newTable[i];
                // 把当前entry赋给entry数组
                newTable[i] = e;
                // 对当前entry链表的下一个entry进行赋值
                e = next;
            }
        }
    }

JDK1.8

get方法
对输入的key的值计算hash值,
首先判断hashmap中的数组是否为空和数组的长度是否为0,如果为空和为0,则直接放回null
如果不为空和0,计算key对应的数组下标,判断对应位置上的第一个node是否满足条件,如果满足条件,直接返回
如果不满足条件,判断当前node是否是最后一个,如果是,说明不存在key,则返回null
如果不是最后一个,判断是否是红黑树,如果是红黑树,则使用红黑树的方式获取对应的key,
如果不是红黑树,遍历链表是否有满足条件的,如果有,直接放回,否则返回null

    public V get(Object key) {
        Node<K,V> e;
        // 计算hash值,与key一起找key的值
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }

调用 getnode方法

    /**
     * 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;
        // 首先获取hashmap中的数组和长度,并判断是否为空,如果为空,返回null
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
            // 获取key对应的下标对应的链表对象, 并比较第一个是否满足条件
            if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
                // 第一个如果满足条件,则直接返回
                return first;
            // 判断当前对象是否是最后一个,如果是,说明没有找到对应的key的值
            if ((e = first.next) != null) {
                // 如果不为空,判断是否是红黑树,如果是红黑树,使用红黑树获取对应key的值
                if (first instanceof TreeNode)
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                // 如果不是红黑树, 遍历链表,找到对应hash和key的node对象
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }
    public V put(K key, V value) {
        // 获取key的value值,调用用putval方法
        return putVal(hash(key), key, value, false, true);
    }

1.8 hashmap中put方法
首先计算key的hash值,获取hashmap中的数组和数组长度,如果数组为空,初始化计算key的下标
数组对应下标的位置是否为空,如果为空,则先添加一个,放在这个下标位置,然后判断数组内元素是否大于阈值,如果大于,则进行扩容
如果数组对应下标不为空,则先获取对应链表的第一个值,判断hash和key是否相同,如果相同,新value替换旧value,返回旧value
如果第一个值key不相同,判断当前链表是否是红黑树,如果是红黑树,调用红黑树链表put的方法
如果也不是红黑树,遍历链表,判断当前node是否是最后一个,如果是,说明链表中没有新添加的key,则在最后面新添加一个,然后判断是否超过阈值(8-1),如果超过,则转换成红黑树
如果不是最后一个,说明在中间已经存在key了, 把新值赋值给旧值,并返回旧值,判断是否需要扩容.

    /**
     * Implements Map.put and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @param value the value to put
     * @param onlyIfAbsent if true, don't change existing value
     * @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;
        // 判断key对应下标的数组位置是否为空,如果为空,在这个下标位置创建一个
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            // 如果key对应的参数不为空
            Node<K,V> e; K k;
            // 判断新添加的对象与旧对象中第一个对象的key的hash值和key是否相等
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                // 如果相等, node值存进临时值
                e = p;
            else if (p instanceof TreeNode)
                // 如果 已经存在的node对象是 treennode(红黑树)的实现类, 调用treenode的putval方法存
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                // 如果新添加的key不遇第一个node中的key相同,并且不是treenode的实现类,遍历node链表
                for (int binCount = 0; ; ++binCount) {
                    // 判断当前node是否是最后一个node,如果是,把新添加的node添加进这个链表中
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        // TREEIFY_THRESHOLD 默认是8, 即判断当前链表的长度是否大于7,如果是,则把链表转换成红黑树
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        // 执行到最后一个后,跳出循环
                        break;
                    }
                    // 如果当前node不是最后一个,比较key和hash值,如果存在相同,跳出循环(此时已经找到链表中key对应的node)
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    // 下一个node返回
                    p = e;
                }
            }
            // 判断找到的node是否为空,如果不为空
            if (e != null) { // existing mapping for key
                // 获取旧值
                V oldValue = e.value;
                // 如果设置是否可以改变值,如果设置不能改,但是旧值为空,则可以改,否则,需要设置可以改值的情况下才可以把新值赋给旧值
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                // 返回旧值
                return oldValue;
            }
        }
        ++modCount;
        // 判断当前数量是否超过扩容阈值,如果超过,进行扩容, 与1.7先扩容再添加不同,1.8是先扩容再添加
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }
  • 11
    点赞
  • 49
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值