Fork me on GitHub

Map

Map接口

image-20230326175355426

基本结论

  • 注意哈(解释并列存在):

    • 我们平时所说的 集合 大部分认为的是 collection 接口,不包括 map 接口

    • 但是,map 算不算是集合呢?

      这是一个翻译导致的问题,因为一些书 翻译集合是包括两者的,collection、map接口

      一部分书,翻译的是 集合就是 Collection,两者一起是叫 集合类、容器、集合框架

翻译问题:

Map是不是集合并不重要,重要的是在我们的工作和学习中应该明确说明和指出集合是指容器(即《java编程思想》中的'集合类') 还是指Collection接口下的实现类

image-20230323101325456

Entry接口

entrySetHashMap 的属性,为了方便 取 key、value,遍历而设置的

hashSet 大体一样,table 还是 Node[] 数组,entry 是 table 索引位置上面存放的链表的数据结构

image-20230323130512688
证明
image-20230323130847415

这个是 entrySet 存放元素的地址,两者地址是一致的,所以是一个指向的指针image-20230323130914954

keySet 其实就是 entrySet 里面的 getKey,当然还有 getValue 方法

所以 keySet 的地址也是一样的image-20230323131519935

Entry、Node、EntrySet之间关系

  • HashMap底层维护的Set<Map.Entry<K,V>> entrySet存储的是HashMap$EntrySet,而这个EntrySet集合里边存储的是HashMap$Node,并且Node是Entry接口的实现类
  • entrySet内部类存储了node节点的引用,返回一个集合,方便遍历
  • entrySet是个方法,entry是个接口,只有set是集合,set<Map.entry>,又因为Node是个链表,实现了entry接口,所以通过多态,可以认为set集合里存放了Node
  • Entry提供了两个方法,getKey(),getValue(),方便进行遍历,获取
  • 存的是newNode(hash, key, value, null),会转换为entry,然后会放在entrySet里边,
    HashMap hashMap = new HashMap();
    hashMap.put("1","a");
    hashMap.put("2","b");

    Set set =hashMap.entrySet();
    System.out.println(set.getClass()); // class java.util.HashMap$EntrySet
    for (Object o : set) {
        System.out.println(o.getClass()); // class java.util.HashMap$Node
        // 为了从HashMap$Node 取出k,v
        Map.Entry o1 = (Map.Entry) o;
        System.out.println(o1.getKey() + "-" + o1.getValue());
    }

六种遍历方式

  1. 取出 keySet 推荐image-20230323222110066
  2. 直接取出 value 不推荐image-20230323222225853
  3. 取出 entrySet 推荐image-20230323222312286

hashMap

线程不安全

  1. k-v最后是 HashMap$Node node = newNode(hash,key,value,null)

  2. k-v为了方便程序员的遍历,还会创建EntrySet 集合,该集合存放的元素的类型Entry,而一个Entry对象就有k,v

    EntrySet<Entry<K,V>>即: transient Set<Map.Entry<K, V>>entrySet;

  3. entrySet中,定义的类型是 Map.Entry ,但是实际上存放的还是 HashMap$Node
    这是因为 static class Node<K,V> implements Map.Entry<K, V>

  4. 当把 HashMap$Node对象存放到entrySet 就方便我们的遍历,因为 Map.Entry提供了重要方法
    K getKey () ; V getValue();

底层原理

其实代码逻辑,源码在 hashSet 已经分析过了,自己去看就行

table 数组 + 节点都是 Node,每一个节点 Node 都实现了 Entry 接口,所以一个 entry相当于一个K_V

image-20230323223834514image-20230323223759470

hashTable(安全)

线程安全的,扩容机制是 2 倍 + 1

hashTable 的  key、value不能为空,会报错 空指针异常

因为 int hash = key.hashCode 这个语句导致的。

image-20230326172324065
底层原理

