Java正则引发的思考

Java字符串替换:从反斜杠转义到正则表达式双重转义原理详解 在Java编程中,字符串处理是基础且高频的操作,其中转义字符和正则表达式的正确使用是关键。转义字符(如反斜杠)在源代码、字符串内存和正则表达式三个层面有不同的解析规则,理解这些规则是避免常见错误的前提。正则表达式通过模式匹配提供了强大的文本处理能力,但在Java中使用时需要特别注意双重转义机制,即正则表达式本身的转义规则与Java字符串字面量转义规则的叠加。这一机制在实现字符串替换、模式匹配等功能时具有重要技术价值,能够提升代码的灵活性和表达力,广泛应用于数据清洗、路径格式化、文本解析等场景。本文以字符串替 阅读详情

pre: 感谢 九任 对我的支持~

情况回放:

上周预发机器出了一个问题,CPU不定时会近100%满负载运行。重启以后就会恢复,之后又会到达100%,而且不会自恢复。


首先想到的是程序出现了死循环,于是用jstack把栈打印出来,发现业务线程都停在了regex相关的代码上,有死循环的样子。

查看栈,发现一切都是由ClientFilter这个类开始,其使用了matcher.matches()方法。这样一来,就很可能是由于输入了不规范的正则导致的了。于是查看输入日志,发现这么一个输入:


也就是说输入的正则表达式为:******Deliver …,我们的代码会将这种代码规范成:.*.*.*.*.*.*.*Deliver。在java试了一下,试着匹配 "sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss",果然会假死。

那么问题是:为什么输入这种正则会导致假死?


这里的原因是:java使用的是greedy模式来匹配 .*。为了让分析简单,我们将输入改成:.*.*.*.*D,正则需要匹配的字符串为:abcdefghijklmnopqrstuvwxyz0123456789,共36个字符。首先,我们将正则转换成 ”有限自动机(Finite-State Machine)“


那么greedy模式(可参看:java.util.regex.Pattern.Curly.match0(...),另两个是possessive与lazy,分别对应 + 与 ?)的意思就是:最大可能的匹配当前状态(优先匹配粗的路径),当不能匹配时再回溯配置下一个(虚线所示),直到,回溯到cmin个匹配(对于 .* 这个cmin为0)。比如说 .*D,如果想匹配 testDdev,那么Java首先将 .* 转成 .{0, MAX}(这里的MAX应该是2亿多,具体可以看代码),那么 .{0, MAX} 得到的匹配是(java会自动在string后加上一个终止字符,这个字符只能java.util.regex.Pattern.LastNode匹配):

testDev$
RED: 已匹配的部分

当到最后时,java会调用 next.match(matcher, i, seq)

testDev$
RED: 已匹配的部分BLUE: 回溯部分

显然这里 D 不匹配,所以又需要回溯

testDev$
RED: 已匹配的部分BLUE: 回溯部分

显然这里 e 也不匹配,所以还需要回溯,直到回溯到 D,才会正式进入到下一个状态:

testDev$
RED: {0 MAX} 配置的部分BLUE: 回溯部分GREEN: D 配置的部分


testDdev
RED: 已匹配的部分

如下面的代码所示(java.util.regex.Pattern.Curly.match0(...)):


看了上面的示例我想大家应该有点头绪了。现在我们回到 .*.*.*.*D 这里,其在java内经过Pattern.compile之后是这个样子:

type=0,表示使用的就是greedy方式。那么这里面有4个curly,我们用C1-4代表之。首先是C1满匹配:

abcdefghijklmnopqrstuvwxyz0123456789$

我们省略前面几步,看看回溯到5字符有什么特别

abcdefghijklmnopqrstuvwxyz0123456789$

这时候,C1释放出了5个字符,那么这里就相当于 用 .*.*.*D 去配置6789$,那么老样子C2会首先满匹配

abcdefghijklmnopqrstuvwxyz0123456789$

然后next.match(matcher, i, seq),不匹配,再next.match(matcher, i, seq),‘D’也不匹配。只能回溯,我们看看回溯4个字符是什么样子

