Java8 HashMap put( ) 过程解析

put( ) 方法用于向 HashMap 中插入一个键值对,如果键已存在,那么就替换原来的值,如果键不存在,那么就创建一个新的节点并插入到 HashMap 中。

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

// 第四个参数 onlyIfAbsent 如果是 true,那么只有在不存在该 key 时才会进行 put 操作
// 第五个参数 evict 我们这里不关心
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    // 第一次 put 值的时候,会触发下面的 resize(),类似 java7 的第一次 put 也要初始化数组长度
    // 第一次 resize 和后续的扩容有些不一样,因为这次是数组从 null 初始化到默认的 16 或自定义的初始容量
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    // 找到具体的数组下标,如果此位置没有值,那么直接初始化一下 Node 并放置在这个位置就可以了
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);

    else {// 数组该位置有数据
        Node<K,V> e; K k;
        // 首先,判断该位置的第一个数据和我们要插入的数据,key 是不是"相等",如果是,取出这个节点
        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) {
                // 插入到链表的最后面(Java7 是插入到链表的最前面)
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    // TREEIFY_THRESHOLD 为 8,所以,如果新插入的值是链表中的第 8 个
                    // 会触发下面的 treeifyBin,也就是将链表转换为红黑树
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                // 如果在该链表中找到了"相等"的 key(== 或 equals)
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    // 此时 break,那么 e 为链表中[与要插入的新值的 key "相等"]的 node
                    break;
                p = e;
            }
        }
        // e!=null 说明存在旧值的key与要插入的key"相等"
        // 对于我们分析的put操作,下面这个 if 其实就是进行 "值覆盖",然后返回旧值
        if (e != null) {
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    // 如果 HashMap 由于新插入这个值导致 size 已经超过了阈值,需要进行扩容
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}

代码中的变量和方法有以下含义:

  • hash: 用于计算键的哈希值,用于确定键在数组中的位置
  • key: 要插入或查找的键
  • value: 要插入或返回的值
  • onlyIfAbsent: 一个布尔值,表示是否只在键不存在时才插入值
  • evict: 一个布尔值,表示是否在插入后调用 afterNodeInsertion 方法
  • tab: HashMap 的内部数组,存储 Node 节点
  • p: 一个 Node 变量,用于遍历链表或树
  • n: 数组的长度
  • i: 数组的索引
  • e: 一个 Node 变量,用于存储找到或创建的节点
  • k: 一个临时变量,用于存储节点的键

代码中的逻辑步骤如下:

  1. 检查数组是否为空或长度为零,如果是,则调用 resize 方法初始化或扩容数组,并更新 n 的值。
  2. 根据 hash 和 n 计算 i 的值,并从数组中取出 i 位置上的节点 p。
  3. 如果 p 为空,说明该位置没有数据,直接创建一个新节点并赋给 p。
  4. 否则,说明该位置有数据,则进入 else 分支。
  5. 首先判断 p 的哈希值和 key 是否和传入的参数相等(使用 == 或 equals 方法),如果是,则说明找到了相同的键,将 p 赋给 e。
  6. 否则判断 p 是否是 TreeNode 类型(一种特殊的 Node 类型,用于实现红黑树),如果是,则调用 putTreeVal 方法在树中查找或插入节点,并将返回值赋给 e。
  7. 否则进入 for 循环,在链表中遍历查找或插入节点。每次循环都会增加 binCount 的值,并检查 e 是否为空。如果为空,则说明没有找到相同的键,在链表尾部创建一个新节点并赋给 e,并且判断 binCount 是否超过了 TREEIFY_THRESHOLD(默认为8),如果是,则调用 treeifyBin 方法将链表转换为树结构。然后跳出循环。如果不为空,则继续判断 e 的哈希值和 key 是否和传入参数相等(使用 == 或 equals 方法),如果是,则说明找到了相同的键,并跳出循环。否则将 e 赋给 p 并继续循环。
  8. 如果 e 不为空,则说明找到了相同的键或者创建了新节点。取出 e 的旧值 oldValue,并根据 onlyIfAbsent 参数决定是否更新 e 的 value 值。然后调用 afterNodeAccess 方法进行一些后续操作(比如移动节点到链表头部)。最后返回 oldValue。
  9. 增加 modCount 的值(表示 HashMap 结构被修改了多少次)。
  10. 如果 size 超过了 threshold(阈值,默认为数组长度乘以负载因子),则调用 resize 方法进行扩容操作。
  11. 调用 afterNodeInsertion 方法进行一些后续操作(比如删除最老的元素)。
  12. 返回 null。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
您好!要使用Java 8开发API参数解析器,您可以使用Java 8中引入函数式接口和Lambda表达式,以及Stream API来解析和处理参数。 首先,您可以创建一个函数式接口来表示参数解析器的行为。例如,您可以定义一个名为`ArgumentParser`的接口,其中包含一个方法`parse(String argument)`,用于解析单个参数并返回解析结果。 ```java @FunctionalInterface public interface ArgumentParser<T> { T parse(String argument); } ``` 接下来,您可以创建一个参数解析器的工具类,该类包含一个方法`parseArguments(String[] arguments, Map<String, ArgumentParser<?>> parsers)`,用于解析整个参数列表。该方法使用`Map`来将参数名称映射到相应的解析器。 ```java import java.util.HashMap; import java.util.Map; public class ApiParameterParser { public static Map<String, Object> parseArguments(String[] arguments, Map<String, ArgumentParser<?>> parsers) { Map<String, Object> parsedArguments = new HashMap<>(); for (String argument : arguments) { int delimiterIndex = argument.indexOf('='); if (delimiterIndex != -1) { String parameterName = argument.substring(0, delimiterIndex); String parameterValue = argument.substring(delimiterIndex + 1); ArgumentParser<?> parser = parsers.get(parameterName); if (parser != null) { Object parsedValue = parser.parse(parameterValue); parsedArguments.put(parameterName, parsedValue); } } } return parsedArguments; } } ``` 使用参数解析器时,您需要创建一个`Map`来指定每个参数的解析器。例如,假设您要解析一个名为`name`的字符串参数和一个名为`age`的整数参数,您可以这样做: ```java import java.util.Map; public class Main { public static void main(String[] args) { String[] arguments = { "name=John", "age=30" }; Map<String, ArgumentParser<?>> parsers = new HashMap<>(); parsers.put("name", argument -> argument); parsers.put("age", Integer::parseInt); Map<String, Object> parsedArguments = ApiParameterParser.parseArguments(arguments, parsers); System.out.println("Name: " + parsedArguments.get("name")); System.out.println("Age: " + parsedArguments.get("age")); } } ``` 在这个例子中,我们将`name`参数的解析器定义为返回原始字符串,将`age`参数的解析器定义为将字符串转换为整数。 希望这可以帮助到您!如果您有任何其他问题,请随时问我。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值