6.jdk源码阅读之HashMap(下)

基于C#的ASP.NET开发的党史学习网站课设 开展党史学习教育,就是要以史为镜、以史明志,把苦难辉煌的过去、日新月异的现在、光明宏大的未来贯通起来,在鉴往知来中砥砺前行,在乱云飞渡中把准航向,在凝心聚力中团结奋斗,共同创造新的历史伟业。登陆按钮与数据库进行连接,逻辑实现是如图3.1.3所示,自己定义一个SQL查询语句,将账号、密码放入SQL查询语句中,用sqlDataReader.HasRows语句进行逐一比较,找出结果,将用户名存入session并调用cookies,如果结果存在就关闭数据库,进行数据跳转,并弹出登陆成功的提示框,并关闭数据库。 阅读详情

1.写在前面

上一篇文章,我们从往HashMap中put元素开始,详细分析了创建HashMap和put元素的过程,这篇文章我想和大家一起来深入的再研究下HashMap,我先抛出几个问题:

  1. jdk7 和 jdk8 中 HashMap的源码和底层实现发生了什么变化? 为什么要这么改?要解决什么问题?
  2. jdk8 中HashMap为什么线程不安全? 有具体例子吗? 什么情况下会有线程安全问题?

2.JDK 7 中的 HashMap 实现

在 JDK 7 中,HashMap 的底层实现主要是通过数组和链表的组合来实现的。每个桶(bucket)是一个链表,所有具有相同哈希值的键值对都存储在同一个链表中。

2.1 数组和链表

HashMap 使用一个数组来存储链表的头节点。每个数组的元素(桶)是一个链表,链表中的每个节点存储一个键值对。

2.2 哈希冲突处理

当多个键的哈希值相同(即发生哈希冲突)时,这些键值对会被存储在同一个链表中。

2.3 扩容

当负载因子超过设定值时,HashMap 会进行扩容(rehash),将容量加倍,并重新分配所有键值对。

2.4 性能问题

当哈希冲突频繁发生时,链表会变得很长,导致查找、插入和删除操作的时间复杂度从平均的 O(1) 退化到最坏的 O(n)。

2.5 多线程操作时的死循环

在 JDK 7 中,HashMap 在多线程环境下使用不当确实可能导致死循环。这主要发生在 HashMap 进行扩容(rehash)的过程中。如果多个线程同时对 HashMap 进行插入操作,可能会导致链表形成环形结构,从而引发死循环。

以下是一个简单的示例,展示了在多线程环境下如何导致 HashMap 死循环:

import java.util.HashMap;
import java.util.Map;

public class HashMapInfiniteLoopExample {
    private static final Map<Integer, Integer> map = new HashMap<>();

    public static void main(String[] args) throws InterruptedException {
        // 创建两个线程同时对 HashMap 进行插入操作
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                map.put(i, i);
            }
        });

        Thread thread2 = new Thread(() -> {
            for (int i = 1000; i < 2000; i++) {
                map.put(i, i);
            }
        });

        // 启动线程
        thread1.start();
        thread2.start();

        // 等待线程完成
        thread1.join();
        thread2.join();

        System.out.println("Map size: " + map.size());
    }
}

在上述代码中,如果 HashMap 发生扩容,并且两个线程同时对同一个桶进行操作,可能会导致链表形成环形结构。此时,如果另一个线程试图遍历这个桶中的链表,就会陷入死循环,导致 CPU 使用率飙升,程序无法继续执行。我们通过画图来说明死循环的形成过程。

2.5.1 jdk7 HashMap的基本结构

在这里插入图片描述
如上图所示,在JDK1.7中HashMap的底层数据实现是数组+链表。

2.5.2 头插法

在这里插入图片描述
如上图所示,在JDK7中HashMap的插入是头插法,这也是造成死循环的根本原因。在JDK8中改成了尾插法,这个我们稍后再详细看。

2.5.3 HashMap正常扩容过程

