专业的JAVA编程教程与资源

网站首页 > java教程 正文

Java 17 Map 接口知识点(java map遍历)

temp10 2024-10-26 15:25:36 java教程 7 ℃ 0 评论

Java 17 Map 接口知识点

思维导图

Map 集合可以认为是一个包含键值对的数据集合。也可以称为键值对。并且对于键(key)而言,是不允许重复的。并且每个键只能映射到一个值上。对于 key 相等时, 就认为该键值对是相同的数据。对于 Java 平台来说包含了三个通用的实现。 分别是:HashMap、LinkedHashMap 以及 TreeMap。

对于三个通用实现的行为和性能基本上是相似的。 在老的版本里面还有 Dictionary<K,V>, C# 使用这种方式, 但是 Java 中已经不推荐继承这个类去扩展了,而是实现 Map 接口。但依然还会有部分以后的类使用了该接口。

Java 17 Map 接口知识点(java map遍历)

Map的定义:

public interface Map<K, V> 

对于 Map 集合, 同样会有增删改查基础的功能。

  • put(K key, V value); 新增数据,将指定的值和键进行关联。
  • remove(Object key); 从映射中删除键关联的值(当然这个键存在的话)
  • entrySet(); 返回此映射中包含的映射的 Set 视图。
  • replace(K key, V value); 仅当指定键当前映射到某个值时,才替换该项的条目。
  • size(); 返回此映射中键值映射的数目。

这些都是常用的方法, 更多的内容可以参考API。

API地址: https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Map.html

AbstractMap

对于 Map 接口, 使用的时候, 必须进行实现, 不实现就无法进行使用。 并且对于 Java SDK 默认有通用的实现。 实现之前为了使用多种不同的实现, 会进行抽象封装, 所以就有了抽象类 AbstractMap。对于该接口和 AbstractSet 和 AbstractList 一样, 都是为了减少实现上层接口的工作量。

抽象类的定义:

public abstract class AbstractMap<K,V> implements Map<K,V>

实现的相关方法:

抽象类只是实现了通用的一些变化不大的实现。 对于实际的 put 方法, 以及如何遍历元素都没有具体的实现。

只是直接返回了 UnsupportedOperationException (不支持的操作异常)。

HashMap

上面说了抽象类相关的定义, 接下来看看第一个常用的实现。 HashMap<K,V>。他主要是基于哈希表进行实现。 它的实现提供了全部的 Map 接口可选的操作。 对于哈希表(Hash table)也叫作散列表,是根据键和值进行访问的数据结构,只需要输入对应的 key,就可以查找到对应的值 value。

类实现的定义:

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable

从实现的数据结构上来看, HashMap 是用到了数组、链表、红黑树(从 JDK8 加入)。

对于 HashMap 而言是非同步的, 也就是说在操作中没有锁的保护, 将会出现数据不一致的情况出现。在单线程中可以放心使用。

举个例子, 张三这个用户, 对系统有三个权限, 浏览, 修改和删除权限。 这个时候, 我们可以模拟一下数据的操作。

先创建一个权限对象。

class Auth {
    private String id;
    private String name;

    public Auth(String id, String name) {
        this.id = id;
        this.name = name;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Auth [id=" + id + ", name=" + name + "]";
    }

}

为了调试方便, 重写了 toString() 方法。

这只是简单的使用方式, 可以快速的定位是否包含某个权限, 可能会进行更改, 保存的键是权限的主键。

在代码的测试后面加上下面的代码

System.out.println("删除 编码3 的删除权限");
map.remove("3");
mapUser.put("zhangsan", map);
authDel = mapAuth.get("3");
if(authDel != null){
    System.out.println("包含删除的权限。");
}else{
    System.out.println("不包含删除的权限。");
}

执行代码,查看。

对于 HashMap 排序是随机的

对于 HashMap 是无序的, 测试案例。

测试代码如下:

执行运行查看效果:

LinkedHashMap

如果使用 HashMap 的同时, 还想保持插入的顺序并能更好的排序怎么办呢? 可以使用 LinkedHashMap 实现。

public class LinkedHashMap<K,V>
    extends HashMap<K,V>
    implements Map<K,V>

该类是基于哈希表和链表的实现,具有可预测的迭代顺序。对于该类的实现和 HashMap 的区别在于它内置了一个双重链表列表,此链表定义了迭代顺序。通常是将键插入的顺序(insert-order)。

举例如下:

所以对于业务中需要使用 Map 键值对并且想要保证数据的顺序的就可以使用该类。

当然对于键来说, 可以通过方法直接知道是否包含该值和该键。 演示如下:

除了这些常用的使用方式, 还有是否为空, 以及集合的大小。

map.size(); // 集合的大小
map.isEmpty(); // 如果集合没有对应的键值对数据就返回 true

WeakHashMap

