【Java基础】分析Map接口常用实现类


==比较的是地址值,而不是HashCode,所以这里以后千万不要掉进误区了。!!!

Java集合架构图

点击放大查看
在这里插入图片描述
List , Set, Map都是接口,List , Set继承至Collection接口(Collection继承至Iterable),Map为独立接口

Map接口简介

  1. Map不是collection的子接口或者实现类。Map是一个接口。

  2. Map 的 每个 Entry 都持有两个对象,也就是一个键一个值,Map 值可以相同,但键肯定是唯一的

  3. Map 接口最流行的几个实现类是HashMap、LinkedHashMap、Hashtable 和 TreeMap(HashMap、TreeMap最常用)

Map接口的基本操作

方法 描述
int size()获取Map集合大小(即元素数量)
boolean isEmpty() 判断是否为空
boolean containsKey(Object key) 判断是否包含某个键
boolean containsValue(Object value) 判断是否包含某个值
V get(Object key) 获取某个键对应的值
V put(K key, V value) 添加键值对(K,V)
V remove(Object key) 移除某个键对应的键值对
void putAll(Map<? extends K, ? extends V> m) 添加另一个Map集合
void clear() 清空所有键值对
Set keySet() 获取键的集合
Collection values() 获取值的集合
Set<Map.Entry<K, V>> entrySet() 获取键值对实体的集合
interface Entry<K,V> Map中的内部接口

Map接口直接或者间接的实现类

