HashMap源码 之 put() 相关方法

数据结构 Map和Set(看这篇就够了~) Map是一个接口,不能直接实例化对象,如果要实例化对象只能实例化其实现类TreeMap或者HashMapMap中存放键值对的Key是唯一的,value是可以重复的Map中的Key可以全部分离出来,存储到Set中来进行访问(因为Key不能重复)。Map中的value可以全部分离出来,存储在Collection的任何一个子集合中(value可能有重复)。Map中键值对的Key不能直接修改,value可以修改,如果要修改key,只能先将该key删除掉,然后再来进行重新插入。...... 阅读详情

一、HashMap 的 put() 方法流程:

  1. put(key, value) 中直接调用了内部的 putVal() 方法,并且先对 key 进行了 hash 操作;
  2. putVal() 方法中,先检查 HashMap 数据结构中的索引数组表是否位空,如果是的话则进行一次 resize 操作;
  3. 以 HashMap 索引数组表的长度减一与key的hash值进行与运算,得出在数组中的索引,如果索引指定的位置值为空,则新建一个 k-v 的新节点;
  4. 如果不满足的3的条件,则说明索引指定的数组位置的已经存在内容,这个时候称之碰撞出现;
  5. 在上面判断流程走完之后,计算 HashMap 全局的 modCount 值,以便对外部并发的迭代操作提供修改的Fail-fast判断提供依据,于此同时增加 map 中的记录数,并判断记录数是否触及容量扩充的阈值,触及则进行一轮 resize 操作;
  6. 在步骤4中出现碰撞情况时,从步骤7开始展开新一轮逻辑判断和处理;
  7. 判断 key 索引到的节点(暂且称作被碰撞节点)的 hash 、key 是否和当前待插入节点(新节点)的一致,如果是一致的话,则先保存记录下该节点;如果新旧节点的内容不一致时,则再看被碰撞节点是否是树(TreeNode)类型,如果是树类型的话,则按照树的操作去追加新节点内容;如果被碰撞节点不是树类型,则说明当前发生的碰撞在链表中(此时链表尚未转为红黑树),此时进入一轮循环处理逻辑中;
  8. 循环中,先判断被碰撞节点的后继节点是否为空,为空则将新节点作为后继节点,作为后继节点之后并判断当前链表长度是否超过最大允许链表长度8,如果大于的话,需要进行一轮是否转树的操作;如果在一开始后继节点不为空,则先判断后继节点是否与新节点相同,相同的话就记录并跳出循环;如果两个条件判断都满足则继续循环,直至进入某一个条件判断然后跳出循环;
  9. 步骤8中转树的操作 treeifyBin ,如果 map 的索引表为空或者当前索引表长度还小于64(最大转红黑树的索引数组表长度),那么进行 resize 操作就行了;否则,如果被碰撞节点不为空,那么就顺着被碰撞节点这条树往后新增该新节点;
  10. 最后,回到那个被记住的被碰撞节点,如果它不为空,默认情况下,新节点的值将会替换被碰撞节点的值,同时返回被碰撞节点的值(V)。

二、使用的具体方法

  1. put() 方法
	/**
	* 将指定的值与此映射中的指定键相关联,如果Map中已经包含了该键的映射,那么旧的映射值将会被替代,也就是说在put时,
	* 如果map中已经包含有key所关联的键值对,那么后续put进来的键值对,将会以相同key为准替换掉原来的那一对键值对。
	* 返回的值则将是之前在map中实际与key相关联的Value值(也就是旧的值),如果key没有实际映射值的话那就返回null。
	*/
	public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