在这里插入图片描述
如上图所示,扩容的过程概括来说就是先生成一个更大容量的HashMap,然后把旧HashMap中的数据迁移到新HashMap,问题就发生在迁移的过程中。前面提到JDK7 HashMap 是采用的头插法,这样原先是A->B->C,迁移到新的HashMap之后就会变成C->B->A。

2.5.4 死循环形成

在这里插入图片描述

按照我们上面的代码举例,有两个线程并发在往HashMap中put元素,发现需要扩容,然后都进行扩容。此时T1和T2指向的是链表的头结点元素A,而T1和T2的下一个节点,也就是T1.next和T2.next指向的是B节点。
在这里插入图片描述
假设由于某种原因,T2线程挂起了,T1线程先进行了扩容,扩容完成后如上图右侧所示。因为是头插法,所以HashMap的顺序已经发生了改变,但线程T2对于发生的一切是不可知的,所以它的指向元素依然没变,如上图展示的那样,T2指向的是A元素,T2.next指向的节点是B元素。
在这里插入图片描述
这个时候T2线程恢复了,准备开始执行扩容。因为T1执行完扩容之后B节点的下一个节点是A,而T2线程指向的首节点是A,第二个节点是B,这个顺序刚好和T1扩完容完之后的节点顺序是相反的。T1执行完之后的顺序是B到A,而T2的顺序是A到B,这样A节点和B节点就形成死循环了。
JDK7 HashMap造成死循环的源码如下,大家可以对照的上图和源码自行走一遍流程:

void resize(int newCapacity) {
    Entry[] oldTable = table;
    int oldCapacity = oldTable.length;
    if (oldCapacity == MAXIMUM_CAPACITY) {
        threshold = Integer.MAX_VALUE;
        return;
    }

    Entry[] newTable = new Entry[newCapacity];
    transfer(newTable);
    table = newTable;
    threshold = (int)(newCapacity * loadFactor);
}

void transfer(Entry[] newTable) {
    Entry[] src = table;
    int newCapacity = newTable.length;
    for (int j = 0; j < src.length; j++) {
        Entry<K,V> e = src[j];
        if (e != null) {
            src[j] = null;
            do {
                Entry<K,V> next = e.next;
                int i = indexFor(e.hash, newCapacity);
                e.next = newTable[i];
                newTable[i] = e;
                e = next;
            } while (e != null);
        }
    }
}

3. JDK 8 中的 HashMap 实现

在 JDK 8 中,HashMap 的实现进行了优化,主要引入了红黑树来替代链表,以提高在哈希冲突严重时的性能。

  • 通过引入红黑树来替代长链表,在哈希冲突严重时,显著提高了查找、插入和删除操作的性能
  • 在 JDK 7 中,如果所有键都映射到同一个桶,链表会变得很长,导致性能退化到 O(n)。通过引入红黑树,最坏情况下的时间复杂度也能保持在 O(log n)。
  • 在多线程环境下,链表的转换和扩容过程更加稳定,减少了死循环的风险。

3.1 数组、链表和红黑树

HashMap 仍然使用一个数组来存储链表或红黑树的头节点。当链表长度超过一定阈值(默认是 8)时,链表会转换为红黑树

3.2 红黑树

当链表中的元素数量超过阈值时,链表会转换为红黑树。红黑树的查找、插入和删除操作的时间复杂度为 O(log n),比链表的 O(n) 更高效

3.3 哈希冲突处理

仍然使用链表来处理哈希冲突,但当链表长度超过阈值时,转换为红黑树。

3.4 扩容

扩容机制与 JDK 7 类似,但在重新分配键值对时,红黑树的节点会保持树的结构。

4. jdk8 HashMap 在多线程环境下还有可能死循环吗?

在 JDK 8 中,HashMap 引入了一些新的机制和优化,例如使用红黑树来替代长链表,以提高性能和解决哈希冲突严重时的最坏情况。然而,HashMap 仍然不是线程安全的,在多线程环境下使用时,仍然可能会出现一些问题,包括死循环或数据丢失。主要有以下几种情况:

4.1 扩容(Rehashing)

与 JDK 7 类似,JDK 8 中的 HashMap 在扩容过程中,如果多个线程同时进行插入操作,可能会导致数据结构的不一致,甚至形成环形链表,导致死循环