map集合 说明
HashMap Map基于散列表(hash表)的实现。插入和查询“键值对”的开销是固定的。可以通过构造器设置容量capacity负载因子load factor,以调整容器的性能。
LinkedHashMap 类似于HashMap,但是迭代遍历它时,取得“键值对”的顺序是其插入次序,或者是最近最少使用(LRU)的次序。只比HashMap慢一点。而在迭代访问时反而更快,因为它使用链表维护内部次序
TreeMap 底层是二叉树数据结构线程不同步,可用于给Map集合中的键进行排序
EnumMap 集合中的所有key必须是同一个枚举类的实例。当EnumMap创建后,会表现成一个数组array,这种表现方式是紧凑高效的。EnumMap的顺序,不是由添加时顺序决定,而是由枚举类内部定义的枚举字段顺序决定。
HashTable HashMap是Hashtable的轻量级实现,非线程安全的实现他们都实现了map接口,主要区别是HashMap键值可以为空null,效率可以高于Hashtable。
ConcurrentHashMap ConcurrentHashMap通常只被看做并发效率更高的Map,用来替换其他线程安全的Map容器,比如Hashtable和Collections.synchronizedMap。
WeakHashMap 弱键(weak key)MapMap中使用的对象也被允许释放: 这是为解决特殊问题设计的。如果没有map之外的引用指向某个“键”,则此“键”可以被垃圾收集器回收
IdentifyHashMap 使用==代替equals()对“键”作比较的hash map
ArrayMap(安卓) ArrayMap是一个<key,value>映射的数据结构,它设计上更多的是考虑内存的优化内部是使用两个数组进行数据存储,一个数组记录key的hash值,另外一个数组记录Value值,它和SparseArray一样,也会对key使用二分法进行从小到大排序,在添加、删除、查找数据的时候都是先使用二分查找法得到相应的index,然后通过index来进行添加、查找、删除等操作,所以,应用场景和SparseArray的一样,如果在数据量比较大的情况下,那么它的性能将退化至少50%
SparseArray(安卓) SparseArray比HashMap更省内存,在某些条件下性能更好,主要是因为它避免了对key的自动装箱(int转为Integer类型),它内部则是通过两个数组来进行数据存储的,一个存储key,另外一个存储value,为了优化性能,它内部对数据还采取了压缩的方式来表示稀疏数组的数据,从而节约内存空间。
HashMap
  • 底层结构
    版本 结构 数组类型 初始化容量
    Java1.6/1.7 位桶(数组) + 链表 Entry 16
    Java1.8 位桶(数组) + 链表 + 红黑树(当链表长度超过阈值 “8” 时,将链表转换为红黑树 Node 0

1.8前使用位桶和链表实现
在这里插入图片描述
1.8中HashMap是以数组+链表+红黑树的模式存储(当链表长度超过阈值 “8” 时,将链表转换为红黑树),当同一个hash值(Table上元素)的链表节点数不小于8时,将不再以单链表的形式存储了,它是线程不安全的Map,并允许使用 Null值Null键, 方法上都没有synchronize关键字修饰,具体可以参考HashMap1.7和1.8的区别
在这里插入图片描述

Hashtable
  • Hashtable是线程安全的一个Map,,它在各个方法上添加了synchronize关键字但是现在已经不再推荐使用HashTable了,因为现在有了ConcurrentHashMap这个专门用于多线程场景下的map实现类,其大大优化了多线程下的性能。
  • keyvalue不允许为空,线程安全,效率低
LinkedHashMap
  • LinkedHashMap底层数据结构是 哈希表和链表哈希表保证键唯一链表保证 有序,根据插入顺序存储
		LinkedHashMap<Integer, String> linkedHashMap= new LinkedHashMap<Integer, String>();
		linkedHashMap.put(01, "张三1");
		linkedHashMap.put(02, "张三2");
		linkedHashMap.put(03, "张三3");
		linkedHashMap.put(04, "张三4");
		linkedHashMap.put(05, "张三5");
		
		Set<Integer> keys = linkedHashMap.keySet();
		for (Integer key : keys) {
			System.out.println(key + "|" + linkedHashMap.get(key));
		}

执行结果:
在这里插入图片描述

TreeMap
  • 基于红黑树(Red-Black tree)的 NavigableMap 实现。该映射根据其键的自然顺序进行排序,或者根据创建时提供的 Comparator (内比较器)进行排序

  • 排序方式类似于TreeSet,分为自然排序Comparable比较器排序Comparator,具体排序取决于在构造器中使用使用比较器

默认自然排序(自然顺序,从小到大)

TreeMap<Integer, String> treeMap= new TreeMap<Integer, String>();
		treeMap.put(24, "Hello1");
		treeMap.put(14, "Hello2");
		treeMap.put(34, "Hello3");
		treeMap.put(124, "Hello4");
		treeMap.put(24, "Hello5");
		treeMap.put(24, "Hello6");
		treeMap.put(24, "Hello7");
		treeMap.put(244, "Hello8");
		treeMap.put(624, "Hello9");
		treeMap.put(24, "Hello10");
		Set<Integer> keys = treeMap.keySet();
		for (Integer key : keys) {
			String value = treeMap.get(key);
			System.out.println(key + "|" + value);
		}

执行结果
在这里插入图片描述

比较器排序(使用Comparatpr倒序)

	    TreeMap<Integer, String> treeMap = new TreeMap<Integer, String>(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2.compareTo(o1);
            }
        });
        treeMap.put(24, "Hello1");
        treeMap.put(14, "Hello2");
        treeMap.put(34, "Hello3");
        treeMap.put(124, "Hello4");
        treeMap.put(24, "Hello5");
        treeMap.put(24, "Hello6");
        treeMap.put(24, "Hello7");
        treeMap.put(244, "Hello8");
        treeMap.put(624, "Hello9");
        treeMap.put(24, "Hello10");
        Set<Integer> keys = treeMap.keySet();
        for (Integer key : keys) {
            String value = treeMap.get(key);
            System.out.println(key + "|" + value);
        }

执行结果
在这里插入图片描述

  • TreeMap是NavigableMap接口实现类,NavigableMap接口继承了SortedMap(继承了Map)即一个支持排序的map,所以treemap才会支持排序

Treemap四个构造方法

  • 无参 默认实现key的自然排序
  • 有参 参数为comparator比较器 实现定制排序
  • 有参 参数为map 实现key的自然排序
  • 有参 参数为map,comparator比较器 实现key的定制排序