abcdefghijklmnopqrstuvwxyz0123456789$
这时就相当于用 .*.*D 去匹配 789$ 了,又满匹配,next又不匹配,再回溯,如下:
abcdefghijklmnopqrstuvwxyz0123456789$

就成了用 .*.*D 去match 89$,当 C2-4 都失败后,C1才会再退一个字符,再进行递归:

abcdefghijklmnopqrstuvwxyz0123456789$


我们到底需要多少步才能将这些数字match完?


可想而知,这里的数目有多么大。那么问题来了,我们到底需要多少步才能将这些数字match完?OK,要解决这个问题,关键是要弄清这个递归。

设字符长度为n(加上终止符),正则长度为 m(这里是有效节点,如 .* 是一个节点)。从上面的例子,我们能总结出递归的步骤为:

1、若m=1,返回 1;若m>1,步数 + n

2、回溯 i=1到n-1个字符,对于每个i 取 m=m-1, n=n-i 回1,并把所有的结果求合;


总的来说,用数学公式表示的话,就是这个样子:


这里我写了个简单的实现:


(注:这里的 depth 并不是递归深度,而是递归次数,当时搞错了)

private static long findTotalWays(int regexLength, int targetLength) {
        ++recursionDepth;

        if (regexLength == 1) {
            return 1;
        }

        long totalWays = targetLength;
        for (int i = 0; i < targetLength; i++) {
            totalWays += findTotalWays(regexLength - 1, targetLength - i);
        }

        return totalWays;
    }



好了,现在我们来验证一下我们的结果,通过看jdk源码,我们知道,.* 在匹配时调用的是java/util/regex/Pattern$CharProperty.match 方法,而 D 调用的是java/util/regex/Pattern$BmpCharProperty.match 。由于我们不能更改源代码,我们使用ASM 字节注入工具,分别在这两个方法上埋点,部分代码如下:

package com.alibaba.taobao.tinyprofiler;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;

import org.objectweb.asm.ClassAdapter;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;

public class MethodCallCountTransformer implements ClassFileTransformer {
    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
            ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
        try {
            if (!"java/util/regex/Pattern$CharProperty".equals(className)
                    && !"java/util/regex/Pattern$BmpCharProperty".equals(className)) {
                return classfileBuffer;
            }

            ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
            ClassAdapter adapter = new MethodCallClassAdapter(writer, className);

            ClassReader reader = new ClassReader(classfileBuffer);
            reader.accept(adapter, 0);

            // 生成新类字节码
            return writer.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();

            // 返回旧类字节码
            return classfileBuffer;
        }
    }
}



package com.alibaba.taobao.tinyprofiler;

import org.objectweb.asm.MethodAdapter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

public class CountCallMethodAdapter extends MethodAdapter {
    private String methodName;

    private String className;

    public CountCallMethodAdapter(MethodVisitor visitor, String className, String methodName) {
        super(visitor);

        this.methodName = methodName;
        this.className = className;
    }

    @Override
    public void visitCode() {
        visitLdcInsn(className);
        visitLdcInsn(methodName);
        visitMethodInsn(Opcodes.INVOKESTATIC, "com/alibaba/taobao/tinyprofiler/Counter", "printAndIncCount", "(Ljava/lang/String;Ljava/lang/String;)V");

        super.visitCode();
    }
}

package com.alibaba.taobao.tinyprofiler;

import java.util.concurrent.atomic.AtomicInteger;

public class Counter {
    private static AtomicInteger methodCallCount = new AtomicInteger(0);

    public static void printAndIncCount(String className, String methodName) {
        System.out.println(className + "." + methodName + " called, total times " + methodCallCount.incrementAndGet());
    }
}

OK,现在我们输入:

String regex = ".*.*.*D";
String target = "22asdvasdx";
Pattern.compile(regex).matcher(target).matches();
System.out.println("Xuanyin's estimated count: " + findTotalWays(4, target.length()) + "; depth: " + recursionDepth);

输出结果(注:这里的 depth 并不是递归深度,而是递归次数,当时搞错了):


