一、常用方法
Map
接口定义了如何存储和检索键值对。一个 Map
对象可以将任意类型的对象作为键(key),并且可以将任意类型的对象作为值(value)。在 Java 中,Map
接口有几个常见的实现: HashMap
、LinkedHashMap
和 TreeMap
。
方法名 |
描述 |
put(K, V) |
将指定的值与该映射中的指定键相关联。如果映射先前包含一个该键的映射关系,则用指定值替换旧值。 |
get(key) |
返回指定键所映射的值;如果此映射不包含该键的映射关系,则返回null 。 |
remove(key) |
如果存在一个键的映射关系,则将其从映射中移除。 |
containsKey(key) |
如果此映射包含指定键的映射关系,则返回true 。 |
containsValue(value) |
如果此映射将一个或多个键映射到指定值,则返回true 。 |
size() |
返回此映射中的键-值映射关系数。 |
isEmpty() |
如果此映射未包含键-值映射关系,则返回true 。 |
clear() |
从此映射中移除所有映射关系(可选操作)。 |
hashCode() |
返回此映射的哈希码值。 |
1、put
方法
将指定的键和值添加到映射中。如果该键已经存在于映射中,则该方法会更新与该键相关的值。
1 2 3 4 5 6 7 8
| public static void main(String[] args) { Map map = new HashMap(); map.put("No1", "张三"); map.put("No1", "李四"); System.out.println(map); }
|
2、get
方法
返回指定键所映射的值;如果此映射不包含该键的映射关系,则返回 null
。
1 2 3 4 5 6 7 8
| public static void main(String[] args) { Map map = new HashMap(); map.put("No1", "张三"); map.put("No1", "李四"); System.out.println(map.get("No1")); }
|
3、remove
方法
从映射中移除指定键的映射关系(如果存在)并返回其对应的值。
1 2 3 4 5 6 7 8
| public static void main(String[] args) { Map map = new HashMap(); map.put("No1", "张三"); map.put("No1", "李四"); System.out.println(map.remove("No1")); System.out.println(map.get("No1")); }
|
4、clear
方法
移除此映射中的所有映射关系。
1 2 3 4 5 6 7 8 9
| public static void main(String[] args) { Map map = new HashMap(); map.put("No1", "张三"); map.put("No2", "李四"); map.put("No3", "李四"); map.clear(); System.out.println(map); }
|
5、size
方法
返回此映射中的映射关系数。
1 2 3 4 5 6 7 8 9
| public static void main(String[] args) { Map map = new HashMap(); map.put("No1", "张三"); map.put("No2", "李四"); map.put("No3", "李四"); int size = map.size(); System.out.println(size); }
|
6、isEmpty
方法
如果此映射不包含任何键-值映射关系,如果此映射为空,则为 true
;否则为 false
。
1 2 3 4 5 6 7 8 9
| public static void main(String[] args) { Map map = new HashMap(); map.put("No1", "张三"); map.put("No2", "李四"); map.put("No3", "李四"); map.clear(); System.out.println(map.isEmpty()); }
|
7、containsKey
方法
查找键是否存在,如果存在则为 true
;否则为 false
。
1 2 3 4 5 6 7 8 9
| public static void main(String[] args) { Map map = new HashMap(); map.put("No1", "张三"); map.put("No2", "李四"); map.put("No3", "李四"); System.out.println(map.containsKey("No1")); System.out.println(map.containsKey("No4")); }
|
8、containsValue
方法
如果此映射包含至少一个指定值,则返回 true
。
1 2 3 4 5 6 7 8 9
| public static void main(String[] args) { Map map = new HashMap(); map.put("No1", "张三"); map.put("No2", "李四"); map.put("No3", "李四"); System.out.println(map.containsValue("张三")); System.out.println(map.containsValue("王五")); }
|
二、Map接口遍历
1、使用keySet获取所有的键
keySet
方法返回一个 Set
,其中包含了 Map
中的所有键。我们可以使用迭代器或增强型 for
循环来遍历这个 Set
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public static void main(String[] args) { Map map = new HashMap(); map.put("No1", "张三"); map.put("No2", "李四"); map.put("No3", "王五"); map.put("No4", "赵六"); Set set = map.keySet(); for (Object key : set) { System.out.println(key + "-" + map.get(key)); } Iterator iterator = set.iterator(); while (iterator.hasNext()) { Object key = iterator.next(); System.out.println(key + "-" + map.get(key)); } }
|
2、把所有的values取出
values
方法返回一个 Collection
,其中包含了 Map
中的所有值。我们可以使用迭代器或增强型 for
循环来遍历这个 Collection
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public static void main(String[] args) { Map map = new HashMap(); map.put("No1", "张三"); map.put("No2", "李四"); map.put("No3", "王五"); map.put("No4", "赵六"); Collection values = map.values(); for (Object obj : values) { System.out.println(obj); } Iterator iterator = values.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); } }
|
3、获取所有关系k-v
entrySet
方法返回一个 Set
,其中包含了 Map
中的所有键值对。每个元素都是一个 Map.Entry
对象,表示一个键值对。我们可以使用迭代器或增强型 for
循环来遍历这个 Set
并访问每个键值对。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public static void main(String[] args) { Map map = new HashMap(); map.put("No1", "张三"); map.put("No2", "李四"); map.put("No3", "王五"); map.put("No4", "赵六"); Set set = map.entrySet(); for (Object obj : set) { Map.Entry entry = (Map.Entry) obj; System.out.println(entry.getKey() + ":" + entry.getValue()); } System.out.println("-----------------------------------------"); Iterator iterator = set.iterator(); while (iterator.hasNext()) { Map.Entry entry = (Map.Entry) iterator.next(); System.out.println(entry.getKey() + ":" + entry.getValue()); } }
|
三、HashMap
HashMap
是 Map
接口的常见实现类。HashMap
以 key-value
对的形式存储数据,内部使用了一个名为 Node
的结构来保存这些键值对。Node
类实际上是一个简单的容器,包含一个键和一个值。
1、基本结构
HashMap 的底层主要依赖于数组和链表(在 JDK 1.8 及以后版本中还引入了红黑树)。
HashMap 底层使用一个数组来存储键值对,每个数组元素称为一个桶(bucket)。在创建 HashMap 时,会初始化这个数组。当多个键的哈希值映射到同一个数组位置时,会形成哈希冲突。为了解决冲突,HashMap 使用链表来存储冲突的键值对。即每个桶内可以包含多个键值对。从 JDK 1.8 开始,当链表长度大于8且数组长度大于64时,链表会转换成红黑树,以提高查询效率。
2、put 方法流程
-
判断键值对数组table是否为空或为null,否则执行resize()进行扩容(初始化)
-
根据键值key计算hash值得到数组索引
-
判断table[i]==null
,条件成立,直接新建节点添加
-
如果table[i]==null
,不成立
4.1 判断table[i]的首个元素是否和key一样,如果相同直接覆盖value
4.2 判断table[i] 是否为treeNode,即table[i] 是否是红黑树,如果是红黑树,则直接在树中插入键值对
4.3 遍历table[i],链表的尾部插入数据,然后判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操 作,遍历过程中若发现key已经存在直接覆盖value
-
插入成功后,判断实际存在的键值对数量size是否超多了最大容量threshold(数组长度*0.75),如果超过,进行扩容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
| 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) treeifyBin(tab, hash); break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) 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; }
|
3、扩容机制
- 在添加元素或初始化的时候需要调用
resize
方法进行扩容,第一次添加数据初始化数组长度为16,以后每次每次扩容都是达到了扩容阈值(数组长度 * 0.75)
- 每次扩容的时候,都是扩容之前容量的2倍;
- 扩容之后,会新创建一个数组,需要把老数组中的数据挪动到新的数组中
- 没有hash冲突的节点,则直接使用
e.hash & (newCap - 1)
计算新数组的索引位置
- 如果是红黑树,走红黑树的添加
- 如果是链表,则需要遍历链表,可能需要拆分链表,判断
(e.hash & oldCap)
是否为0,该元素的位置要么停留在原始位置,要么移动到原始位置+增加的数组大小这个位置上
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
| 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; } else if (oldThr > 0) newCap = oldThr; else { 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 { 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; }
|