自然排序: 要求待添加元素必须实现compareable接口并实现compareTo方法
自定义排序: 要求待添加元素无序实现compareable接口,但创建Treemap对象时必须传入comparator的比较器,并实现compare方法(匿名内部类)

WeakHashMap
  • WeakHashMap 以弱键实现的,它是Key和Value都可以是null,的非同步且无序的散列表(hash哈希表)

  • Map中如果这个Key值指向的对象没被使用(除了自身有对key的引用外,此key没有其他引用那么此map会自动丢弃此值,然后被GC回收 ),此时触发了GC,该对象就会被回收掉的。

  • HashMap的key保留了对实际对象的强引用,这意味着只要HashMap对象不被销毁,还HashMap的所有key所引用的对象就不会被垃圾回收,HashMap也不会自动删除这些key所对应的key-value对;

  • WeakHashMap的key只保留对实际对象的弱引用,这意味着如果WeakHashMap对象的key所引用的对象没有被其他强引用变量所引用,则这些key所引用的对象可能被垃圾回收,WeakHashMap也可能自动删除这些key所对应的key-value对。

  • WeakHashMap中的每个key对象只持有对实际对象的弱引用,因此,当垃圾回收了该key所对应的实际对象之后,WeakHashMap会自动删除该key对应的key-value对。

  • 原理主要是使用的WeakReferenceReferenceQueue实现的,其key就是weakReference,而ReferenceQueue中保存了被回收的 Key-Value。

  • 如果当其中一个Key-Value不再使用被回收时,就将其加入ReferenceQueue队列中。当下次再次调用该WeakHashMap时,就会去更新该map,比如ReferenceQueue中的key-value,将其中包含的key-value全部删除掉。这就是所谓的“自动删除”

HashMap和WeakHashMap的区别也在于此,HashMap的key是对实际对象的强引用

  • 弱引用(WeakReference)的特性是:当gc线程发现某个对象只有弱引用指向它,那么就会将其销毁并回收内存。WeakReference也会被加入到引用队列queue中。
        WeakHashMap<String,String> whm = new WeakHashMap<>();
        whm.put(new String("hello1"), "world1");
        whm.put(new String("hello2"), "world2");
        whm.put(new String("hello3"), "world3");
        whm.put("hello4", "world3");
        System.out.println(whm);
        // System.gc()来通知JVM进行垃圾回收
        System.gc();
        System.runFinalization();
        System.out.println(whm);

执行结果
在这里插入图片描述

EnumMap
  • EnumMap,该类是专门针对枚举类设计的一个集合类。创建EnumMap是必须指定一个枚举类,从而将该EnumMap和指定枚举类关联起来。
  • 该Map在内部以数组的形式保存,所以这种实现形式非常紧凑、高效
  • EnumMap不允许key为空,value可以为空
  • EnumMap的顺序,不是由添加时顺序决定,而是由枚举类内部定义的枚举字段顺序决定。
  • 线程不安全,最好在创建的时候调用Collections.synchronizedMap方法来进行同步。
class TestEnumMap {
    public static void main(String[] args) {
        //在创建EnumMap时必须显示或隐式指定它对应的枚举类
        EnumMap<Direction, String> enumMap = new EnumMap<>(Direction.class);

        // 所有的key都必须是单个枚举类的枚举值
        enumMap.put(Direction.UP, "向上移动");
        enumMap.put(Direction.DOWN, "向下移动");
        enumMap.put(Direction.LEFT, "向左移动");
        enumMap.put(Direction.RIGHT, "向右移动");

        //EnumMap根据key的自然顺序(枚举值在枚举类的定义顺序)来维护key-value对的顺序
        for (Map.Entry<Direction, String> entry : enumMap.entrySet())
            System.out.println(entry.getKey() + "-----" + entry.getValue());
    }

    /**
     * 内部枚举类
     */
    enum Direction {
        UP,
        LEFT,
        DOWN,
        RIGHT;
    }
}

执行结果:
在这里插入图片描述
插入EnumMap的顺序是UP,DOWN,LEFT,RIGHT 但是实际打印顺序是 UP,LEFT,DOWN,RIGHT,可以看出 EnumMap的顺序是不是由添加时顺序决定, 而是有枚举类中定义的字段顺序决定

