一直使用HashMap,但是 你知道他的实现原理吗?
HashMap的存储结构:
首先 看一张图:
hashmap 的存储结构其实就是 数组加链表的形式来存放数据,这个数组的类型就是Entry。map里面的所有内容都保存在Entry里面。每个数组的元素其实就是一个链表,当哈希值重复的时候,就会将数据存到链表中。形象一点说就是 有好多个桶(数组的元素),当你需要存放一个键值对的时候,就会通过键去拿到对应的哈希值,然后将数据放在和哈希值对应的桶(数组的下标)里面,这个桶里面的存放形式是链表。
HashMap的put方法: public V put(K key, V value) { return putVal(hash(key), key, value, false, true); }
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
可以看到在hashmap保存值的时候,首先会调用hash方法,判断key是否为空,如果为空返回0,否在继续调用hashCode方法获取这个key的哈希值,最后返回。获取到哈希值后,会调用putVal方法,将要保存的值按照哈希值存放在对应的数组中,(注意,保存的不仅仅是value,HashMap存储着Entry(hash, key, value, next)对象。)。hashmap可以允许key和value为null,当key为null的时候,他的哈希值就为0,保存在数组的第一个位置上。
如果put的时候计算出来key的哈希值有重复怎么办:
上面说存储结构的时候说过,有一个数组,数组的每一个元素都是一个链表。当通过key计算出来的哈希值有重复,还是会找到对应的桶(数组的下标),然后和桶中的元素进行判断,如果两个key相同,则覆盖原来的值,否则 将值插入到链表中。每次插入的元素位于链表的最前面。
hashmap的get方法: public V get(Object key) { Node<K,V> e; return (e = getNode(hash(key), key)) == null ? null : e.value; } /** * 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; if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) { 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; }
get方法会根据对应的key 然后计算出来哈希值,然后找到对应的数组元素,然后遍历链表,取出和key 相同的值,找不到则返回null。
hashmap 的 resize
当hashmap中的元素越来越多的时候,key的哈希值相同的就会越来越多(因为桶是固定的),所以为了查询的效率,就要对hashmap进行扩容,扩容也是最耗性能的,在扩容的时候,会创建一个是原来数组二倍的数组,然后将原来数组中的元素必须重新计算在新数组中的位置,然后存放在新数组对应的下标中。那么hashmap什么时候扩容呢,当hashmap中的元素个数超过数组大小为loadFactor时,就会进行数组扩容,loadFactor的默认值为0.75,16*0.75= 12,当hashmap中的元素超过12的时候,就会对数组进行扩容。因为这样是一个非常耗性能的操作,所以在使用hashmap的时候,需要对存入的元素有一个 估计,然后通过hashmap的构造方法指定数组的长度,比如new HashMap(1024);
总结:
利用key的hashcode计算出哈希值,也就是当前元素位于数组的什么地方
存储时,当key的哈希值相同,有两种情况 1,key相同,覆盖掉原来的数据,2,key不相同(出现碰撞),将当前的数据存放在链表中。
读取时,还是获取key的哈希值,找到数组中对应的下标,然后遍历链表,返回key对应的值。
理解了以上就不难明白hashmap 是怎么解决哈希值冲突的问题。原理就是运用了数组和链表。当哈希值冲突时,就会在冲突的那个下标中 比较key是否重复(数组中每个元素就是一个链表),重复侧覆盖值,不重复则为冲突,添加进链表。