Map 笔记记录

Map

Map 是一个存放二元 Key - Value 对的数据集合接口。在其中每个元素都对应于一个唯一的 key,使用 key 可以获得对应的 value。

其有如下两个常用实现类:

  • HashMap

  • TreeMap

Map 接口的常用方法:

  • size()

  • containsKey(Object) and containsValue(Object)

  • get(Object K)

  • put(K, V) and putAll(Map<? extends K, ? extends V>)

  • keySet() and values()

  • entrySet()

  • merge()

其还有一个重要的内部接口:

  • Entry

HashMap

HashMap 的基础部分在 HashSet 中已经阐述的很明白了,在此主要理解其中重要的内部类和一些重要的方法。

重要的内部类:

  • EntrySet

  • KeySet

  • Values ( Collection ):他的思路和 EntrySet 以及 KeySet 类似,就不赘述

常用方法

EntrySet

调用next()方法

调用 entrySet() 方法

获得 EntrySet 类的实例对象

调用 iterator()

获得 EntryIterator 实例对象

返回实际存储数据的 Node 对象

Node 对象被修饰成 Map.Entry

在 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 对象

返回一个 KeyIterator

返回下一个 Node 对象的 key

首先摆上源码:

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 差异不大,使用方式可以直接在用的时候去查。

posted @   Stephen-zhang  阅读(21)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示