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

来源:互联网 发布:list<map>转json 编辑:程序博客网 时间:2024/06/07 08:38

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

java基础解析系列

  • java基础解析系列(一)---String、StringBuffer、StringBuilder
  • java基础解析系列(二)---Integer
  • java基础解析系列(三)---HashMap
  • 这是我的博客目录,欢迎阅读

基本概念

  • 节点: 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的查找速度,所以当要求更少的内存的时候可以增加负载因子,当要求更高的查找速度的时候,可以减少负载因子。
  • 默认的参数是平衡的选择,所以不建议修改


原创粉丝点击