IdentifyHashMap

IdentityHashMap不是Map的通用实现,它有意违反了Map的常规协定。是一个可以添加重复key的Map实现类, 并且key/value都允许为空,且是线程不安全

  1. IdentityHashMap 和 HashMap区别

    • IdentifyHashMap和HashMap差不多,唯一的区别就是在底层判断key的方式不一样
    • HashMap类判断键k1和k2相等的条件为p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))) : 先判断hashCode是否相同,如果hashCode相同,在用equals判断值是否相同
    • IdentifyHashMap判断k1和k2相等的条件是(item == k)判断内存地址是否相等
      (上面红色均为HashMap,IdentityHashMap部分源码)
  2. 对比HashMap和IdentityHashMap 添加元素区别
    HashMap(java.8)

    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) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

可以得出结论
if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))

1. 通过新key的hashCode()方法,计算出哈希码,然后从Node数组中找到对应的位置,若为null就放进去。若已经有值了,请看第二步
2. 调用新key的equals()方法去和已经存在的key比较,如果返回ture 。则视新键与已经存在的键相同,用新值去更新旧值,然后put方法返回旧值
3. 若调用equals()返回false,则认为新键和已存在的键不一样,那就会新建一个Node节点,放在此链表里

HashMap的put()方法返回null的特殊情况:

1. 要是已经存在键的映射,但是值是null,那么调用put()方法再更新键的值时, put()方法会把旧值null返回(因为旧值为null,所以很特殊)
2. 要是找到的位置上没有键的映射,put()方法也是返回null

IdentityHashMap


    public V put(K key, V value) {
        final Object k = maskNull(key);

        retryAfterResize: for (;;) {
            final Object[] tab = table;
            final int len = tab.length;
            int i = hash(k, len);

            for (Object item; (item = tab[i]) != null;
                 i = nextKeyIndex(i, len)) {
                if (item == k) {
                    @SuppressWarnings("unchecked")
                        V oldValue = (V) tab[i + 1];
                    tab[i + 1] = value;
                    return oldValue;
                }
            }

            final int s = size + 1;
            // Use optimized form of 3 * s.
            // Next capacity is len, 2 * current capacity.
            if (s + (s << 1) > len && resize(len))
                continue retryAfterResize;

            modCount++;
            tab[i] = k;
            tab[i + 1] = value;
            size = s;
            return null;
        }
    }

可以得出结论
if (item == k) {}
IdentityHashMap比较key值,直接使用的是==,只要两个对象的内存地址相同即会覆盖旧的key/value

示例代码
测试对象Demo,用于给HashMap以及IdentityHashMap做key

public class Demo {
        private Integer num;
        
        public Demo(Integer num) {
            this.num = num;
        }

        /**
         * 重写了equals方法,只要值相同就可以认为是同一个对象
         * @param obj
         * @return
         */
        @Override
        public boolean equals(Object obj) {
            if (obj == null) {
                return false;
            }
            if (obj instanceof Demo) {
                Demo demo = (Demo) obj;
                if (num.equals(demo.num)) {
                    return true;
                }
            }
            return false;
        }

        /**
         * 重写hashCode方法,返回当前num值
         * @return
         */
        @Override
        public int hashCode() {
            return num;
        }
    }

测试HashMap 添加元素