肿么样,分毫不差~OK,那么我们现在回到最开始的问题,输入 .*.*.*.*.*.*D 去匹配 com.taobao.binary.bogda.query.service.RulesInfoQueryService:1.0.0.daily




结果显示需要 5 亿 匹配, 还要进出栈近 2.5 亿次哦

这里我的机器是i7-2600K 超4.5G,结果显示需要5秒,这还是不是最差的情况哦~


而每次用户查询要匹配近600个这样的字符串,~你说匹配得完嘛





---------------------草稿------------------------
设字符长度为n,.* 的个数为m(C1-m) ,且需要match的字符串不匹配(为什么?),那么C1到满mach首先需要n步,之后回溯一个字符。那么回溯的字符无论怎么样总会被一个C给匹配,被Ci匹配了就不会被Cj 匹配(i <> j),那么可能的情况有:

C1C2C3C4
n000
---------------------------------
n-1100
n-1010
n-1001
即3种,当回溯2个字符时

C1C2C3C4

n000
---------------------------------
n-1100
n-1010
n-1001
---------------------------------
n-2200
n-2110
n-2101
n-2020
n-2011
n-2002
即6种

好了,现在我们知道其规律了,这个问题就可以简化为:有n’个相同的球,投向m个槽,有多少种投法?注意这里是同相的球,其结果显然不是 m^n。

那么是多少?我们可以这样理解:
1、先算第一层,所有的球最多可以填 个槽,就里有就种情况
2、第二层的球实际上就只有 i 个槽可选了,这时有 n'- i 个球。若 i = 1,返回 n’ - i,若n’-i <=1 返回 1,否则递归,m=i,n’ = n’ - i,回1

总的说来这个递归还是很简单的,整理一下,如下:


这里我写了一个简单的实现:

   private static long findTotalWays(int stackSize, int ballCount) {

       if (stackSize == 1) {

           return ballCount;

       }


       if (ballCount == 0) {

           return 1;

       }


       int maxSlotsCanFill = stackSize > ballCount ? ballCount : stackSize;


       long totalWays = 0;

       while (maxSlotsCanFill > 0) {

           totalWays += combination(stackSize, maxSlotsCanFill)

                   * findTotalWays(maxSlotsCanFill, ballCount - maxSlotsCanFill);

           maxSlotsCanFill--;

       }


       return totalWays;

   }


   private static long combination(int n, int m) {

       if (m == 0) {

           return 0;

       }


       if (m > n) {

           return 1;

       }


       return factorial(n) / (factorial(m) * factorial(n - m));

   }


   private static long factorial(int n) {

       if (n <= 1) {

           return 1;

       }


       return n * factorial(n - 1);

   }

当C1回溯到第n-1个字符时,我们套套公试看看有多少种情况:

f(3, 36) = 8119

好,8k种左右,完了吗?当然不是!这只是第n-1次回溯,那么应该从0(因为*号可以匹配空)开始到n。全部回溯的数量应该是:

其中k=N - [C1最低需要匹配的字符数],。那么如上所述,现在计算C1完成第一个字符需要match的步骤,将k=36, m=3 代入得:

g(3, 36) = 78331

好,近8万种,完了吗?当然不是,由于每个字符必会被C1-4之中的一个匹配,所以对于每种情况,需要match的步数为N。同时细心的同学可以发现其实g(m,k)=f(m+1,k),当然我们表示成g(m,n)还是有其它原因的。所以最终的式子为:





N是字符串的总长度,M 就是 [总.*个数] 。现在将N=36 M=4代入看看

sum(4, 36) = 78331

好,7万的样子,现在我们看看Java匹配这个正则需要多长时间:

long startTime = System.nanoTime();

Pattern.compile(".*.*.*.*D").matcher("abcdefghijklmnopqrstuvwxyz0123456789").matches();

System.out.println("time used: " + (System.nanoTime() - startTime));

时间显示是3961571ns,也就是7万3.96ms。好,现在我们来输入导至问题的正则:

也就是 .*.*.*.*.*.*Deliver,有6个星号。预发环境下需要匹配的字母串为:com.taobao.matrix.apps.cooperate.service.CooperateShareService:2.0.0.daily,其长度为:74