​ 2. Hash() 方法

	/**
	* 将key的hashcode值(由native方法计算得到)再与该值的高16位进行异或运算得到最终的hash值。
	* 这样做的目的作者也给出了解释,就是通常的hash算法都总是碰撞,我们这样做的目的尽量使得hash值较为分散。
	*/
	static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
  1. putVal() 方法
	final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    // 如果 HashMap 的数组还是 null,则先初始化,table是map中用于存放索引的表
    if ((tab = table) == null || (n = tab.length) == 0) {
        n = (tab = resize()).length;
    }
    // 使用 hash 与 数组长度 - 1 的值进行异或得到分散的数组下标,
    // 如果这个位置上没有值,那么直接新建 k-v 节点存放
    if ((p = tab[i = (n - 1) & hash]) == null) {
        tab[i] = newNode(hash, key, value, null);
    }
    // 如果走到else这一步,说明 key 索引到的数组位置上已经存在内容,即出现了 Hash 碰撞
    // eles 内容为发生 Hash 碰撞时进行的操作
    else {
        Node<K,V> e; K k;
        // p 是产生 Hash 碰撞的节点(k-v) 
        // 如果该节点的hash 和 新值的 hash 相等,而且 key 也相等 
        // 或者在 key 不等于 null 的情况下 key 的内容也相等
        // 则将当前节点 p 用临时节点 e 保存
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k)))) {
            e = p;   
        }
        // 如果当前节点 p 是(红黑)树类型的节点,则说明碰撞已经开始用树来处理,
        // 则后续的数据结构都是树而非链表
        else if (p instanceof TreeNode) {
            // 其中this表示当前 HashMap , tab 为 HashMap 中的数组
            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);
                    // TREEIFY_THRESHOLD = 8
                    // 从 0 开始遍历,如果到 7 则说明满 8 了,这个时候就需要判断是否进化为树,还是进行扩容操作
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                // 找到了碰撞节点中,key 完全相等的节点,则用新节点替换老节点
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        // 此时的 e 是保存的被碰撞的那个节点,即老节点。如果不进行替换则 e 为 null
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            // onlyIfAbsent是方法的调用参数,表示是否替换已存在的值,
            // 在默认的put方法中这个值是false,所以这里会用新值替换旧值
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;  // 此时才完成了新值替换旧值得操作
            // Callbacks to allow LinkedHashMap post-actions
            afterNodeAccess(e);
            return oldValue;
        }
    }
    // map变更性操作计数器
    // 比如map结构化的变更像内容增减或者rehash,这将直接导致外部map的并发
    // 迭代引起fail-fast问题,该值就是比较的基础
    ++modCount;
    // size即map中包括k-v数量的多少
    // 当map中的内容大小已经触及到扩容阈值时,则需要扩容了
    if (++size > threshold)
        resize();
    // Callbacks to allow LinkedHashMap post-actions
    afterNodeInsertion(evict);
    return null;
}
  1. putTreeVal() 方法

​ 当存储值发生碰撞,当前节点已经进化为到树时,将执行putTreeVal方法:(使用了红黑树的储值计算方法)

	/**
 	* Tree version of putVal.
 	*/
	final TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab,
                               int h, K k, V v) {
    Class<?> kc = null;
    boolean searched = false;
    //获取根节点
    TreeNode<K,V> root = (parent != null) ? root() : this;
    //循环所有的节点,如果key冲突,返回原key对应的对象,如果不存在key冲突,则直接存放对象,并返回null对象
    for (TreeNode<K,V> p = root;;) {
        int dir, ph; K pk;
        //如果当前节点的的hash大于,当前存放对象的hash
        //dir的意思是放在节点的左边还是右边
        if ((ph = p.hash) > h)
            dir = -1;
        else if (ph < h)
            dir = 1;
        //如果当前节点的key与当前存入的key一致,则返回当前
        else if ((pk = p.key) == k || (k != null && k.equals(pk)))
            return p;
        else if ((kc == null &&
                  (kc = comparableClassFor(k)) == null) ||
                 (dir = compareComparables(kc, k, pk)) == 0) {
            if (!searched) {
                TreeNode<K,V> q, ch;
                searched = true;
                if (((ch = p.left) != null &&
                     (q = ch.find(h, k, kc)) != null) ||
                    ((ch = p.right) != null &&
                     (q = ch.find(h, k, kc)) != null))
                    return q;
            }
            dir = tieBreakOrder(k, pk);
        }
		//根据dir的值,设置当前对象应该存放的位置,是左边子节点,还是右边子节点
        TreeNode<K,V> xp = p;
        //如果p的左节点或右节点为空,证明已经找到存放位置
        if ((p = (dir <= 0) ? p.left : p.right) == null) {
            Node<K,V> xpn = xp.next;
            //创建新节点
            TreeNode<K,V> x = map.newTreeNode(h, k, v, xpn);
            //设置存放位置
            if (dir <= 0)
                xp.left = x;
            else
                xp.right = x;
            //绑定上下级关系
            xp.next = x;
            x.parent = x.prev = xp;
            if (xpn != null)
                ((TreeNode<K,V>)xpn).prev = x;
            //balanceInsertion树化结构,设置红黑节点,是否需要旋转
            //moveRootToFront重置根节点
            moveRootToFront(tab, balanceInsertion(root, x));
            return null;
        }
    }
}
  1. treeifyBin() 方法