@Test
    public void testHashMap() {
        // HashMap put方法去重
        //通过新key的hashCode()方法,计算出哈希码,然后从Node数组中找到对应的位置,若为null就放进去。若已经有值了,请看第二步
        //调用新key的equals()方法去和已经存在的key比较,如果返回ture 。则视新键与已经存在的键相同,用新值去更新旧值,然后put方法返回旧值
        //调用equals()返回false,则认为新键和已存在的键不一样,那就会新建一个Node节点,放在此链表里
        Map<String, String> testMap1 = new HashMap<>();
        testMap1.put("a", "1");
        testMap1.put("a", "2");
        testMap1.put("a", "3");
        System.out.println(testMap1.size()); //长度1

        Map<String, String> testMap2 = new HashMap<>();
        //String 底层重写了hashCode/equals方法,所有值相同的对象都会返回相同的HashCode并且equals返回true
        testMap2.put(new String("a"), "1");
        testMap2.put(new String("a"), "2");
        testMap2.put(new String("a"), "3");
        System.out.println(testMap2.size()); //长度1

        // Integer 底层重写了hashCode/equals方法,所有值相同的对象都会返回相同的HashCode并且equals返回true
        Map<Integer, String> testMap3 = new HashMap<>();
        testMap3.put(new Integer(200), "1");
        testMap3.put(new Integer(200), "2");
        testMap3.put(new Integer(200), "3");
        System.out.println(testMap3.size()); //长度1

        //Demo类 重写了hashCode/equals方法,所有值相同的对象都会返回相同的HashCode并且equals返回true
        Demo demo1 = new Demo(1);
        Demo demo2 = new Demo(1);
        Demo demo3 = new Demo(1);
        Map<Demo, String> testMap4 = new HashMap<>();
        testMap4.put(demo1, "1");
        testMap4.put(demo2, "2");
        testMap4.put(demo3, "3");
        System.out.println(testMap4.size()); //长度1
    }

执行结果
在这里插入图片描述
测试IdentityHashMap 添加元素

@Test
    public void testIdentityHashMap() {
        // IdentityHashMap put方法去重   使用 == 判断两个key使用相同, 如果内存地址相同覆盖旧key/value

        // 常量 "a" 第一次使用时如果常量池中没有则会添加一个,后续如果在使用会直接使用常量池中已有的变量,所以内存地址是指向同一处地方
        Map<String, String> testMap1 = new IdentityHashMap<>();
        testMap1.put("a", "1");
        testMap1.put("a", "2");
        testMap1.put("a", "3");
        System.out.println(testMap1.size()); //长度1

        // 使用 new String() 会在堆中创建一个对象,然后如果常量池没有会在常量池中创建 "a" ,因此内存地址是指向不是同一处地方
        Map<String, String> testMap2 = new IdentityHashMap<>();
        testMap2.put(new String("a"), "1");
        testMap2.put(new String("a"), "2");
        testMap2.put(new String("a"), "3");
        System.out.println(testMap2.size()); //3

        //,new Integer()会直接在堆中创建对象,并将引用赋值给栈中,因此内存地址是指向不是同一处地方
        Map<Integer, String> hashMap2 = new IdentityHashMap<>();
        hashMap2.put(new Integer(200), "1");
        hashMap2.put(new Integer(200), "2");
        hashMap2.put(new Integer(200), "3");
        System.out.println(hashMap2.size()); //长度3

        // IdentityHashMap底层使用 == 判断新key与旧key内存地址是否一致,只有内存地址一致才会覆盖旧key/value
        Demo demo1 = new Demo(1);
        Demo demo2 = new Demo(1);
        Demo demo3 = new Demo(1);
        Map<Demo, String> testMap3 = new IdentityHashMap<>();
        testMap3.put(demo1, "1");
        testMap3.put(demo1, "1");
        testMap3.put(demo2, "2");
        testMap3.put(demo2, "2");
        testMap3.put(demo3, "3");
        testMap3.put(demo3, "3");
        System.out.println(testMap3.size()); //长度3
    }

执行结果:
在这里插入图片描述
注意:

  • IdentityHashMap重写了equals和hashcode方法,不过需要注意的是hashCode方法并不是借助Object的hashCode来实现的,而是通过System.identityHashCode方法来实现的。
  • 该类不是线程安全的,如果要使之线程安全,可以调用Collections.synchronizedMap(new IdentityHashMap(…))方法来实现。