4.2 红黑树转换

当链表长度超过一定阈值(默认是 8)时,链表会转换为红黑树。如果多个线程同时对同一个桶进行操作,可能会导致红黑树结构的不一致,从而引发不可预知的问题

4.3 代码例子

以下是一个简单的示例,展示了在多线程环境下使用 JDK 8 的 HashMap 可能导致的问题:

import java.util.HashMap;
import java.util.Map;

public class HashMapInfiniteLoopExample {
    private static final Map<Integer, Integer> map = new HashMap<>();

    public static void main(String[] args) throws InterruptedException {
        // 创建两个线程同时对 HashMap 进行插入操作
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                map.put(i, i);
            }
        });

        Thread thread2 = new Thread(() -> {
            for (int i = 10000; i < 20000; i++) {
                map.put(i, i);
            }
        });

        // 启动线程
        thread1.start();
        thread2.start();

        // 等待线程完成
        thread1.join();
        thread2.join();

        System.out.println("Map size: " + map.size());
    }
}

4.4 解决方法

为了避免上述问题,可以使用线程安全的集合类,例如 ConcurrentHashMap。ConcurrentHashMap 通过使用分段锁或其他并发控制机制,确保在多线程环境下的安全性。

以下是使用 ConcurrentHashMap 的示例代码:

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class ConcurrentHashMapExample {
    private static final Map<Integer, Integer> map = new ConcurrentHashMap<>();

    public static void main(String[] args) throws InterruptedException {
        // 创建两个线程同时对 ConcurrentHashMap 进行插入操作
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                map.put(i, i);
            }
        });

        Thread thread2 = new Thread(() -> {
            for (int i = 10000; i < 20000; i++) {
                map.put(i, i);
            }
        });

        // 启动线程
        thread1.start();
        thread2.start();

        // 等待线程完成
        thread1.join();
        thread2.join();

        System.out.println("Map size: " + map.size());
    }
}

系列文章

1.JDK源码阅读之环境搭建

2.JDK源码阅读之目录介绍

3.jdk源码阅读之ArrayList(上)

4.jdk源码阅读之ArrayList(下)

5.jdk源码阅读之HashMap

6.jdk源码阅读之HashMap(下)

7.jdk源码阅读之ConcurrentHashMap(上)