代码逻辑

  1. 底层 table数组是 HashTable$Entry[] 对象,初始化大小是 11
  2. 临界值 threshold = 8 = 11 * 0.75(默认的初始化大小是 11)
  3. 扩容:按照自己的扩容机制进行即可 2倍+1
  4. 执行方法 addEntry(hash,key,value,index) 添加 k-v 封装到 Entry 对象中
  5. if(count >= threshold) 满足的时候,就进行扩容
  6. 按照 int newCapacity = (oldCapacity << 1) + 1 进行扩容
代码逻辑
1. 无参构造

image-20230326173915237image-20230326173957401

2. put 方法(好像是 头插法,待确定)
image-20230326174358007
3. addEntry 方法(添加到 table 数组位置的,不是添加到 链表)
  • 这个code和size一样,只要添加元素元素就会++,不是添加到table数组上才++
image-20230326174825802
4. rehash 扩容
    protected void rehash() {
        int oldCapacity = table.length;  //旧 table 长度
        Entry<?,?>[] oldMap = table; //旧 table 

        // overflow-conscious code
        int newCapacity = (oldCapacity << 1) + 1;  //扩容   2倍+1
        if (newCapacity - MAX_ARRAY_SIZE > 0) {
            if (oldCapacity == MAX_ARRAY_SIZE)
                // Keep running with MAX_ARRAY_SIZE buckets
                return;
            newCapacity = MAX_ARRAY_SIZE;
        }
        Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];

        modCount++;
        //临界值,扩容之后的长度 * 负载因子(默认是0.75)
        threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
        table = newMap;

        //扩容之后,里面的元素的位置进行调整
        for (int i = oldCapacity ; i-- > 0 ;) {
            for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {
                Entry<K,V> e = old;
                old = old.next;

                int index = (e.hash & 0x7FFFFFFF) % newCapacity;
                e.next = (Entry<K,V>)newMap[index];
                newMap[index] = e;
            }
        }
    }

hashMap、hashTable的区别

image-20230326172109562
  1. Hashtable 的数据结构:
    • Hashtable 是基于哈希表实现的,内部使用一个数组来存储元素。
    • Hashtable 使用哈希函数将键映射到数组的索引位置,并使用链表来解决哈希冲突。
    • 当发生哈希冲突时,新的键值对会添加到冲突位置的链表的末尾。
    • 如果链表的长度超过一定阈值(默认为8),Hashtable 会将链表转换为红黑树,以提高查询和删除操作的性能。
  2. HashMap 的数据结构:
    • HashMap 也是基于哈希表实现的,同样使用一个数组来存储元素。
    • HashMap 使用哈希函数将键映射到数组的索引位置,并使用链表或红黑树来解决哈希冲突。
    • 当发生哈希冲突时,新的键值对会添加到冲突位置的链表或红黑树中,具体取决于链表的长度和当前容量的比值。
    • 如果链表的长度超过一定阈值(默认为8),或者 HashMap 的容量达到一定大小(默认为64),HashMap 会将链表转换为红黑树。
    • 当链表或红黑树中的元素数量较少时,HashMap 会将红黑树转换回链表,以节省空间。

需要注意的是,Hashtable 是线程安全的,对所有公共方法进行了同步处理,因此适用于多线程环境。而 HashMap 不是线程安全的,如果在多线程环境下使用 HashMap,需要进行额外的同步处理或者使用线程安全的 ConcurrentHashMap。

Properties

其实就是我们平常使用的 配置文件<img image
alt="image-20230326180941677" style="zoom: 67%;" />

  • 继承了 hashTable,所以跟 hashTable 大体一样
  • key、value同样是不能为 null

treeMap

逻辑基本和 treeSet 那里介绍的一样

put 方法

比较器,比较key相同的时候,value进行替换,但是 key 不变,不操作,值替换

posted @ 2023-06-17 11:48  小小俊少  阅读(18)  评论(0编辑  收藏  举报