在java.util包中提供了一些集合类,这些集合类又被称为容器。集合是用来存放对象的引用。常用的集合有List集合,Set集合以及Map集合,其中List与Set继承了Collection接口,各接口还提供了不同的实现类,如图:
List和Set的知识点在网上比比皆是,也不太复杂,此文章就主要探索一下Map的一些知识点。
Map是没有继承Collection接口的,其提供的是key到value的映射。Map中不能包含相同的key,每个key只能映射一个value。key还决定了存储对象在映射中的存储位置,但不是由key本身决定的,而是通过一种“散列技术”进行处理。产生了一个散列码的整数值,散列码通常用作一个偏移量,该偏移量对应分配给映射的内存区域的起始位置,从而确定存储对象在映射中的存储位置。
Map接口提供了将key映射到值的对象。一个映射不能包含重复的key,每个key最多只能映射到一个值。Map接口中同样提供了集合的常用方法。如图:
Map集合中允许值对象是null,而且没有个数的限制。map.put(1,null),map.put(2,null)都是可以的。
一、比较
Map中常用的实现类有HashMap和TreeMap,在运用的时候建议用HashMap来实现Map集合,即Map map = new HashMap( )。因为由HashMap类实现的Map集合添加和删除映射关系效率更高。
1、HashMap是基于哈希表的Map接口的实现,HashMap通过哈希表对其内部的映射关系进行了快速查找。
注意:HashMap允许使用null值和null键,即key和value都可以为null,但是必须要保证键的唯一性,即key为null的键值对只能有一个。此类不保证映射的顺序,特别是它不保证该顺序一直不变。
2、TreeMap中的映射关系存在一定的顺序,如果你希望Map集合中的对象也存在一定的顺序,应该使用TreeMap类来实现Map集合,即Map map = new TreeMap( )。
注意:TreeMap类不仅实现了Map接口,还实现了java.util.SortedMap接口,因此,在集合中的映射关系具有一定的顺序。但是在添加、删除和定位映射关系时,TreeMap比HashMap的性能稍差。由于TreeMap类实现的Map集合中的映射关系是根据键对象按照一定的顺序排列的,因此不允许键的对象为null,即key不能为null。
比对一个HashMap和TreeMap的源码所实现的类:
HashMap:
TreeMap:
看了源码之后,HashMap,TreeMap和Map的更加详细的关系应该是,如图:
二、HashMap的实现原理(重点)
在java语言中,最基本的结构就是数组,引用(模拟指针)。所有的数据结构都可以用这两个基本结构来构造的,HashMap也是一样的。但是实际上,HashMap就是一个“散列链表”的数据结构,即是链表和数组的结合体。
从源码来做分析:
其实不难看出,HashMap的底层就是一个数组结构,数组中的每一项又是一个单独的链表。当新建一个HashMap时,就会初始化一个Node<K,V>[ ]数组。
Node就是数组中的元素,每个Map.Entry其实就是一个key-value的键值对,它拥有一个指向下一个元素的引用(this.next = next,next实质上还是一个Node元素),这就构成了链表。
1、储存
第一步:先将key进行hash算法:
第二步:调用的是putVal( )方法:
/** * Implements Map.put and related methods * * @param hash hash for key * @param key the key * @param value the value to put * @param onlyIfAbsent if true, don't change existing value * @param evict if false, the table is in creation mode. * @return previous value, or null if none */ final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; //1.判断tab是否为空,是否还有剩余空间,是否需要扩容操作 if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; //2.给p赋值,值为一个节点的位置,如果其为空,就创建一个新节点, // 调用的LinkedHashMapde的newNode方法 if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else { Node<K,V> e; K k; //3.比较hash值是否相同 if (p.hash == hash && //4.比较key的实例是否相同 ((k = p.key) == key || //5.比较key的值 (key != null && key.equals(k)))) e = p;//6.如果值已经存在,则直接覆盖 else if (p instanceof TreeNode)
//7.如果p是一个TreeNode对象的话,就在红黑树中直接插入键值对 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else { //8.开始遍历链表,准备插入 for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { //9.创建要进行插入的节点 p.next = newNode(hash, key, value, null); //10.如果链表的长度大于了8,就进行链表->红黑树的转换操作 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } //11.如果存在key值,则直接赋值覆盖掉value if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } //12.以上的过程都是给e这个Node<K,V>赋值 if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } //13.判断是否超过最大容器,需要扩容 ++modCount; if (++size > threshold) resize(); afterNodeInsertion(evict); return null; }
这里对put方法只是有一个简单的说明,如果想要更加深入研究的同学,可以阅读以下文章:
1). https://www.cnblogs.com/jzb-blog/p/6637823.html
2). https://blog.csdn.net/AJ1101/article/details/79413939
2.获取
第一步:调用getNode方法,根据key返回的hash值和key值查询Node节点。
第二步:判断e这个节点是否为null,如果为null就返回NULL值,如果不为null就返回它的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) { //1.根据key搜索节点的方法,(判断key相等的条件:hash相同且符合equals方法) 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) { //2.根据输入的hash值,可以直接计算出对应的下标(n-1)&hash,缩小查询范围, // 如果存在结果,则必定在table的这个位置上 if (first.hash == hash && // always check first node ((k = first.key) == key || (key != null && key.equals(k)))) //3.判断第一个存在的节点的key是否和查询的key相等,如果相等,直接返回该节点 return first; if ((e = first.next) != null) { //4.遍历该链表(或红黑树)直到next为null if (first instanceof TreeNode) //5.当这个table节点上存储的是红黑树结构时, // 在根节点first上调用getTreeNode方法, // 在内部遍历红黑树节点,查看是否有匹配的TreeNode return ((TreeNode<K,V>)first).getTreeNode(hash, key); do { //6.如果当这个table节点上存储的是链表结构时,用跟第3点的方式去判断key是否相同 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e; } while ((e = e.next) != null); //7.如果key不同,则一直遍历下去直到链表尽头,直到e.next == null } } return null; }
3.删除
第一步:调用removeNode方法
第二步:判断e这个节点是否为null,如果为null就返回null值,如果不为null就返回其value值
/** * Implements Map.remove and related methods * * @param hash hash for key * @param key the key * @param value the value to match if matchValue, else ignored * @param matchValue if true only remove if value is equal * @param movable if false do not move other nodes while removing * @return the node, or null if none */ final Node<K,V> removeNode(int hash, Object key, Object value, boolean matchValue, boolean movable) { Node<K,V>[] tab; Node<K,V> p; int n, index; if ((tab = table) != null && (n = tab.length) > 0 && (p = tab[index = (n - 1) & hash]) != null) { Node<K,V> node = null, e; K k; V v; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) node = p; else if ((e = p.next) != null) { if (p instanceof TreeNode) node = ((TreeNode<K,V>)p).getTreeNode(hash, key); else { do { if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { node = e; break; } p = e; } while ((e = e.next) != null); } } if (node != null && (!matchValue || (v = node.value) == value || (value != null && value.equals(v)))) { if (node instanceof TreeNode) ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable); else if (node == p) tab[index] = node.next; else p.next = node.next; ++modCount; --size; afterNodeRemoval(node); return node; } } return null; }
4.扩容方法
final Node<K,V>[] resize() { Node<K,V>[] oldTab = table; int oldCap = (oldTab == null) ? 0 : oldTab.length; int oldThr = threshold; int newCap, newThr = 0; if (oldCap > 0) { if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1; // double threshold } else if (oldThr > 0) // initial capacity was placed in threshold newCap = oldThr; else { // zero initial threshold signifies using defaults newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } if (newThr == 0) { float ft = (float)newCap * loadFactor; newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); } threshold = newThr; @SuppressWarnings({"rawtypes","unchecked"}) Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; table = newTab; if (oldTab != null) { for (int j = 0; j < oldCap; ++j) { Node<K,V> e; if ((e = oldTab[j]) != null) { oldTab[j] = null; if (e.next == null) newTab[e.hash & (newCap - 1)] = e; else if (e instanceof TreeNode) ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); else { // preserve order Node<K,V> loHead = null, loTail = null; Node<K,V> hiHead = null, hiTail = null; Node<K,V> next; do { next = e.next; if ((e.hash & oldCap) == 0) { if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } else { if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null); if (loTail != null) { loTail.next = null; newTab[j] = loHead; } if (hiTail != null) { hiTail.next = null; newTab[j + oldCap] = hiHead; } } } } } return newTab; }
扩容太过复杂,请参照
1). https://blog.csdn.net/mymilkbottles/article/details/76576367
2). https://blog.csdn.net/u012961566/article/details/72963157
结合理解。
5.清除
/** * Removes all of the mappings from this map. * The map will be empty after this call returns. */ public void clear() { Node<K,V>[] tab; modCount++; if ((tab = table) != null && size > 0) { size = 0; for (int i = 0; i < tab.length; ++i) //遍历循环,所有设置为null tab[i] = null; } }