[特殊字符] 从零构建携程/飞猪机票价格爬虫:Python异步抓取与反爬对抗实战 在出行决策、竞品分析、价格预测模型训练等场景中,稳定获取携程(Ctrip)和飞猪(Fliggy)的实时机票价格波动数据是刚需。然而,这两大平台均部署了业界顶尖的反爬体系——包括但不限于:动态请求签名(SignToken设备指纹采集(UACanvasWebGL接口参数加密(X-Sign_abtestcookie混淆)频率限制与 IP 封禁(滑动窗口 + 行为检测)目录前言:为什么需要自建机票价格爬虫?一、技术选型与项目架构1.1 核心技术栈1.2 系统架构图(文字描述)1.3 法律与道德声明。 阅读详情

相关推荐

C#计算八字

private string[] jq = { "小寒", "大寒", "立春", "雨水", "惊蛰", "春分", "清明", "谷雨", "立夏", "小满", "芒种", "夏至", "小暑", "大暑", "立秋", "处暑", "白露", "秋分", "寒露", "霜降", "立冬", "小雪", "大雪", "冬至" };private string[] dz = { "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥", "子" };

m0_72939038的博客 822

jdk1.6集合源码阅读之HashMap

2019独角兽企业重金招聘Python工程师标准>>> ...

weixin_33860528的博客 162

STM32与SD卡SPI接口硬件设计:从原理图到调试的完整指南

SPI(串行外设接口)作为一种广泛应用的同步串行通信协议,以其接线简单、驱动成熟的特点,成为连接微控制器与各类外设的经典方案。其工作原理基于主从架构,通过时钟、数据线和片选信号实现全双工通信,在嵌入式系统中具有极高的技术价值,常用于连接传感器、存储设备和显示模块等。在数据存储场景中,SD卡凭借其大容量和便携性成为首选,而通过SPI模式驱动SD卡,则平衡了性能与设计复杂度。本文聚焦于使用Altium Designer进行硬件设计的工程实践,详细解析了STM32微控制器与SD卡通过SPI通信时,电源管理、上拉电

weixin_34101784的博客 545

22.源码阅读(jdk1.6 HashMap源码和原理分析)

HashMap 底层采用数组 + 链表的的实现方式来降低数据插入和查询的时间复杂度,理想状态下可以实现时间复杂度位O(1),今天就从源码的角度看一下它是如何实现的。我们从它的两个关键方法put和get入手。 put方法 public V put(K key, V value) { if...

weixin_34232744的博客 177

抖音Web端私信Protobuf逆向解析实战

本文详细解析了抖音Web端私信通信的逆向过程,发现其核心采用WebSocket传输Protobuf序列化数据。通过抓包获取二进制样本,利用protoc工具进行原始解码,并逐步推导和编写出对应的.proto协议定义文件,最终实现模拟消息的编码与解码,为理解私有协议逆向提供了完整实战案例。

water的专栏 754

FastGPT 引申:借鉴 FastGPT 基于MySQL + ES 实现知识库(含表结构以及核心代码)

FastGPT 引申:借鉴 FastGPT 基于MySQL + ES 实现知识库(含表结构以及核心代码)

蜗牛的专栏 3427

JDK源码阅读项目搭建之记录

一、JDK源码的重要性 JDK源码的重要性不言而喻,平时的深入学习、面试等都离不开JDK的源码。当然,JDK源码是非常优秀的代码,我们之所以阅读JDK源码,就是为了理解底层原理、学习优秀的设计模式和思想。不过JDK源码也是相当难啃的知识点,我们一定要有信心,不要畏难,硬着头皮去学习,一定会有很大的收获的。 二、搭建JDK源码阅读项目 1、新建项目并导入JDK源码 首先新建一个普通的Java项目,笔...

学而时‘享’之,乐乎 4056

【STK】手把手教你利用STK进行导弹和反导仿真04 - STK/MMT模块03 导弹飞行工具应用案例

飞行设置的工具栏就在附加窗口的中间,首先点击左边的General,修改这个项目的名字。然后在下拉的System中选择导弹的类型,这里我们选择远程。(4)输出数据文件到STK,任务窗口里选择Export Manager项,飞行数据输出到STK。项目可以命名为Test;(1)首先建立新项目,点击Flight菜单中的New Flight选项,见下图。首先看世界地图中间的红圈,是一个导弹形状的图标,这个图标表示的是导弹的发射点。选择下面的Export Manager,依次勾选红框的选项。我们拖动这个导弹的图标,

weixin_44184852的博客 1623

如何阅读JDK源码?

阅读源码初期会感到艰涩,但一旦突破某个临界点,你会发现自己对Java乃至编程的理解会产生质的飞跃。如果在阅读具体某个类时遇到难以理解的代码段,随时可以带着具体问题来探讨。阅读JDK源码是进阶为高级工程师的必经之路,它能帮你建立对技术深度的“手感”。、并发容器、线程池),不仅要读懂,还要能口述核心流程,并能在白板上画出数据结构演变图(如。每天花1小时,专注一个类的一个方法,搭配调试和画图。(IoC容器核心)的源码,看看它们如何应用这些JDK基础组件。,并提供一个从源码分析到动手实现的清晰路径。

futurn_hero的博客 894

Google Play上架:谷歌2024年5月份审核政策改动,审核强度逐步提升

Google Play上架:谷歌2024年5月份审核政策改动,审核强度逐步提升

专业解决各种上架过包问题,专业团队专业处理 3186

网络无线AP信号走场测试软件(Ekahau 使用说明)

每次开启 Ekahau 软件需要重复此过程,才可以正常使用。 双击“Ekahau Pro activation.reg”,如下图所示; 点击“Yes”按钮 ,进行注册表合并; 点击“OK”按钮,确认; 运行“Ekahau Pro” 点击“Close”关闭提示; 点击“OK”,关闭提示; ...

Reyn_观极 6390

JDK源码阅读之HashMap演进

HashMap一直是Java开发中的重点,在面试中经常被问及,本文从JDK7出发首先分析其源码了解其不足,进而开始分析JDK8的源码,查看HashMap的改进,加深印象,打败面试。

带翅膀的猫的博客 3933

AI测试入门:基于 RAG 的 LLM 应用程序的测试方法「详细介绍」

一个典型的 RAG 架构包括一个检索器(对知识库进行矢量搜索检索上下文的组件)和一个生成器(从检索器中获取检索上下文以构建提示并生成自定义 LLM 响应作为最终输出的组件)。在大多数情况下, 知识库”由存储在向量数据库(如 ChromaDB)中的向量嵌入组成。

blues_C的博客 1868

如何更高效地阅读JDK源码

简介 阅读源码的几个问题: 为什么要看JDK源码 JDK源码的阅读顺序 JDK源码的阅读方法 为什么要看JDK源码 一,JDK源码是其它所有源码的基础,看懂了JDK源码再看其它的源码会达到事半功倍的效果。 二,JDK源码中包含大量的数据结构知识,是学习数据结构很好的资料,比如,链表、队列、散列表、红黑树、跳表、桶、堆、双端队列等。 三、JDK源码中包含大量的设...

每天叫醒自己的不是闹铃 是娟 是梦 是理想。 1369

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

HashMap源码阅读笔记

本文基于jdk1.8 HashMap采用 key/value 存储结构,每个key对应唯一的value。 在jdk1.7之前,HashMap 的内部存储结构是数组+链表。 在jdk1.8中 HashMap的存储结构是 数组+链表+红黑树,提高了效率。 红黑树 在阅读HashMap源码之前,有必要对红黑树进行一些了解。 红黑树的性质 红黑树是一种自平衡二叉查找树。 红黑树具有如下特性: 1、 任何一个节点都有颜色,黑色或者红色 2、根节点是黑色的 3、 父子节点之间不能出现两个连续的红节点 4、任何一.

三分恶的博客 1167

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

JDK源码阅读之HashMap

JDK源码阅读之HashMapHashMap简介HashMap类图HashMap重要方法方法变量构造方法精华方法putValresize重要方法getNodeputMapEntriescloneHashMap变动HashMap阅读感想说明 HashMap简介 基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作,并允许使用 null 值和 null 键。(除了非同步和允许使用 null...

BlingZeng的博客 527

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

JDK源码阅读,手写HashMap

HashMap是jdk提供的最常用的容器之一,jdk 1.7及之前版本,HashMap底层基于数组和单链表结构,数组每个元素是一对键值对对象,该对象包括hash值,key,value以及单链表下一个键值对的引用。jdk 1.8对HashMap底层结构做了一些改进,当数组同一位置的键值对超过8个,不再以单链表形式存储,而是改为红黑树。进一步提升了性能。

yozzs的博客 592

java的JDK源码阅读顺序整理笔记

很多java开发的小伙伴都会阅读jdk源码,然而确不知道应该从哪读起。以下为小编整理的通常所需阅读的源码范围。 标题为包名,后面序号为优先级1-4,优先级递减 文章目录 1、java.lang 2、java.util 3、java.util.concurrent 4、java.util.concurrent.atomic 5、java.lang.reflect 6、java.lang.annotation 7、java.util.concurrent.locks

宜春 1951

看看别人是如何阅读JDK源码的?

1. 前言 之前断断续续读过一部分 JDK 常用类的源码,这里想把过程中的一些心得和方法记录下来,如果能帮到需要的小伙伴就再好不过了! 本文主要分享一下我的阅读工具和阅读顺序。 PS: 由于当前主流使用的 JDK 版本仍是 1.8,因此源码阅读主要是 1.8 版本,有些地方可以参考 1.7(面试可能问到)。 2. 工具 工欲善其事,必先利其器。 需要的工具不多,IDE + Google 翻译足够了。 使用 IDE 的主要目的是可以写一些测试代码以便跟踪调试。这个还是很有必要的,跟进代码的执行流

895

阅读 JDK 源码:HashMap 扩容总结及图解

本文基于 Java8,通过阅读 HashMap 的 resize 方法了解其扩容原理,并对桶上链表的迁移过程进行调试,画图以加深理解。 文章目录1. 扩容的时机2. 扩容的源码如果是链结构如果是树结构3. 链表迁移算法执行结果执行过程图示4. 总结 1. 扩容的时机 HashMap 中 put 入第一个元素,初始化数组 table。 HashMap 中的元素数量大于阈值 threshold。 threshold = capacity * load factor。 当 size > thresho

Sumkor的博客 8229

JDK源码(1)-阅读指引

说在最前面的话: 其实JDK源码的阅读,网上资料特别多,我阅读的最主要目的是自己学习,所以我读的可能不那么好,我的角度是从源码和源码对应的注释读起,顺便还能练练英语。 接下来准备对JDK的常见源码进行一下阅读,特整理一个学习指南出来供大家参考: 一、准备工作 以JDK1.8版本进行阅读。我下载的是比较新的jdk8u版本。 源码下载地址:http://hg.openjdk.java.net...

十步杀一人-千里不留行 1845

搭建JDK源码阅读环境

1 新建Project和基本Java模块 1.1 新建一个空的Project 项目名称:jdk-demo 1.2 新建一个基础的Java模块 模块名称:jdk-src。用于存放JDK源码。 2 将JDK源码的src.zip文件解压到模块的src目录下 3 替换源码关联 3.1 新增SDK File --> Project Structure --> SDKs 3.2 添加Sourcepath关联,并去除原先关联 4 解决系统内存资源不足(OutOfMemoryError) File –

佳佳的博客 548

我是如何阅读JDK源码的?

1. 前言 之前断断续续读过一部分 JDK 常用类的源码,这里想把过程中的一些心得和方法记录下来,如果能帮到需要的小伙伴就再好不过了!本文主要分享一下我的阅读工具和阅读顺序。PS: 由于...

WriteOnRead 1192

如何阅读jdk源码?

简介 这篇文章主要讲述jdk本身的源码该如何阅读,关于各种框架的源码阅读我们后面再一起探讨。 笔者认为阅读源码主要包括下面几个步骤。 设定目标 凡事皆有目的,阅读源码也是一样。 从大的方面来说,我们阅读源码的目的是为了提升自己的技术能力,运用到工作中,遇到问题快速定位,升职加薪等等。 从小的方面来说,阅读某一段源码的目的就是要搞清楚它的原理,就是死磕,就是那种探索真相的固执。 目的是抽象的,目标是...

whoisyourdaddy6的博客 1029

jdk源码阅读顺序_还在为怎么阅读 JDK 源码犯愁吗?

简介这篇文章主要讲述jdk本身的源码该如何阅读,关于各种框架的源码阅读我们后面再一起探讨。笔者认为阅读源码主要包括下面几个步骤。设定目标凡事皆有目的,阅读源码也是一样。从大的方面来说,我们阅读源码的目的是为了提升自己的技术能力,运用到工作中,遇到问题快速定位,升职加薪等等。从小的方面来说,阅读某一段源码的目的就是要搞清楚它的原理,就是死磕,就是那种探索真相的固执。目的是抽象的,目标是具体...

weixin_39999222的博客 581

JDK 01 搭建JDK源码阅读环境及入门学习

1.环境搭建步骤详细介绍 1.1 创建阅读环境项目 1.2 找到JDK源码路径, 我的 路径即为 D:\JAVA\jdk 如果编译的时候报资源不足错误,则需要去配置一下 1.3 更改JDK的默认配置, 即改后可以进行修改和添加注释 2.测试追溯 HashMap 很烧CPU… ...

Beyond_Nothing 700
上一篇: 5.jdk源码阅读之HashMap(上)
下一篇: 7.jdk源码阅读之ConcurrentHashMap(上)
至真源
博客等级 码龄11年 665粉丝 46原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

至真源

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值