在值发生碰撞并需要延续追加时,如果追加的链表长度大于8,那么需要重新评估当前是扩充数组还是将链表转换为红黑树来存储。

/**
 * Replaces all linked nodes in bin at index for given hash unless
 * table is too small, in which case resizes instead.
 */
final void treeifyBin(Node<K,V>[] tab, int hash) {
    int n, index; Node<K,V> e;
    // MIN_TREEIFY_CAPACITY = 64
    // 如果当前map的数组为空,或者数组长度还小于64,则选择扩充数组长度
    if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) {
        // 扩充数组长度涉及到原内容的重新散列再存储
        resize();
    }
    // 如果执行else if则说明数组长度已经大于64了,这个时候就使用了
    // 红黑树来处理
    else if ((e = tab[index = (n - 1) & hash]) != null) {
        TreeNode<K,V> hd = null, tl = null;
        do {
            TreeNode<K,V> p = replacementTreeNode(e, null);
            if (tl == null)
                hd = p;
            else {
                p.prev = tl;
                tl.next = p;
            }
            tl = p;
        } while ((e = e.next) != null);
        if ((tab[index] = hd) != null)
            // table表从此节点链接成树
            hd.treeify(tab);
    }
}
深入解析 HashMap 的 put() 方法及其相关实现 在 Java 编程中,HashMap 是一个非常常用且高效的集合类,用于存储键值对。它基于哈希表实现,提供了快速的插入、删除和查找操作。本文将深入探讨 HashMap 的 put() 方法及其相关的核心内部方法,包括如何处理哈希冲突、链表到红黑树的转换以及扩容机制。通过理解这些细节,你将能够更好地利用 HashMap 来优化你的应用程序,并解决可能出现的性能问题。先来看下hashMap插入数据的大致流程,以便我们接下来理解源码。 阅读详情

相关推荐

攀藤PMS系列传感器STM32驱动开发与数据解析实战

本文详细介绍了攀藤PMS系列传感器与STM32微控制器的驱动开发与数据解析实战,涵盖硬件连接、数据协议解析、驱动源码编写及数据处理优化。重点解析了传感器数据帧结构,提供了完整的STM32驱动实现代码,并分享了实际应用中的问题排查与性能优化经验,帮助开发者快速实现高精度颗粒物浓度检测。

milk8的博客 349

ruoyi若依前后端分离项目部署到服务器后,PUT DELETE请求403错误,GET POST请求正常

第三种是tomcat 8以上不支持PUT DELETE,rest风格的api更适合将打包方式改成 jar,直接运行,目前看来第三种最有效。后端打包方式war,部署到tomcat8, PUT DELETE请求报403错误,网上有三种说法。服务器根据Header:X-HTTP-Method-Override,转换请求method。并更改 /src/utils/request.js 拦截请求,添加header。第二种是服务器没有放开了PUT DELETE请求,需要前端添加header。第一种是跨域请求问题。