对于 WeakHashMap 是基于哈希表实现的, 但是相对于上面两种 Map 实现, 它是弱键(weak keys)的实现。弱键又被称为弱引用(Weak Reference) 也就是在使用过程中也会有可能被 GC 自动回收清理的。对于 GC 的自动回收的依据是:是否有有效的引用指向该对象。这里有效的引用不包含弱引用。 所以在 GC 回收垃圾的时候, 还是有可能会对弱引用的对象收回的。

public class WeakHashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V> 

对于它的使用场景, 经常用于缓存中, 因为缓存不需要必须保证数据一直存在, 即使没有命中, 还有处理机制重新赋值。这样也避免了长时间不用一直占用资源的情况。

这里对于使用上没有任何的区别。 注意使用场景就好了。

给一个实际中的项目例子: tomcat 中的 ConcurrentCache 就使用了 WeakHashMap

代码来源:https://github.com/apache/tomcat/blob/main/java/org/apache/el/util/ConcurrentCache.java

IdentityHashMap

该类的定义

public class IdentityHashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V>, java.io.Serializable, Cloneable

虽然此类使用哈希表实现了 Map 接口, 虽然此类实现了 Map 接口, 但是它违反了 Map 的通用约定,该约定要求在比较对象时使用equals方法。此类设计为仅在需要引用相等语义的极少数情况下使用。 不再过多的演示。

TreeMap

基于红黑树的 NavigableMap 接口实现的。和上一章说过的 Set 一直, 可以通过 key 进行排序。 以及可以通过 Comparator 接口进行排序。 再次重申数据结构的重要性。 花点精力要好好的吃透数据结构, 能给你技术有质的飞跃。从不懂到了解, 从了解到精通, 从精通到直觉, 内心用就应该这样用, 就给你一开始学习编程, 就很难理解定义变量怎么来定义, 突然有一天顿悟了, 字符串就是要定义字符串。 数值就是要定义数值。 希望我们下一次的顿悟来的更早一点。 继续,扯得有点远了。

EnumMap

用于枚举类型的专用 Map 实现。key 继承自 Enum。对于枚举中的键值对中的键都必须来自创建对象时的显式或隐式指定的单个枚举类型, 对于枚举类型的键值对内部是用数组实现,对于这种实现非常的紧凑和高效。

对于它的实现, key 键是不允许为空的。 插入 null 时, 会有 NullPointerException 异常。但是在测试的时候判断是否为 null 键,或者删除 null 键不会提示异常。

EnumMap 的定义:

public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V>
    implements java.io.Serializable, Cloneable

举个简单的例子:

这里需要注意的是, 该实现的构造函数没有默认的构造方法, 基于 Java 自定义构造方法, 不会生成默认的构造方法的规则, 所以直接使用空的构造方法是不行的。

所以下面的将会提示错误。

对于 Map 的遍历

对于 Map 遍历一般情况下,有以下三种常用的方法。

第一种方式 keySet()

Set<String> keySet = map.keySet();
for (String key : keySet) {
    // 循环所有key,通过get方法打印所有value
    System.out.print("key: " + key + ", value: " + map.get(key) + "\n");
}

使用 keySet() 方法获得所有 Map 中的 key。并使用 get(key) 获得对应的值。

第二种方式 entrySet

entrySet 方法的定义

Set<Map.Entry<K, V>> entrySet();

其中 Map.Entry<K, V> 中的 Entry 是一个 Map 中定义的内部接口 interface Entry<K, V>。在其实现类中实现了该接口 static class Node<K,V> implements Map.Entry<K,V>

Set<Map.Entry<String, String>> entrySet = map.entrySet();
for (Map.Entry<String, String> entry : entrySet) {
    System.out.println("key: " + entry.getKey() + ", value: " + entry.getValue());
}

当然也可以使用迭代器进行实现, 举例代码如下:

Iterator<Entry<String, String>> it = map.entrySet().iterator();
while (it.hasNext()) {
    Entry<String, String> entry = it.next();
    System.out.println("key: " + entry.getKey() + ", value: " + entry.getValue());
}

第三种方式 values

该方式主要是为了遍历对应的值, 而不需要键的场景上使用。

Collection<String> values = map.values();
for (String str : values) {
    System.out.println("value: " + str);
}

如果想同事操作多个集合或者针对集合进行条件的汇总, 集合的合并可以使用封装类 Collectors。 这里不针对该类进行详细的说明。 后续单独开一篇。 今天这篇内容有点超长。 如果你能读到这, Java 对你来说就太容易了。

Map 接口的知识点先到这里吧。 后续更多原理性的东西,数据结构篇会讲到。 码字不易。

点赞, 关注, 收藏。 十分感谢!

编程是一种终身学习持续不断的事,开始就要做好准备,当然其他行业也是一样。躺平也要休息的有意义!



Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表