套公式,sum(6, 74) = 268010793

soga,2.68亿的样子,那么 (300500199/91389)*3.85ms= 12659.355 ms,也就是12.659秒

但实际上执行上面的程序在同样的配置上只花了6.482秒的样子?咦?差距这么多,难道计算有错?实现上我们只算了种类数量,对应的匹配数量还没有计算呢







英特尔® NUC迷你电脑实现无人值守自动开机配置指南 本文详细介绍了如何配置英特尔® NUC迷你电脑实现无人值守自动开机功能。从BIOS设置到操作系统级配置,涵盖硬件兼容性确认、电源恢复功能设置及常见问题解决,帮助用户轻松实现设备断电后自动恢复运行,适用于家庭服务器、企业终端等多种场景。 阅读详情

相关推荐

Coze知识库搭建:智能化管理与高效检索实践

知识管理系统是现代企业信息资产管理的核心基础设施,其核心原理是通过结构化存储和智能检索技术实现知识的快速流转。在技术实现上,基于NLP的语义理解引擎和知识图谱构建是关键,能够显著提升检索准确率和知识关联性。以Coze平台为例,其针对中文场景优化的分词算法和多模态处理能力,可有效解决技术文档管理中的术语识别和格式兼容问题。这类系统在开发团队知识沉淀、新员工培训等场景中价值显著,特别是当集成RBAC权限模型和自动化备份策略后,既能保障信息安全,又能实现持续的知识迭代。通过合理配置同义词扩展和权重调整,可使搜索命

weixin_29041195的博客 262

java正则表达式引起死循环导致程序卡主

正则表达式使用贪婪型匹配,在调用matcher.find()方法时,引起程序卡死,不执行后续的代码

lieying411 4774

x86架构学习之中断

外部中断,一般是由CPU的外部引脚电信号触发,这些引脚留给硬件厂商去定义中断,例如主板厂商可以将USB中断挂载70号中断引脚上,MCU厂商可以将片上外设(SPI、I2C等)的中断挂载在指定中断线上。中断描述符主要用于说明该中断的入口地址,中断描述符表则是将中断描述符一项一项管理在一个数组中,每一项中断描述符占用8个字节(64位模式下,占用16字节),其。简称中断,后文如果没特别说明,中断都指外部中断,由外部IO事件触发,由于时钟源来自CPU外部,与CPU时钟不同源,因此是异步事件。

qq_26962739的博客 1571

Pattern和Matcher的结合使用

