Map 笔记记录
Map
Map 是一个存放二元 Key - Value 对的数据集合接口。在其中每个元素都对应于一个唯一的 key,使用 key 可以获得对应的 value。
其有如下两个常用实现类:
-
HashMap
-
TreeMap
Map 接口的常用方法:
-
size()
-
containsKey(Object)
andcontainsValue(Object)
-
get(Object K)
-
put(K, V)
andputAll(Map<? extends K, ? extends V>)
-
keySet()
andvalues()
-
entrySet()
-
merge()
其还有一个重要的内部接口:
Entry
HashMap
HashMap 的基础部分在 HashSet 中已经阐述的很明白了,在此主要理解其中重要的内部类和一些重要的方法。
重要的内部类:
-
EntrySet
-
KeySet
-
Values ( Collection ):他的思路和 EntrySet 以及 KeySet 类似,就不赘述
常用方法
EntrySet
在 HashMap 类中,有一个变量 transient Set<Map.Entry<K,V>> entrySet;
存放着关于一个个指向 HashMap$Node<K,V>
的 Map.Entry<K,V>
引用对象。
获取 entrySet 的方法是调用 entrySet()
方法,该方法返回一个 EntrySet 的实例对象:
public Set<Map.Entry<K,V>> entrySet() { Set<Map.Entry<K,V>> es; return (es = entrySet) == null ? (entrySet = new EntrySet()) : es; } final class EntrySet extends AbstractSet<Map.Entry<K,V>>{...}
EntrySet
就是存放一个个 Map.Entry<K,V>
集合的对象,其最重要的方法是 iterator()
,它返回了一个 EntryIterator
对象,这个就是实际的遍历器。
public final Iterator<Map.Entry<K,V>> iterator() { return new EntryIterator(); } final class EntryIterator extends HashIterator implements Iterator<Map.Entry<K,V>> { // 可以看到,其 next 方法实际就是返回一个个 Node<K,V> 节点 public final Map.Entry<K,V> next() { return nextNode(); } } final Node<K,V> nextNode() { Node<K,V>[] t; Node<K,V> e = next; if (modCount != expectedModCount) throw new ConcurrentModificationException(); if (e == null) throw new NoSuchElementException(); if ((next = (current = e).next) == null && (t = table) != null) { do {} while (index < t.length && (next = t[index++]) == null); } return e; }
也就是说,其实 entrySet 并没有维护一个实际的 Set 集合数据,而是存放了一个空的 EntrySet
对象。
该对象可以调用 iterator()
方法,返回一个 EntryIterator()
迭代器,该迭代器的 next()
方法可以返回一个个 Node<K,V>
对象,但是该对象被封装成了Map.Entry<K,V>
,相当于一个 Map.Entry<K,V>
类型的引用指向了 Node<K,V>
对象。
为什么能做到这样呢?因为 Node<K,V>
类是 Map.Entry<K,V>
接口的实现类:
static class Node<K,V> implements Map.Entry<K,V> {....}
KeySet
首先摆上源码:
KeySet定义:
KeySet 是 HashMap 中的一个内部类
final class KeySet extends AbstractSet<K> { public final int size() { return size; } public final void clear() { HashMap.this.clear(); } // 这个方法用于返回迭代器,也是这个类最关键的方法之一 public final Iterator<K> iterator() { return new KeyIterator(); } public final boolean contains(Object o) { return containsKey(o); } public final boolean remove(Object key) { return removeNode(hash(key), key, null, false, true) != null; } public final Spliterator<K> spliterator() { return new KeySpliterator<>(HashMap.this, 0, -1, 0, 0); } public final void forEach(Consumer<? super K> action) { Node<K,V>[] tab; if (action == null) throw new NullPointerException(); if (size > 0 && (tab = table) != null) { int mc = modCount; for (int i = 0; i < tab.length; ++i) { for (Node<K,V> e = tab[i]; e != null; e = e.next) action.accept(e.key); } if (modCount != mc) throw new ConcurrentModificationException(); } } }
keySet()
方法,用于返回一个 KeySet 对象,实际上该对象是空的:
public Set<K> keySet() { Set<K> ks = keySet; if (ks == null) { ks = new KeySet(); keySet = ks; } return ks; }
每次调用 keySet 方法时,其实都是返回一个空的 KeySet 对象。这么做的目的其实也是因为实际上无需开辟一个新的空间来维护 keySet,否则每次在 HashMap 对象插入元素时都要维护一次 keySet 对象( values 同理 ),非常麻烦。
我们只需要保证,我们能够单独的拿到 HashMap 的每一个 key 就行。
作者在 KeySet 对象中重写了 public final Iterator iterator() { return new KeyIterator()}
方法。该方法返回一个 KeyIterator()
对象,该对象的源码如下:
final class KeyIterator extends HashIterator implements Iterator<K> { // nextNode 是用于获取下一个 Node<K,V> 对象的 // 调用这个 next() 方法会在迭代时只返回 Node<K,V> 对象的 key // 可以看到该类只重写了 next() 这一个方法 public final K next() { return nextNode().key; } }
现在我们反过来思考:keySet 之于我们的意义是什么?或者说为什么要创建这么一个变量?
我认为 keySet 的意义就是方便我们单独遍历 HashMap 中的 key。我们使用 keySet 的场景无非就是遍历 keySet( Set 无法使用 get(index)
单独取某个元素 )。
而一个封装的迭代器的 next()
方法就能实现,所以无需单独维护一个 keySet 表,只需要在逻辑层面将 keySet 能够分离出来即可。
Hashtable
Hashtable 是一个二元无序集合,与 HashMap 类似,其添加数据也是存储到一个table
数组中,同时其也是由 数组+链表 构成。但其不接受 null
做 key 或 value。
其内部实际存储数据的内部类并不是 Node<K,V>
,而是 Entry<K,V>
,但其实差不多。
private static class Entry<K,V> implements Map.Entry<K,V> { final int hash; final K key; V value; Entry<K,V> next; .... }
其遍历器也是 Enumertator 类:
// 调用 getIterator 方法后,会返回一个 Enumerator 对象 // 这个 type 是用来控制遍历时是遍历 key / value / entry, // 与之前的 keySet 中的迭代器作用一样 private <T> Iterator<T> getIterator(int type) { if (count == 0) { return Collections.emptyIterator(); } else { return new Enumerator<>(type, true); } } // 这是 Enumerator 中的 nextElement 的返回语句 // 这一个语句就能做到返回不同的值,实现 keySet / values / EntrySet return type == KEYS ? (T)e.key : (type == VALUES ? (T)e.value : (T)e);
当然,值得一提的是 Hashtable 类中的 put()
方法加入元素的形式与 HashMap 有区别:
Hashtable 中如果由 hash 值计算得到的 table 的 index 相同,且 key 不同时,执行的是在当前 index 位置维护的链的头部加加入新的节点,然后将之前的链加到新放入的节点之后。
而在 HashMap 中是插入到尾部。
当然,其扩容的机制也有区别,但差异不大,此处就不赘述。
Properties
Properties 是 Hashtable 的子类:
class Properties extends Hashtable<Object,Object>{...}
其作用主要是用来读取配置文件 xxx.properties
,设置程序中的参数。其方法与 Hashtable 差异不大,使用方式可以直接在用的时候去查。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通