LBX1111的博客 3986

P450酶催化循环的Amber-Gromacs混合模拟实战:从血红素加氧到产物释放

本文详细介绍了针对P450酶完整催化循环的Amber-Gromacs混合模拟实战策略。通过Amber为关键氧合血红素中间体(CPDI)构建精确力场,再利用Gromacs的高效GPU加速进行长时间尺度动力学模拟,完整覆盖从底物结合到产物释放的催化过程,为研究酶催化机制提供了强大的计算工具。

weixin_29201859的博客 316

HashMap中put()方法的执行流程

HashMap 是 Java 中最常用的数据结构之一,用于存储键值对。其 put() 方法是向哈希表中插入或更新键值对的核心操作。本文将详细解析 put() 方法的执行过程,涵盖哈希值计算、桶定位、冲突处理和扩容等步骤。

动动发财的小手点点赞 1217

我的交叉验证翻车实录:从‘炼丹’到可靠评估,我是怎么用五折验证拯救我的图像分割模型的

本文分享了作者在医学图像分割模型评估中从失败到成功的实战经验。通过引入五折交叉验证方法,解决了传统数据划分的局限性,显著提升了模型评估的稳定性和泛化能力。文章详细介绍了Python实现、数据准备策略和训练流水线改造,为AI医疗影像分析提供了可靠的技术方案。

weixin_42625070的博客 271

深入AOSP构建系统:从BUILD_FINGERPRINT的生成逻辑,看如何定制你的Android设备标识

本文深入解析AOSP构建系统中的设备指纹生成机制,详细介绍了BUILD_FINGERPRINT的生成逻辑及其在Android设备中的关键作用。通过分析多个指纹属性的区别与联系,以及如何定制设备标识,帮助开发者更好地理解和掌握Android系统构建过程。

weixin_27017211的博客 222

HashMap中putAll()方法批量保存的源码解读,并发现严重缺陷

本文通过源码分析HashMap中批量保存数据的方法putAll(),在分析中发现严重缺陷,一起看看吧

理想万岁万万岁的博客 931

MicroPython ubluetooth 库详细介绍

MicroPython的ubluetooth库为蓝牙通信提供了强大的支持,使得开发者能够在MicroPython环境下轻松实现蓝牙设备之间的通信。其中ubluetooth库为MicroPython提供了强大的蓝牙通信功能,使得开发者能够轻松地在各种支持MicroPython的硬件平台上实现蓝牙设备之间的通信。它提供了一系列的函数和类,用于实现蓝牙设备的扫描、连接、数据传输等功能。- 上述代码中,首先创建了一个蓝牙服务和一个特征,然后可以通过read和write函数读取和写入特征值。# 处理连接成功事件。

m0_46311182的博客 1762

若依框架前后端各个请求方式参数传递示例

若依框架前后端各个请求方式参数传递示例

D__O__N的博客 1万+

【AirPlay】苹果认证MFI学习

配件认证是一个过程,通过这个过程,可验证其配件符合MFi技术规范,并通过苹果指定的任何认证测试。MFi门户中的产品计划 "认证 "页面将指导你完成特定配件所需的认证步骤,并指出何时完成所有要求。在完成所有MFi认证要求之前,你不得制造、分发或销售任何配件。在开始MFi认证过程之前,你必须测试你的配件,以确保它符合最新的MFi技术规范,通过产品计划 "测试计划 "上的所有适用测试,并与它将声称具有兼容性的每个苹果设备兼容。

天天的博客 2277

基于YOLOv10的机器人抓取引导系统:从物体检测到精准抓取

python# 数据标注工具简化版import os"""启动标注工具""""""标注单张图像"""print(f"标注图像: {os.path.basename(img_path)}")print("请在图像上绘制抓取矩形框...")# 这里应该实现交互式标注界面# 简化版本直接返回示例标注# 示例标注数据"""保存标注结果"""# 保存为YOLO格式# 转换为YOLO格式。