ConcurrentHashMap
  1. Hashtable之所以效率低下主要是因为其实现使用了synchronized关键字对put等操作进行加锁,而synchronized关键字加锁是对整个对象进行加锁,也就是说在进行put等修改Hash表的操作时,锁住了整个Hash表,从而使得其表现的效率低下;因此,在Java1.5~1.7版本,Java使用了分段锁机制实现ConcurrentHashMap.

  2. 简而言之,ConcurrentHashMap在对象中保存了一个Segment数组,默认长度为16。即将整个Hash表划分为多个分段;而每个Segment元素,即每个分段则类似于一个Hashtable;这样,在执行put操作时首先根据hash算法定位到元素属于哪个Segment,然后对该Segment加锁即可。因此,ConcurrentHashMap在多线程并发编程中可是实现多线程put操作。
    (segment有多少个,理论上就可以同时有多少个线程来持有它这个资源。)

  3. 在JDK1.7之前,ConcurrentHashMap是通过分段锁机制来实现的,所以其最大并发度受Segment的个数限制。因此,在JDK1.8中,ConcurrentHashMap取消了Segment分段锁字段,底层采用与HashMap类似的数组+链表+红黑树的方式实现,而加锁则采用CAS和synchronized实现。
    (这里注意Node其实就是保存一个键值对的最基本的对象。其中Value和next都是使用的volatile关键字进行了修饰,以确保线程安全。)

四种遍历Map接口的方式

  • Entry
    • 由于Map中存放的元素均为键值对,故每一个键值对必然存在一个映射关系。
    • Map中采用Entry内部类来表示一个键值对,键值对包含Key和Value (我们总说键值对键值对, 每一个键值对就是一个Entry)
    • Map.Entry里面包含getKey()和getValue()方法
  Iterator<Map.Entry<Integer, Integer>> it=map.entrySet().iterator();
    while(it.hasNext()) {
        Map.Entry<Integer,Integer> entry=it.next();
        int key=entry.getKey();
        int value=entry.getValue();
        System.out.println(key+" "+value);
    }
  • entrySet
    • entrySet是 java中 键-值 对的集合,Set里面的类型是Map.Entry,一般可以通过map.entrySet()得到。
    • entrySet实现了Set接口,里面存放的是键值对。一个K对应一个V。
Set<Map.Entry<String, String>> entryseSet=map.entrySet();

for (Map.Entry<String, String> entry:entryseSet) {
    System.out.println(entry.getKey()+","+entry.getValue());
}
  • keySet
    • keySet是键的集合,Set里面的类型即key的类型
Set<String> set = map.keySet();
for (String s:set) {
    System.out.println(s+","+map.get(s));
}
  • 四种遍历Map方式对比
public static void main(String[] args) {
 
    Map<String, String> map = new HashMap<String, String>();
    map.put("1", "value1");
    map.put("2", "value2");
    map.put("3", "value3");
  
 
    //第一种:键找值方式遍历,普遍使用,二次取值
    System.out.println("通过Map.keySet遍历key和value:");
    for (String key : map.keySet()) {
        System.out.println("key= "+ key + " and value= " + map.get(key));
    }
  
     //第二种:遍历entrySet迭代器遍历方式
    System.out.println("通过Map.entrySet使用iterator遍历key和value:");
    Iterator<Map.Entry<String, String>> it = map.entrySet().iterator();
    while (it.hasNext()) {
        Map.Entry<String, String> entry = it.next();
        System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue());
    }
  
    //第三种:遍历entrySet方式,推荐,尤其是容量大时
    System.out.println("通过Map.entrySet遍历key和value");
    for (Map.Entry<String, String> entry : map.entrySet()) {
        System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue());
    }
 
    //第四种:keySet迭代器遍历
    Iterator<String> iterator=map.keySet().iterator();
      System.out.println("通过Map.keySet使用iterator遍历key和value:");
    while (iterator.hasNext()) {
            String key=iterator.next();
            String  value=map.get(key);
            System.out.println("key: " + key +",value: "+value);
   	}
 }

推荐使用第三种方式遍历map集合

发布了62 篇原创文章 · 获赞 109 · 访问量 5295

猜你喜欢

转载自blog.csdn.net/qq877728715/article/details/103029866