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

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

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

初始化
HashMash中常量:


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; }
来源:https://www.cnblogs.com/whllow/p/12189718.html