YOLO 378

PUT和POST方法的区别-比较POST和PUT方法

文章目录PUT vs POST - Comparing HTTP MethodsPUT vs POST2. PUT方法做了什么?3. POST方法做了什么?4. 何时使用? PUT vs POST - Comparing HTTP Methods PUT vs POST 存在各种各样的HTTP方法,每个方法用于不同的目的。最常用的HTTP方法是GET方法,用于从web服务器检索数据。例如,如果要从特定网站加载图像,浏览器将使用以下命令向web服务器发出请求: GET https://website.com/

我是张跑跑 2万+

基于深度学习的水质预测:多参数监测与预测

在本篇博客中,我们详细介绍了如何使用深度学习技术处理水质预测问题,包括数据集的准备与预处理、多层感知机模型的构建、模型的训练与评估以及模型优化策略。我们使用 Python 语言基于 TensorFlow 和 Keras 框架实现了模型,并给出了各种模型优化方法及对应代码。通过实践,我们可以发现深度学习技术在水质预测方面具有很好的应用潜力。在实际应用中,我们还可以根据具体问题和数据集进一步调整和优化模型,以获得更好的预测性能。

斌擎科技 1081

HashMap源码之put、get方法

本篇文章主要聊聊我们HashMap的put()、get()方法的源码。因为HashMap永远伴任何一名程序猿,对于HashMap来说,put()和get()方法是我们最常使用的两种方法,一直伴随我们左右。但其又不是线程安全的,所以我们需要查看其源码,了解其原理。 HashCode()方法 我们首先来插播一下hashCode()方法。对于任何一个对象都存在HashCode()方法,...

zhyk1117的博客 451

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

JDK1.8:HashMap 源码学习

JDK1.8:HashMap 源码学习 初学的时候只知道其中的方法怎么用,并不太了解其中的实现,所以呢,写篇记录一下学习过程。 关于JDK 1.8中的HashMap相关面试题会在后续新文章中分享 ,本文仅作为学习HashMap源码记录 简单说说 对于JDK 1.8中的HashMap 对比之前JDK版本相关的设计实现 ;也是面试过程中的必问点,只知道方法的使用,是万万行不通的!!!! public class HashMap<K,V> extends Abst...

weixin_41901345的博客 525

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

HashMap底层原理(含面试题)

一文搞懂HashMap底层原理(含面试题)HashMap1. HashMap结构图2. HashMap中定义的一些成员变量3. 继承关系4. 简单总结5. 深入了解源码5.1 构造方法5.2 数组的索引bucket5.3 HashMap.put(k,v)5.4 resize()数组扩容5.4 HashMap.get(k)6. 总结(面试相关)6.1 能否让HashMap同步6.2 HashMap的工作原理6.3 HashMap的put()方法和get()方法的工作原理6.4 当两个对象的hashcode相同

m0_47396773的博客 1324

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

学习笔记05——HashMap实现原理及源码解析(JDK8)

HashMap通过数组存储Node节点解决快速定位问题,使用链表和红黑树处理哈希冲突。当执行put操作时,首先计算键的扰动哈希值确定桶位置。若发生哈希冲突,JDK8采用尾插法维护链表,当链表长度达到树化阈值时会转换结构提升查询效率。扩容时采用2倍扩容策略,通过位运算快速重新分配节点位置,这个设计避免了重新计算哈希的开销。需要注意的是,HashMap不是线程安全的,多线程put可能导致数据丢失或死循环(JDK7版本)。建议结合手写伪代码/流程图进行说明,展现对底层实现的深入理解。

码代码的小朋友 876

HashMap的添加元素流程:put方法全流程详解

