zoukankan      html  css  js  c++  java
  • java基础解析系列(三)---HashMap

    java基础解析系列(三)---HashMap

    java基础解析系列

    基本概念

    • 节点: Node<Key,Value>,存放key和value
    static class Node<K,V> implements Map.Entry<K,V> {    
        final int hash;   
        final K key;   
        V value;   
        Node<K,V> next;
    }
    
    • 键值对数组:Node<K,V>[] table
    • 加载因子
    • 容量 :Node数组的长度
    • 大小:hashmap存放的Node的数目
    • 阈值:容量*加载因子

    工作原理

    • 创建一个长度为2的次幂的node数组
    • put的时候,计算key的hash值,将hash值与长度-1进行与运算
    • 如果数组该下标的位置为空,直接存放,如果不为空,判断节点是否为树节点,如果是的话按红黑树的方式存入,否则按照链表的形式存入
    • 当hashmap的节点数目大于阈值的时候,将会重新构造hashmap,而这种操作是费时的操作,所以建议初始化一个合适的容量

    • 默认容量,2的四次方

    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

    • 默认加载因子
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    
    • node 数组
     transient Node<K,V>[] table;
    
        
    
    • 键值对数目,不是table的长度
        /**
         * The number of key-value mappings contained in this map.
         */
        
        transient int size;
    
    • 阈值
        /**
         * The next size value at which to resize (capacity * load factor).
         *
         * @serial
         */
        //阈值
        int threshold;
    
    • 加载因子
    
        /**
         * The load factor for the hash table.
         *
         * @serial
         */
         //加载因子
        final float loadFactor;
    

    构造方法

    • 传入初始容量和加载因子
     public HashMap(int initialCapacity, float loadFactor) {
            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);
        }
    
    • 传入初始容量,使用默认的加载因子
    
        public HashMap(int initialCapacity) {
            this(initialCapacity, DEFAULT_LOAD_FACTOR);
        }
    
    • 无参数,默认容量和加载因子
        /**
         * Constructs an empty <tt>HashMap</tt> with the default initial capacity
         * (16) and the default load factor (0.75).
         */
        public HashMap() {
            this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
        }
    

    • 容量必须是2的n次方,当你传入的参数不符合条件,会有方法找到一个大于这个参数的最小的2的n次方数(比如大于6的最小2的n次幂是8),

    put方法

    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)
                n = (tab = resize()).length;
            if ((p = tab[i = (n - 1) & hash]) == 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))))
                    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))))
                            break;
                        p = e;
                    }
                }
                if (e != null) { // existing mapping for key
                    V oldValue = e.value;
                    if (!onlyIfAbsent || oldValue == null)
                        e.value = value;
                    afterNodeAccess(e);
                    return oldValue;
                }
            }
            ++modCount;
            if (++size > threshold)
                resize();
            afterNodeInsertion(evict);
            return null;
        }
    
    
    • 直接用伪代码表示
    
    
    put()
    {
        index=[hash(key)&(captity-1)]----下标的最大值为captity-1,进行与运算后最终的结果小于等于最大下标
        if(table[index])==null)
            直接添加node
        else
        {
            if(p是treenode)
            {
                直接将节点添加到红黑树    
            }
            else
            {
                如果不是红黑树是链表
                if(p的键值==key)
                    覆盖value
                else
                {
                    遍历链表:
                    {
                        if(有对应的key)
                        {
                            覆盖value
                            break;
                        }
                    }
                   遍历完成后没有发现对应的key
                    {
                        添加到链表
                        if(链表长度>8)
                        {
                            将链表转化为红黑树
                        }
                    }
                }
            }
            if(大小大于阈值)
            {
                容量加倍,重新构造
            }
            
        }
    }
    

    get方法

     public V get(Object key) {
            Node<K,V> e;
            return (e = getNode(hash(key), key)) == null ? null : e.value;
        }
    
        
        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) {
                //如果链表的第一个节点是的键和要查找的键相等,那么返回该node
                if (first.hash == hash && // always check first node
                    ((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;
        }
    

    为什么长度设置为2的n次方

    if ((p = tab[i = (n - 1) & hash]) == null)
                tab[i] = newNode(hash, key, value, null);
    
    • 存放node到table数组的时候,他的下标是通过(n-1)&hash计算出来的(数组长度-1 和 key的hash的值相与,最后结果小于等于长度-1),n为table的长度。
    • 当长度为2的n次幂的时候,(n-1)&hash==hash%n,而前者是位运算,速度会快很多

    负载因子

    • 负载因子较大,说明阈值较大,也就意味着可能发生更多的冲突
    • 负载因子较小,说明阈值较小,也就意味着可能会更少的冲突
    • 发生冲突的时候,会降低hashmap的查找速度,所以当要求更少的内存的时候可以增加负载因子,当要求更高的查找速度的时候,可以减少负载因子。
    • 默认的参数是平衡的选择,所以不建议修改

    我觉得分享是一种精神,分享是我的乐趣所在,不是说我觉得我讲得一定是对的,我讲得可能很多是不对的,但是我希望我讲的东西是我人生的体验和思考,是给很多人反思,也许给你一秒钟、半秒钟,哪怕说一句话有点道理,引发自己内心的感触,这就是我最大的价值。(这是我喜欢的一句话,也是我写博客的初衷)

    作者:jiajun 出处: http://www.cnblogs.com/-new/
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。如果觉得还有帮助的话,可以点一下右下角的【推荐】,希望能够持续的为大家带来好的技术文章!想跟我一起进步么?那就【关注】我吧。

  • 相关阅读:
    环境是如何建立的 启动文件有什么
    环境中存储的是什么
    串行 并行 异步 同步
    TPC-H is a Decision Support Benchmark
    进程通信类型 管道是Linux支持的最初Unix IPC形式之一 命名管道 匿名管道
    删除环境变量
    14.3.2.2 autocommit, Commit, and Rollback 自动提交 提交和回滚
    14.3.2.2 autocommit, Commit, and Rollback 自动提交 提交和回滚
    14.3.2.1 Transaction Isolation Levels 事务隔离级别
    14.3.2.1 Transaction Isolation Levels 事务隔离级别
  • 原文地址:https://www.cnblogs.com/-new/p/7472455.html
Copyright © 2011-2022 走看看