Pattern和Matcher java.lang.Object ————java.util.regex.Pattern public final class Patterne xtends Object implements Serializable 正则表达式的编译表示形式。  因此,典型的调用顺序是   Pattern p = Pattern.compile("a*b

whaleluo的博客 1859

0基础手把手搭建 Trae\+Seedance2\.0 真人短剧生成智能体(可直接运行)

前言:本文专为0基础新手编写,全程无复杂代码、无专业术语,一步一截图提示(操作路径清晰),只需跟着点鼠标、复制粘贴,就能搭建出可自动生成真人短剧的智能体,生成的短剧可直接导出发布。核心工具:Trae(字节跳动AI IDE,免费)+ Seedance2.0(真人视频生成API,公测免费),无需提前学习编程、设计,全程10-15分钟搞定。 适用人群:0基础小白、想快速生成真人短剧(自媒体、短视频创作者)、无需懂技术即可实现自动化短剧生成的人群。

skygulangfendouz的专栏 1058

音频测试专题

麦克风与扬声器单体测试指标及整机性能测试概述 麦克风测试指标包括频率响应、灵敏度、指向性等10项关键参数,用于评估声音捕捉和信号转换性能。扬声器测试包含频率响应、灵敏度等12项指标,涉及声学特性和物理参数。整机性能测试覆盖安静、嘈杂和正常三种环境,重点检测麦克风底噪、清晰度、扬声器音量及通话质量等12项功能指标。两类单体测试与整机测试共同构成完整的音频设备质量评估体系,确保产品在不同场景下的性能表现。

weixin_52640906的博客 1317

关于正则效率问题(正则导致程序卡死)

前言:   在Java中,正则的使用需要谨慎,好的正则可以方便我们的代码,但是不好的正则,可能成为黑客攻击的漏洞。类似本例子的正则,黑客可以组织不同的匹配字符,使得校验不过,耗尽服务器资源(资源耗尽攻击)。详见正则的状态机原理。 1.说明:2018/8/17: 校验输入字符串是否合规,允许:100000000000^145^1^1000.00|100000000000^14...

weixin_30568591的博客 1397

【新手必看】挖漏洞流程指南,黑客手把手教你挖漏洞月入过万!收藏这篇就够了!(附学习笔记)

【新手必看】挖漏洞流程指南,黑客手把手教你挖漏洞月入过万!收藏这篇就够了!(附学习笔记)【新手必看】挖漏洞流程指南,黑客手把手教你挖漏洞月入过万!收藏这篇就够了!(附学习笔记)

weixin_57514792的博客 1105

delight-nashorn-sandbox JsSanitizer 第2环节,注入函数调用时,使用正则匹配可能导致死循环,进而线程CPU飙升

环境: java version "1.8.0_271" 1. 现象 JsSanitizer.java /** * The name of the variable which holds reference to interruption checking * class. To prevent collisions random suffix is added. */ final static String JS_INTERRUPTED_TEST = "__it"; p

yhtppp的专栏 679

【AI 编程工具】Codex配置部署指南

目录一、环境准备与基础安装1. 系统兼容性检查2. 前置依赖安装3. 安装方式选择二、API密钥配置与认证1. 获取API密钥2. 配置文件设置3. 临时配置方式三、沙盒环境配置与安全策略1. 沙盒模式选择2. 安全最佳实践四、高级功能配置1. 模型上下文协议(MCP)配置2. 模型版本切换3. 自定义网关配置五、常见问题排查1. 安装失败2. API调用失败3. 处理速度慢4. 配置文件不生效六、生产环境部署建议macOS/Linux:官方提供稳定支持,推荐使用原生环境Windows:官方标注为实验性支持

以实战经验介绍行业知识 2893

【3天快速入门WPF】11-附加属性

创建一个静态类,用于定义附加属性。使用方法注册附加属性。提供Get和Set方法来获取和设置附加属性的值。通过以上步骤,我们可以在WPF中轻松实现和使用附加属性。附加属性的主要用途包括为控件添加自定义行为、控制布局信息(如Grid.Row和)以及实现动态样式。

Zy100Papa的博客 679

【字节码增强ASM3.0】Java正则引发的思考--测试困惑--有解了

1 表情符号源代码 https://github.com/zly394/EmojiRegex/blob/master/src/main/java/com/zly/utils/EmojiRegexUtil.java 2 测试用例 import java.lang.instrument.Instrumentation; public class Main2 { public st...

qfzhangwei的专栏 493

Qt/C++ 音视频开发: 共享解码线程/重复利用解码/极低CPU占用/画面同步/进度同步

通过共享解码线程和重复利用解码技术,可以显著降低CPU占用,提高多媒体应用的性能。同时,画面和进度同步技术保证了视频播放过程的平滑体验。

走向CTO的路上... 988

java连续多位相同字符判断的正则表达式_密码验证连续多位相同或者顺序字符引发的思考.md...

密码验证连续多位相同或者顺序字符引发的思考需求虽然用户对于这种复杂的密码验证恨之入骨,但是有时出于安全的考虑,我们系统不得不强制要求用户设置更高强度的密码。如:不能连续出现多位相同或者连续的字符。思考对于常规的验证,我们通常使用正则表达式,所以我一开始也从这方面入手,但始终没有一个实现的思路。后来看到有人用字符数组,通过下标判断是否连续,再加上偶然看到有人在讨论 ASCII 码,灵机一动,结合这两...

weixin_34007888的博客 1953

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

刘亦菲生日当天,引发了我对正则的思考

前两天从网上采集到一条短视频数据(刷短视频),发现六公主连排5部刘亦菲主演的电影!甚是震惊,太有牌面了,看了一下日子是8月25号,嗷,原来当天是刘亦菲的生日。巧了,正好也是我家柴犬旺财的3岁生日😀。more言归正传,我们看到这条数据的:#刘亦菲35岁生日获央视独宠# 神仙姐姐生日快乐!为了分析数据,我们需要获取数据中所提到的话题。提问:你能想到几种实现方式呢?欢迎评论区留言。

日常开发分享 406

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

去除html富文本标签操作引发的思考

之前在修正项目的bug 的过程中遇到前端姐妹无法解决富文本标签暴露问题,于是bug就推到我们后端去解决这个问题. (尊重原创)去除富文本标签的方法解决方案链接: 转载 —java去掉富文本的html标签方法 - ben跑的换行符 - 博客园(正则方式去除) java实现富文本过滤标签 - cqrunnerxjzh - 博客园(利用第三方包去解决) 当时记得有很多种解决方案,其中最简单的方法就是用正则表达式去除富文本标签的 原本只是解决bug,但是后期引发了一些了的思考,就是完成这样一个需求:

Hana335566的博客 988

java正则匹配及替换的一点思考

Java语言String类提供了简单的relace,replaceAll等方法: 在很多简单的情况下,这几个方法就能满足使用了。但是较为复杂的的情况下就不能仅靠这几个方法解决问题了。由于在实际工作中遇到的问题,引发如下的思考:一个字符串含”hello world”,把hello 替换为hello hello,调用replaceAll会导致无穷死循环吗?java显现不会导致这样的问题发生,答案是不会

tmzk的学习笔记 819

Hamming Distance 引发的思考

算法学习之Hanmming Distance

Bossy的博客 774

Java开发与技术挑战——关于技术的技术思考

 到目前为止,接触Java语言已经有将近10个年头。从趴在学校实验室中认领师兄提供的论坛系统开发任务,到参与WebERP构件化平台、网络资源聚合平台、电信增值业务平台、企业门户网站以及运营管理平台的研发,再到后面的掌上证券、软件商店、移动支付平台等移动互联网平台建设,不论是PC端,还是移动端,底层的技术一直都是用Java架构来进行实现的。期间参与过多次的Java技术培训与问题指导,以扑火队员

machine_chen 931

一个正则引发的 java CPU异常问题

为什么80%的码农都做不了架构师?>>> ...

weixin_33828101的博客 292

密码验证连续多位相同或者顺序字符引发的思考

密码验证连续多位相同或者顺序字符引发的思考 密码验证连续多位相同或者顺序字符引发的思考 需求 思考 实现 总结 参考 需求 虽然用户对于这种复杂的密码验证恨之入骨,但是有时出于安全的考虑,我们系统不得不强制要求用户设置更高强度的密码。 如:不能连续出现多位相同或者连续的字符。 思考 对于常规的验证,我们通常使用正则表达式,所以我一开始也从这方面入手,但始终没有...

小赞的技术园子 2270

一次性上传300张图片引发的思考

批量上传300张图片耗时8s

Dの博客 6859

一个“java.lang.NoSuchMethodError”异常引发的思考

这几天一直很郁闷,我的系统在启动后,访问某个页面一直抛错: ERROR impl.WebxRootControllerImpl - Error occurred while process request /auction/publish/publish.htm java.lang.NoSuchMethodError: com.taobao.shopservice.client.tai

gdp5211314的专栏 916

java 非法字符判断_关于“判断字符串是否含有非法字符”的思考

项目案例项目中遇到一项任务:禁止文件名含有中文字符、空格等非法字符的文件,在circleci持续整合中进行排查。那思路很简单呢,用中文字符正则表达式去检查项目下文件路径就好了不是吗?那这样我们写出的代码是:const str = '我有中文字符';if(/.*[\u4e00-\u9fa5]+.*$/.test(str)){throw new Error('不能含有汉字');}黑名单和白名单什么是黑...

weixin_29827279的博客 3117
上一篇: Java Worker 设计模式
下一篇: Subversion原理
Sefler
博客等级 码龄19年 9粉丝 16原创
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值