本文深入剖析了 Java 中 HashMap 添加元素的核心流程——put 方法的源码实现。通过详细解读哈希值计算、桶数组定位、链表遍历与红黑树转换、扩容机制等关键步骤,系统揭示了 HashMap 高效存储与冲突解决的底层设计原理。文章不仅帮助读者理解 HashMap 的工作细节,还总结了设计中的三大策略和实际应用建议,为Java开发者和面试准备者提供了全面且实用的技术指导。

m0_74182461的博客 4544

HashMap剖析之put()和get()方法

前言 本文是基于Java 8的HashMap进行分析,主要是分析HashMap中的put()和get()方法。 下面将会分析这部分的源码,如果觉得源码分析内容太啰嗦,可以跳过源码部分,直接看源码下面的总结。 put()方法源码分析 HashMap的put()方法是我们最常用的方法,但是put()方法是怎么工作的呢? put()方法 /** * HashMap的put()方法支持k...

2的32次方 867

HashMap的put流程知道吗

HashMap 的 put 方法算是 HashMap 中比较核心的功能了,复杂程度高但是算法巧妙,同时在上一版本的基础之上优化了存储结构,从链表逐步进化成了红黑树,以满足存取性能上的需要。本文逐行分析了 put 方法的执行流程,重点放在了对整个流程的把握,以后遇到相关问题时,也能够得心应手。

周沐子 1430

HashMap是如何进行put操作的

判断table是否为空,如果空的话,会先调用resize扩容;根据当前key的 hash 值,通过 (n - 1) & hash计算应当存放在数组中的下标 index;查看 table[index] 是否存在数据,没有数据就构造一个 Node 节点存放在 table[index] 中;存在数据,说明发生了 hash 冲突,继续判断 key 是否相等,如果相等,用新的 value 替换原数据(这里onlyIfAbsent 为 false);

LYQ 4856

HashMap的Get(),Put()源码解析

是 Java 中用于存储键值对(Key-Value)的集合类,它实现了。方法是 HashMap 中用于获取指定键对应值的核心方法.:键(Key)不能重复,若插入重复键会覆盖原有值。首次插入时,数组默认长度为 16。2. 核心数据结构:哈希表(HashTable):不保证元素的存储顺序,也不保证顺序恒定不变。4.插入后检查是否需要扩容。最核心的方法之一,用于存储键值对。数组长度翻倍(如 16 → 32)。1、什么是 HashMap?数组 + 链表 / 红黑树。不支持同步,性能更高。键值对数量超过阈值(

2301_78566776的博客 638

HashMap之put方法源码理解总结

引入 在理解hashmap的put方法之前,需要对hashmap有一些基础的理解。 **1.hashmap的数据结构:**数组 + 链表 + 红黑树 2.put数据的大概流程: 1.首次put数据 当定义了一个hashmap结构,在第一次调用put方法进行,添加数据的时候,首先会对hashmap结构进行初始化,生成长度为16的数组。 然后插入的数据调用hash(key)生成hash值,用来定位数据...

FortressBesieged的博客 309

【Java集合】HashMap的put()源码详解以及JDK1.7与JDK1.8的区别

详细讲解HashMap添加操作底层源码,以及对比put()源码在JDK1.7和JDK1.8中的区别。

小七的博客 2834

HashMap源码之put方法

1.准备 讲put方法之前,先看两个方法,这两个方法可以根据一个key-value键值对的key确定其在entry数组中的下标位置 1.hash方法,计算key的hash值 static final int hash(Object key) { int h; // h = key.hashCode() 为第一步 取hashCode值 // h ^ (h >>> 16) 为第二步 高位参与运算, return (key == null) ? 0 : (h =

weixin_42044450的博客 928

HashMap中add()方法的源码学习

HashMap add() //hash值和元素的hashCode()方法相关 final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i;...

ouyangxuanruo的博客 3130
上一篇: HashMap源码 之 get() 相关方法
下一篇: SpringData Jpa 之 修改、删除数据
☞十一
博客等级 码龄9年 8粉丝 57原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值