zoukankan      html  css  js  c++  java
  • 浅谈HashMap

    浅谈HashMap

    基本特性

    定义

    hashMap是一个无序的,非空的容器,而且是非同步的容器会造成线程不安全的这类问题,即有许多人都想要同一份学习资料,系统会复制出多份资料后,给每个人一份资料,而不同的人对这份资料有着不同的看法并对该资料进行修改,再上传到系统中。可想而知资料会有多少个版本,但是系统只能存放一个版本的资料,因而会丢失大量版本信息。线程不安全:简单来说,就是用户读到的信息有一定可能是错误的,从而做出错误的操作(抢票时,可能抢到重票或抢到一张不存在的票)

    第一.jpg

    历遍

    HashMap的容量太大或太小,不利于literator(迭代器)查询目标。

    iteration.jpg

    HashMap的模型

    HashMap是数组和单向列表的结合体,即用数组来装列表的表头,因此在做增删等操作时,所消耗的时间和空间会比数组小,查询容器的中元素的速度会比列表快。类似于下图

    mm.jpg

    初始化

    HashMash中常量:

    常数1.jpg

    常数2.jpg

    HashMap有4个构造函数;
    public HashMap() {
            this.loadFactor = DEFAULT_LOAD_FACTOR; //使用默认的加速因子,bucket 的大小为默认的16
        }
    
     1  public HashMap(int initialCapacity, float loadFactor) {
     2 //initialCapacity是bucket的大小,loadFactor是加速因子
     3         if (initialCapacity < 0)
     4             throw new IllegalArgumentException("Illegal initial capacity: " +
     5                                                initialCapacity);
     6         if (initialCapacity > MAXIMUM_CAPACITY)
     7             initialCapacity = MAXIMUM_CAPACITY;//MAXIMUM_CAPACITY为2的30次方
     8         if (loadFactor <= 0 || Float.isNaN(loadFactor))
     9             throw new IllegalArgumentException("Illegal load factor: " +
    10                                                loadFactor);
    11         this.loadFactor = loadFactor;//将设置的加速因子赋给HashMap加速因子
    12         this.threshold = tableSizeFor(initialCapacity);//计算bucket的大小
    13     }
    
     public HashMap(int initialCapacity) {
            //只是设置了HashMap的bucket的大小,加速因子使用默认的
            this(initialCapacity, DEFAULT_LOAD_FACTOR);//调用上述的构造函数
        }
    
    public HashMap(Map<? extends K, ? extends V> m) {
            this.loadFactor = DEFAULT_LOAD_FACTOR;//将加速因子设为默认值
            putMapEntries(m, false);//将Map中的所有值都存放到HashMap中
        }
    
    
    final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
            int s = m.size();
            if (s > 0) {//判断m是否为空
                if (table == null) { // pre-size
                    float ft = ((float)s / loadFactor) + 1.0F;
                    int t = ((ft < (float)MAXIMUM_CAPACITY) ?
                             (int)ft : MAXIMUM_CAPACITY);
                    if (t > threshold)
                        threshold = tableSizeFor(t);
                }
                else if (s > threshold)//m的容量大于HashMap的界限值时就调用resize来扩容
                    resize();
                for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {//历遍m,将所有的元素放入hashMap中去
                    K key = e.getKey();
                    V value = e.getValue();
                    putVal(hash(key), key, value, false, evict);
                }
            }
        }
    

    Get和Put方法

    Get

    简单来说,get方法的核心是通过key.hashcode&(bucket.size-1)来确定桶位,再历遍桶位中列表。

    注意

    一般来,key.hashcode是唯一且不变的。

    key.hashcode&(bucket.size-1)和key.hashcode%(bucket.size-1)都能确桶位,但是前者进行的是位运算会比求余要快一些,而且解释了bucket的大小为什么是2的倍数(如果bucket的大小不是2的倍数,位运算的结果就不对,会造成hash表的混乱。)

    public V get(Object key) {
            Node<K,V> e;
            return (e = getNode(hash(key), key)) == null ? null : e.value;//判断getNode函数返回值来判断是否存在该值
        }
     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 &&//判断hashmap是否非空
                (first = tab[(n - 1) & hash]) != null)//判断目标值的链表头是否非空
            {
                if (first.hash == hash && // 检查目标值是否和链表头是否相同
                    ((k = first.key) == key || (key != null && key.equals(k))))
                    return first;
                //考虑到树结构的情况
                if ((e = first.next) != null) {
                    if (first instanceof TreeNode)
                        return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                    //历遍链表来查找目标值
                    do {
                        if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                            return e;
                    } while ((e = e.next) != null);
                }
            }
            return null;
        }
    
    Put

    简单来说,put方法的核心是通过key.hashcode&(bucket.size-1)来确定桶位,再历遍桶位中列表,找到合适的位置。

    注意

    在里边过程中,如果发现两个hashcode重复,hashmap一般会认为是一个值,就不会进行增加的操作。

     public V put(K key, V value) {
            return putVal(hash(key), key, value, false, true);
        }
    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)//判断hashmap是否非空
                n = (tab = resize()).length;//将扩容的hashmap的容量赋给n
            if ((p = tab[i = (n - 1) & hash/*计算添加值的hashcode所在的链表头*/]) == null)//判断目标值的链表头是否非空
                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))))//判断添加值和头链表中元素是否重复(通过hashcode来判断)
                    e = p;
                //考虑树的情况
                else if (p instanceof TreeNode)
                    e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
               
                else {
                    for (int binCount = 0; ; ++binCount) {
                        if ((e = p.next) == null) {
                            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))))//判断添加值和链表中元素是否重复(通过hashcode来判断)
                            break;
                        p = e;
                    }
                }
                if (e != null) { // 节点和添加值重复了
                    V oldValue = e.value;
                    if (!onlyIfAbsent || oldValue == null)
                        e.value = value;
                    afterNodeAccess(e);
                    return oldValue;
                }
            }
            ++modCount;//元素的数目增加
            if (++size > threshold)//元素的数目是否超过阀值
                resize();//扩容
            afterNodeInsertion(evict);
            return null;
        }
    
  • 相关阅读:
    raid0
    GitHub 标星 11000+,阿里开源的微服务组件如何连续 10 年扛住双十一大促?
    写给大家看的“不负责任” K8s 入门文档
    快速迁移 Next.js 应用到函数计算
    轻松搭建基于 Serverless 的 Go 应用(Gin、Beego 举例)
    阿里巴巴副总裁肖力:云原生安全下看企业新边界——身份管理
    从零开始入门 K8s | K8s 安全之访问控制
    深度解读!阿里统一应用管理架构升级的教训与实践
    CNCF 2019 年度报告重磅发布 | 云原生生态周报 Vol. 41
    HTML+CSS技术实现网页滑动门效果
  • 原文地址:https://www.cnblogs.com/whllow/p/12189718.html
Copyright © 2011-2022 走看看