首页 > 其他分享 >Map 笔记记录

Map 笔记记录

时间:2022-10-03 08:44:08浏览次数:43  
标签:Node Map return 记录 笔记 keySet key final

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

graph LR; A("调用 entrySet() 方法") --> B("获得 EntrySet 类的实例对象") B --> C("调用 iterator()") --> D("获得 EntryIterator 实例对象 ") --"调用next()方法"--> E("返回实际存储数据的 Node 对象") --> F("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

graph LR; A("调用 keySet() 方法") --获得--> B("空的 KeySet 对象") --遍历时使用该对象--> C("返回一个 KeyIterator") --迭代时--> D("返回下一个 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 差异不大,使用方式可以直接在用的时候去查。

标签:Node,Map,return,记录,笔记,keySet,key,final
From: https://www.cnblogs.com/StephenSpace/p/16749980.html

相关文章

  • 第十一篇: GO-数组、切片、Maps、sync.map并发安全map
    数组1、定义数组packagemainimport"fmt"funcmain(){ //定义了一个大小为3的int类型数组 //数组在定义阶段,大小和类型就固定了 vara[3]int//只定义,没有......
  • class 4 笔记
    intsum(intv,inttl,inttr,intl,intr){   if(r<tl||tr<l)       return0;   if(l<=tl&&tr<=r){       returnt[v];......
  • 0-1 bfs 学习笔记
    01bfs是一种可以在\(\mathcal{O}(n+m)\)时间求解只含有\(0\),\(1\)两种边权的单源最短路的算法。这种情形下效率比dijkstra更高,和dijkstra一样好写(甚至更好写一......
  • ABAP语法笔记08 - 事件2和GUI状态
    ATLINE-SELECTION."双击行的时候触发的事件一般用来跳转TOP-OF-PAGEDURINGLINE-SELECTION."双击行显示的次级表单的抬头执行逻辑 GETCURSORFIELDfi......
  • 2022-09-27 计划记录
    摘要:由于最近情况突变,需要重新设定计划.严重等级设置为当前最高等级。内容计划调整:一.保证对mysql内核理解在思想上的融合化需要大段的深入思考的时间需要投入巨大的......
  • MapStruct使用(一)
    官网不同的convert解决方案名字描述mapstruct基于jsr269实现在编译期间生成代码,性能高,精细控制,解耦orika能够精细控制,解耦org.springframewo......
  • SQLMap入门——查询当前用户下的所有数据库
    确定网站存在注入后,用于查询当前用户下的所有数据库pythonsqlmap.py-uhttp://localhost/sqli-labs-master/Less-1/?id=1--dbs  ......
  • SQLMap入门——获取字段内容
    查询完字段名称之后,获取该字段的具体数据信息pythonsqlmap.py-uhttp://localhost/sqli-labs-master/Less-1/?id=1-Dmysql-Tuser-Cuser,password--dump  ......
  • SQLMap入门——获取数据库的所有用户
    列出数据库中的所有用户在当前用户有权读取包含所有用户的表的权限时,使用该命令列出所有管理用户pythonsqlmap.py-uhttp://localhost/sqli-labs-master/Less-1/?id=1......
  • 专升本C语言笔记-2022-10-2
    变量名命名规则:1.变量名只能是英文字母(A-Z,a-z)和数字(0-9)或者下划线(_)组成。               2.第一个字母必须是字母或者下划线开头。 ......