带着问题学Java:2.Java中的Map

图片参考自:http://blog.csdn.net/zhangerqing/article/details/8122075
本篇分析以jdk8为准
首先来看Map的定义:
* An object that maps keys to values. A map cannot contain duplicate keys; * each key can map to at most one value. * * <p>This interface takes the place of the <tt>Dictionary</tt> class, which * was a totally abstract class rather than an interface. * * <p>The <tt>Map</tt> interface provides three <i>collection views</i>, which * allow a map's contents to be viewed as a set of keys, collection of values, * or set of key-value mappings. The <i>order</i> of a map is defined as * the order in which the iterators on the map's collection views return their * elements. Some map implementations, like the <tt>TreeMap</tt> class, make * specific guarantees as to their order; others, like the <tt>HashMap</tt> * class, do not.
Map提供了一种可以通过key映射value的对象,在这种对象里边,是不允许出现重复的key的,每个key也最多只能映射一个value.Map这个接口使用来取代
Dictinary这个抽象类的。这个抽象类在我们看到hashtable的时候会再次提及。Map这个接口提供了三种集合视图,就是说一个map的内容视图可以通过三种方式获取,哪三种呢?第一种是一系列的的key,第二种是一个集合形式的value,第三种是k-v映射。而元素顺序被定义为这个map的集合视图通过迭代器返回元素时的顺序比如HashMap这样。像TreeMap有自己更特殊的顺序。
1.HashMap有什么特点?
看官方注释:
* Hash table based implementation of the <tt>Map</tt> interface. This * implementation provides all of the optional map operations, and permits * <tt>null</tt> values and the <tt>null</tt> key. (The <tt>HashMap</tt> * class is roughly equivalent to <tt>Hashtable</tt>, except that it is * unsynchronized and permits nulls.) This class makes no guarantees as to * the order of the map; in particular, it does not guarantee that the order * will remain constant over time.
HashMap是基于hash表实现Map接口的一个实现类。这个实现类提供了全部的map可选操作。并且允许空值和空键的储存。HashMap大致相当于一个hashtable,区别在于HashMap是不加锁的而且允许key为null存在的。HashMap不保证map中数据的保存数据,尤其是不保证每次的顺序都相同。
2.HashMap的数据是如何保存的?
HashMap用一种非常巧妙的方式实现了数组,链表(在JDK8中,HashMap又引入了红黑树)的结合。

数组在空间上是连续的,查询快,但是增删慢,链表在空间上是分散的,查询慢,但是增删改却很方便。Hash表兼顾了两种数据结构的特点,在保证了较快的查询速度的同时,对数据的增删改速度也很快。HashMap存储数据的方式就是Hash表的一种实现方式,称为拉链法。
在上图我们可以看到,当数据保存进来以后,是挂载在数组的不同索引下的。
当数据进行插入时,首先对获取key的hash值,然后让这个值对数组长度进行取余运算,比如A,B,C三个key的hash值都为12,它们的数据就全都保存在角标为12的这个索引下边。这就是hash碰撞,为了更好的解决这个问题,所以才引入了平衡因子。同时当每个链表中数据大于8个,自动扩展为红黑树结构。数组的中并没有存储真正的数据,它只存储一个链表的头节点,真正的数据保存在一个Entity数组中,它有三部分构成(也可以看成两部分),一个k-v,以及一个next,next永远指向下一个数据的地址。这样就把同一数组下标下的元素串联起来。
3.hashmap如何处理key可以为null吗?value可以为null吗?它是如何处理这些情况的?
hashmap的key可以为null,value也可以为null
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); }
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; }

①.判断键值对数组table[i]是否为空或为null,否则执行resize()进行扩容;
②.根据键值key计算hash值得到插入的数组索引i,如果table[i]==null,直接新建节点添加,转向⑥,如果table[i]不为空,转向③;
③.判断table[i]的首个元素是否和key一样,如果相同直接覆盖value,否则转向④,这里的相同指的是hashCode以及equals;
④.判断table[i] 是否为treeNode,即table[i] 是否是红黑树,如果是红黑树,则直接在树中插入键值对,否则转向⑤;
⑤.遍历table[i],判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操作,否则进行链表的插入操作;遍历过程中若发现key已经存在直接覆盖value即可;
⑥.插入成功后,判断实际存在的键值对数量size是否超多了最大容量threshold,如果超过,进行扩容。
图片和流程引自:http://www.importnew.com/20386.html
如果键是null,我们首先得到调用hash(key),得到hash为0,去查询table[0]这个元素存不存在,果不存在,直接覆盖。
当这个key不存在时,先看下这个存储结构是不是链表,如果是链表,看看插入后长度大不大于8,如果大于,结构变成红黑树,然后插入这个数据。如果不大于,就直接在链表
第一个位置插入这个null的key.
可见,key为null在节点链表中总是排第一个。对value和key都没用限制,都可以为null.
4.hashtable中key可以为null吗?value可以为null吗?为什么会这样?
hashtable中key和value都是不可以为null的。
public synchronized V put(K key, V value) { // Make sure the value is not null if (value == null) { throw new NullPointerException(); } // Makes sure the key is not already in the hashtable. Entry<?,?> tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; @SuppressWarnings("unchecked") Entry<K,V> entry = (Entry<K,V>)tab[index]; for(; entry != null ; entry = entry.next) { if ((entry.hash == hash) && entry.key.equals(key)) { V old = entry.value; entry.value = value; return old; } } addEntry(hash, key, value, index); return null; }
首先先说value,如果value为null,那么在执行时直接就报空指针异常。
那么key呢?key.hashCode()这个方法继承自Object类,这个方法最大的一个特点是没有办法对Null进行hash.所以如果hashtable中如果给key赋值为null,虽然编译时不报错,但是
程序一旦运行就会抛出空指针异常。所以,hashmap在处理key为null时,用三元运算符进行比较,如果key为null,直接将返回0,也就保证了null所在的key永远在链表第一位。
5.hashmap是线程不安全的,号称线程安全的concurrenthashmap它的key和value可以为null吗?
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode());
key和value都不允许为null,否则直接报空指针异常。

浙公网安备 33010602011771号