Java String底层实现详解结合JVM

Java教程之String类的底层原理和版本演变 intern() 方法将这个字符串对象尝试放入常量池中,并返回地址。jdk1 . 6 中:如果池中有,则不会放入,返回已有的池中的对象的地址。如果池中没有,则把此对象重新创建一份,放入池中,并返回池中新的对象地址。jdk1 . 7 起:如果池中有,则不会放入,返回已有的池中的对象的地址。如果池中没有,则把此对象的引用地址复制一份,放入池中,并返回池中的引用地址。 阅读详情

Java-String


基于Oracle JDK11和JVM 11

1.String1简介

Java数据类型分两种:基本数据类型和引用数据类型。关于基本数据类型,可以参考我这篇博文Java极简入门教程——3.基本数据类型和变量_编程还未的博客-优快云博客

除了八个基本数据类型之外,其他的都是引用数据类型。八个基本数据类型中,并没有包含字符串。Java提供了一个类,来储存字符串类型的值。自然而然,类叫 String。

String类表示字符串。Java程序中的所有字符串文本(例如“Hello World”)都是String类的实例(实例既对象)。

String有几个特点:

  1. String类是不可变的;它的值在创建后无法改变。
  2. 针对常量池的优化。
  3. final修饰
  4. javac 编译器可以根据 JDK 版本使用 StringBuffer、StringBuilder 或 java.lang.invoke.StringConcatFactory 实现运算符(+)

前三个比较重要。

String使用有两种方式,隐式创建和显示创建。

隐式创建

String abc = "张三";

显示创建

String abc = new String("李四");

1.1.验证String是不可变对象

学习一个方法System.identityHashCode(),打印对象的hash码值,这个值和默认的hashCode()返回的一样。

先看一小段代码

String abc = "张三";
System.out.println(abc);
abc = "李四";
System.out.println(abc);

输出结果:

张三
李四

对于这段代码,会不会觉得String对象从“张三”变成了“李四”?有过有此疑问的话。我再放一点代码,验证一下。

String abc = "张三";
System.out.println(abc);

//System.identityHashCode 打印对象的hash码值
System.out.println(System.identityHashCode(abc));

abc = "李四";
System.out.println(abc);

//System.identityHashCode 打印对象的hash码值
System.out.println(System.identityHashCode(abc));

//打印第二遍是为了证明同一个对象的hash码值不会因为多次打印而改变。
System.out.println(System.identityHashCode(abc));

结果如下:

张三
1915910607
李四
284720968
284720968

是不是很震惊。abc指向的对象不一样了。这就是Java中String的特性,不可变对象。

我们解释一下刚才的代码,写在注释里面

//创建一个String对象,字符串文本值是“张三”,
//声明String类型的引用abc,指向值是“张三”的String对象
String abc = "张三";
System.out.println(abc);

//引用指向 值是“李四”的对象。之前值是“张三”的对象并没改变,
//而是创建了一个新对象,abc指向了新对象
abc = "李四";
System.out.println(abc);

注释看完后,这两行代码代码在内存中的变化看下图。关于JVM运行时数据区不太懂的,可以参考我这篇博文面试必备——图文剖析JVM的运行时数据区_编程还未的博客-优快云博客

为了更好的理解,把我关于JVM运行时数据区的部分内容,贴过来。

Java会为每个类和接口维护一个运行时常量池(run-time constant pool )。类或接口的二进制表示形式(字节码)中的常量池表用于在创建类或接口时构造运行时常量池。

运行时常量池中所有引用最初都是符号引用。这些符号按照如下方式,从类或接口的二进制表示形式(字节码)中得出:

其中一项是字符串常量。字符串常量是指向String类实例的引用,它来自于类或接口二进制表示(字节码)中的CONSTANT_String_info结构。CONSTANT_String_info结构给出了由Unicode码点序列组成的字符串常量。Java语言规定,相同的字符串常量必须指向同一个String实例。2

下面两项是正确的:

  1. 如果以前在包含与结构给定的相同的 Unicode 码位序列的类实例上调用了该方法,则字符串常量是同一类实例的一个。
  2. 否则,将创建一个新的类实例,其中包含结构给出的 Unicode 码位序列。字符串常量获取的结果是指向那个新String实例的引用。最后,新String实例的intern方法被虚拟机自动调用。

原文如下:

  • A string constant is a reference to an instance of class String, and is derived from a CONSTANT_String_info structure. To derive a string constant, the Java Virtual Machine examines the sequence of code points given by the CONSTANT_String_info structure:
    • If the method String.intern has previously been invoked on an instance of class String containing a sequence of Unicode code points identical to that given by the CONSTANT_String_info structure, then the string constant is a reference to that same instance of class String.
    • Otherwise, a new instance of class String is created containing the sequence of Unicode code points given by the CONSTANT_String_info structure. The string constant is a reference to the new instance. Finally, the method String.intern is invoked on the new instance.

总结一下上面的内容,得到两点:

  1. 运行时常量池中有字符串常量,字符串常量是指向字符串实例的引用。
  2. 相同的字符串常量必须指向同一个String实例。

基于上面两点,以及对象在堆中(不了解这个的,可以看一下我的博文面试必备——图文剖析JVM的运行时数据区_编程还未的博客-优快云博客)。

【字符串常量池在堆中】这个观点,在JVM规范中并无体现。至于HotSpot有没有用【堆中的字符串常量池】实现上述总结的两点,不确定。

我们画一下String的内存示意:

String abc = "张三";

新建对象

abc = "李四";

指向新对象

由此可以看出,“张三”对象还在,abc指向了“李四”对象。

内存图+hashCode码值,为String类是不可变的; 提供支持。

1.2.两种创建字符串对象的区别

Java的String有两种创建对象的方式。

第一种

String abc = "张三";

第二种

String cba = new Stgring("李四");

第一种,会在方法区的运行时常量池的字符串常量中寻找,是否有对象“张三”的引用,有就直接指向;没有就在对创建对象“张三”,把“张三”对象的引用保存到运行时常量池的字符串常量中,abc再指向字符串常量中的引用。

第二种,堆创建对象,检查运行时常量池的字符串常量,有没有指向“李四”对象的引用。如果有,刚才new String()创建的对象,指向字符串常量中“李四”的引用;如果没有,就在堆中创建“李四”对象,“李四”对象的引用保存到字符串常量中,cba指向字符串常量。

放两种情况的字节码,注释放在字节码中。字节码指令官方3

第一种字节码

//String abc = "张三";

LINENUMBER 9 L0
LDC "\u5f20\u4e09"
ASTORE 1

第一种字节码带注释

//String abc = "张三";

//源码的行数 第9行
LINENUMBER 9 L0
//LDC 从运行时常量池推送到顶(Push item from run-time constant pool),如果是字符串常量,则把值推出来
//"\u5f20\u4e09"是“张三”的Unicode
LDC "\u5f20\u4e09"
//ASTORE 存储到局部变量中(Store into local variable reference)     
ASTORE 1

第二种字节码

//String cba = new Stgring("李四");

LINENUMBER 11 L0
NEW java/lang/String
DUP
LDC "\u674e\u56db"
INVOKESPECIAL java/lang/String.<init> (Ljava/lang/String;)V
ASTORE 1

第二种字节码带注释

//String cba = new Stgring("李四");

//源码的行数 第11行
LINENUMBER 11 L0
//NEW 创建对象(Create new object)    
NEW java/lang/String
//DUP 复制顶部操作数堆栈值(Duplicate the top operand stack value)    
DUP
//LDC 从运行时常量池推送到顶(Push item from run-time constant pool),如果是字符串常量,则把值推出来
//"\u674e\u56db"是“李四”的Unicode    
LDC "\u674e\u56db"
//INVOKESPECIAL 调用实例方法;直接调用实例初始化方法和当前类及其超类型的方法
//(Invoke instance method; direct invocation of instance initialization methods and methods of the current class and its supertypes)    
INVOKESPECIAL java/lang/String.<init> (Ljava/lang/String;)V
//ASTORE 存储到局部变量中(Store into local variable reference)     
ASTORE 1

第一种的内存示意图

String abc = "张三";

String新建对象

第二种的内存示意图

新建对象new

1.3.创建了几个对象

1.3.1.情况1
String abc = "张三";

0个对象或1个对象。

  1. 0个对象:如果运行时常量池的字符串常量中有“张三”的话,abc直接指向运行时常量池的字符串常量中的“张三”;
  2. 1个对象:如果运行时常量池的字符串常量中没“张三”的话,先创建对象,对象存储的内容是“张三”,把“张三”放到运行时常量池的字符串常量中。

内存情况,比较简单跟上面的图一样,不重复了。

1.3.2.情况2
String abc = "张三";
String def = "张三";
//打印 abc 的hash码值
System.out.println(System.identityHashCode(abc));
//打印 def 的hash码值
System.out.println(System.identityHashCode(def));

0个对象或1个对象。分析同情况1。

这里只分析String abc = "张三";就可以了。程序的运行结果如下:

1915910607
1915910607

abc的hash码值和def的hash码值是一样的,能证明指向的是同一个对象。

或者用比较也可以,结果是true。==比较对象的地址值,equals比较的是字面量。

String abc = "张三";
String def = "张三";
System.out.println(abc==def);

为什么出现这种情况?def是在干嘛?

JVM的机制,会把String的值,比如“张三”保存到运行时常量池的字符串常量中,方便后续使用,而不重复创建对象,节省开销。这个在上面也讲过了,是Java语言规范的规定。

def发现字符串常量中有指向“张三”对象的引用,直接指向字符串常量。内存情况如下

两个引用指向同一对象

1.3.3.情况3
String abc = "张三";
String hig = new String("张三");
System.out.println(System.identityHashCode(abc));
System.out.println(System.identityHashCode(hig));

输出结果是如下

1915910607
284720968

说明abc和hig指向的不同的对象。

abc那行代码:0个或1个,分析跟上面情况1一样。

hig那行代码:1个对象。new String()肯定会创建1个对象,因为已有“张三”对象的引用在运行时常量池的字符串常量中,new String()会保存字符串常量的引用。

内存分析如下:

引用和new1

1.3.4.情况4
String abc = "张三";
String klm = new String("李四");
System.out.println(System.identityHashCode(abc));
System.out.println(System.identityHashCode(klm));

运行结果

1915910607
284720968

abc那行代码:0个或1个,分析同情况1

klm那行代码:1个或2个。new String()肯定会创建1个对象。“李四”如果在运行时常量池的字符串常量中,就不会在新创建对象了;没有的话就创个对象,把引用存到运行时常量池的字符串常量中。最后都是字符串常量中的引用保存到刚new String()的对象中。所以是1个或2个。

内存分析如下:

引用和new2

2.String实现

这是Java中最常用的一个类,如果看过Java内存的话,会发现占用内存最多的就是char[](Java8及之前)或者byte[](Java8及之后),这也能看出String的重要性。怎么看出来的呢?

看下面源码:

    /**
     * The value is used for character storage.
     *
     * @implNote This field is trusted by the VM, and is a subject to
     * constant folding if String instance is constant. Overwriting this
     * field after construction will cause problems.
     *
     * Additionally, it is marked with {@link Stable} to trust the contents
     * of the array. No other facility in JDK provides this functionality (yet).
     * {@link Stable} is safe here, because value is never null.
     */
    @Stable
    private final byte[] value;
    /**
     * Initializes a newly created {@code String} object so that it represents
     * the same sequence of characters as the argument; in other words, the
     * newly created string is a copy of the argument string. Unless an
     * explicit copy of {@code original} is needed, use of this constructor is
     * unnecessary since Strings are immutable.
     *
     * @param  original
     *         A {@code String}
     */
    @HotSpotIntrinsicCandidate
    public String(String original) {
        this.value = original.value;
        this.coder = original.coder;
        this.hash = original.hash;
    }
  1. private final byte[] value;用来存储。
  2. public String(String original) {}构造方法,也是把值存到value。

因为Java中使用是String是最多的,String底层是byte[],所以通常JVM内存镜像中,byte[]占用最多内存。

2.1.不可变如何实现

先讲一下final4。这里只提两点:

  1. 修饰属性:只能在初始化时赋值,且只能赋值一次,赋值后值无法改变。
  2. 修饰类:被修饰的类无法被继承。

上面提到过,String底层用private final byte[] value来存值,这个value是byte[]数组,初始化一次后不可再变;且并没有提供方法类修改这个byte[]内部的值;private修饰,只能在本类中使用;final修饰不可被继承,没子类重写里面的方法。

最主要的是:初始化时赋值一次,不提供任何修改底层存储值的方法。

3.可变的字符串

要想使用可变字符串,Java提供了StringBuilder、StringBuffer,可以使用这个类。

这俩类的主要区别是:StringBuffer是线程安全的、速度慢一些,StringBuilder是非线程安全的,速度快一些。


  1. 引用资料:Java11语言规范、JVM11规范、JDK11源码 ↩︎

  2. String Literals ↩︎

  3. Instructions ↩︎

  4. final ↩︎

Java中字符串String类,String类型的底层原理,什么是StringBuffer,StringBuffer类的原理 String、StringBuffer 阅读详情

相关推荐

Java中char转String的四种写法与JVM底层原理

字符串类型转换是Java基础中的关键概念,涉及原始类型与引用类型的交互、JVM内存管理及性能优化。char作为16位无符号整数,转为String本质是构建不可变的final char[]封装对象,其路径直连字符串常量池机制、堆内存分配策略与JIT编译优化。不同写法(如c + ""、new String、String.valueOf、Character.toString)在字节码层面触发完全不同的对象创建行为,导致显著的GC压力与执行耗时差异。String.valueOf(c)因复用char数组、规避拷贝且被

346

【JavaSE】从JVM理解String、StringBuffer类

String类是Java类中最重要也是最常用的一个大类,它包含了大量的成员方法功能十分强大,类的存在往往伴随着对象的创建,类的加载,引用类型的数据就存在地址的指向问题,而这在JVM中就有生动的体现

懒羊羊的博客 1640

Java String底层源码分析

在 JDK 9 及以后,Stringvalue是一个字节数组,用于存储字符串的数据。coder是一个字节值,指示字符串的编码(LATIN1 或 UTF16)。是一个本地方法:它通过本地代码实现高效的数组复制。参数检查和边界验证:在进行实际的内存复制之前,进行必要的参数检查和类型验证。高效内存复制:通常使用底层系统的内存复制函数(如memmove)来实现高效的数组数据移动。优化:针对不同的数据类型和平台进行专门优化,以提供最佳的性能。

Enzo的探索之路 975

不懂String?Java中String底层原理详细解析,以及String常见的错误和解决方式,跟着我带你get到String精髓

Java中的String是一个非常常用的类,用于表示不可变的字符序列。下面对String类的底层原理进行详细说明,并附带代码和经常遇到的错误解析。String类使用一个char类型的数组来保存字符串中的每个字符,同时还有一个int类型的变量count来表示字符串的长度。由于String类是不可变的,因此一旦创建了一个String对象,就不能修改其中的字符序列。当使用String。

GuoFuMengXiang的博客 2713

详解Java中String类的不可变性与底层原理

本文主要介绍了Java中String类的不可变性与底层原理,希望能对学习Java的同学们有所帮助。

weixin_43178406的博客 4万+

基于结构的虚拟筛选模型 gnina 从源码编译

所需要的软件安装包: 安装 CUDA 装的时候看着点,第二个要你装驱动的选择,回答N,不装。 sudo sh cuda_10.0.130_410.48_linux.run sudo vim ~/.bashrc 在 ~/.bashrc 文件最后加上 export PATH=/usr/bin:/usr/local/cuda-10.0/bin${PATH} export LD_LIBRARY_PATH=/usr/local/cuda-10.0/lib64:${LD_LIBRARY_PATH} source ~

周迪新的博客 3187

【jvm系列-08】精通String字符串底层原理和运行机制(详解)

深入理解String字符串的底层机制

zhenghuishengq的博客 9686

别再只会调速度了!用STM32的定时器中断精准控制伺服电机转角(避坑指南)

本文详细介绍了如何利用STM32的定时器中断和PWM输出实现伺服电机的精准转角控制,避免常见的速度控制误区。通过硬件配置、脉冲计数逻辑和优化技巧的实战讲解,帮助开发者掌握高精度位置控制方法,适用于工业自动化和机器人控制等场景。

weixin_42531163的博客 414

从底层彻底搞懂String,StringBuilder,StringBuffer的实现

在深入学习字符串类之前, 我们先搞懂JVM是怎样处理新生字符串的. 当你知道字符串的初始化细节后, 再去写 Strings="hello"或 Strings=newString("hello")等代码时, 就能做到心中有数。 首先得搞懂字符串常量池的概念。 常量池是Java的一项技术, 八种基础数据类型除了float和double都实现了常量池技术. 这项技术从字面上是很好理解的: 把经常用到...

爱的叹息的专栏 1万+

图文带你理解什么是Few-shot Learning

Few-shot Learning(少样本学习)是Meta Learning(元学习)中的一个实例1,所以在了解什么是Few-shot Learning之前有必要对Meta Learning有一个简单的认识。不过在了解什么是Meta Learning之前还是要了解一下什么是Meta。因此,阅读本文后你将对如下知识有一个初步的了解。 What is Meta What is Meta Learning What is Few-shot Learning 1. What is Meta? meta就是描述

Pr4da的博客 1万+

LabVIEW小白福音:手把手教你用NI采集卡搭建振动信号采集系统(附源码和避坑指南)

本文为LabVIEW初学者提供详细的NI采集卡振动信号采集系统搭建指南,涵盖硬件连接、驱动安装、信号处理及性能优化等关键步骤。特别针对振动信号采集系统中的常见问题提供解决方案,并附源码和避坑指南,帮助用户快速上手并避免常见错误。

weixin_30485379的博客 103

java高级——String字符串探索(在jvm底层中如何实现,常量池中怎么查看)

## 4. String类常用的方法 1. endsWith:判断字符串是否以指定的后缀结束 2. startsWith,判断字符串是否以指定的前缀开始 3. `equals`,字符串相等比较,不忽略大小写 4. `equalsIgnoreCase`,字符串相等比较,忽略大小写 5. `indexOf`,取得指定字符在字符串的位置 6. lastIndexOf,返回最后一次字符串出现的位置 7. length,取得字符串的长度 8. `replaceAll`,替换字符串中指定的内容,注意赋值原字符串 9.

蓝桉 1108

17、扩展景深成像的瞳孔函数工程技术解析

本文详细解析了扩展景深成像的关键技术,包括基于焦距堆栈的数字处理方法、立方相位掩模的光学设计、对数非球面透镜的应用以及旋转点扩散函数的深度编码功能。通过实验验证和实际应用分析,展示了这些技术在显微镜成像、工业检测和医疗影像中的广泛用途及优势。同时探讨了未来发展方向,如智能算法优化、新型光学材料和多模态成像技术,为相关领域的技术创新提供了参考。

y2z3a4b5c的博客 168

java string的底层实现_从底层彻底搞懂String,StringBuilder,StringBuffer的实现

在深入学习字符串类之前, 我们先搞懂JVM是怎样处理新生字符串的. 当你知道字符串的初始化细节后, 再去写 Strings="hello"或 Strings=newString("hello")等代码时, 就能做到心中有数。首先得搞懂字符串常量池的概念。常量池是Java的一项技术, 八种基础数据类型除了float和double都实现了常量池技术. 这项技术从字面上是很好理解的: 把经常用到的数据存...

weixin_39843078的博客 1341

智能工厂评估模型研究

摘要:通过对国内外现有智能制造评估模型的研究,结合重庆制造业行业领跑者与落后者之间差距大的现状,以实现全要素全连接的数字化工厂为出发点,融合工业互联网标识,构建实践可行的智能工厂评估模型推动中小型企业参与智能制造转型,缓和两极分化问题。本评估模型包含工厂体系架构的3个层级,提供用于推动智能制造转型的参考,包括设计、生产、管理的全过程评估。关键词:智能制造评估模型;智能制造转型;全连接工厂;工业互联网标识0 引言受行业需求与技术创新的影响,全球制造业在历经了工业1.0机械化、工业2.0电气化和工业3.0自动

罗伯特技术屋 677

ASPICE标准快速掌握「1.1. 简介」

ASPICE是汽车软件过程改进能力模型(Automotive SPICE,Automotive - Software Process Improvement and Capability dEtermination)的缩写,是一种针对汽车软件开发过程的国际标准。该标准旨在帮助汽车制造商和供应商改进其软件开发过程,以确保其软件产品的质量和安全性。需要做什么为什么要做技术上的依赖性是什么。

雪云飞星的博客 2292

关于java中的String类详解

Java String类在Java 8及之前使用char[]存储字符串,Java 9后改为byte[]+编码标志位,以节省内存空间。String对象存储在堆中,字符串常量池位于堆内存以实现字符串驻留。String类被声明为final以确保安全性、不可变性和性能优化。从char[]改为byte[]的优化主要针对Latin-1字符,可减少内存占用并提升缓存局部性,同时保持原有API不变。这一改动体现了Java团队对性能的精细优化。

weixin_44146398的博客 1186

实战5:农作物产量预测 蔬菜产量预测 lstm 完整代码数据-可直接运行-计算机毕设

实战5:农作物产量预测 蔬菜产量预测 lstm 完整代码数据-可直接运行-计算机毕设

机器学习深度学习业余选手 1257

Java String在JVM内存中的分布

用一段代码来举例:为一个String对象赋值一个字符串字面量“test”:在启动项目时,在JVM类加载过程的“加载”阶段,当扫描到字节码文件中的“test”字面量时,JVM会把这个字面量存放在运行时常量池中。然后在“解析”阶段,JVM会根据运行时常量池中的这个字符串字面量,在堆中创建出对应的String类对象,然后再把它的引用值存入到字符串常量池中。

qq_66627105的博客 1850

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

jvm中string的底层分析

一、String对象一旦被创建就是固定不变的了,对String对象的任何改变都不影响到原对象,相关的任何change操作都会生成新的对象”。因为Sring源码是被final修饰的。   二、字符串常量池 字符串常量池位于方法区,常量池分为两种,一种是静态常量池,一种是运行时常量池       我们知道字符串的分配和其他对象分配一样,是需要消耗高昂的时间和空间的,而且字符串我们使用的非常多。...

qq_39843374的博客 2060

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

String 深入JVM底层解析

一、说清常量池

沮丧的南瓜 440

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

Java中 String str = new String("hello");的底层实现

String str = &quot;hello&quot;; String str2 = new String(&quot;hello&quot;); 上面两中创建 string对象的方式底层实现的不同之处: (1)首先,先确定String常量池的定义: 常量池(constant pool)指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。它包括了关于类、方法、接口等中的常量,也包括字符串常量。 Java为了提高...

黄亚的博客 3397

Java JVM详解

JVM 全称 Java Virtual Machine,也就是我们耳熟能详的 Java 虚拟机。它能识别 .class后缀的文件,并且能够解析它的指令,最终调用操作系统上的函数,完成我们想要的操作。Java 程序不一样,使用 javac 编译成 .class 文件之后,还需要使用 Java 命令去主动执行它,操作系统并不认识这些 .class 文件。所以JVM就是一个翻译。从图中可以看到,有了 JVM 这个抽象层之后,Java 就可以实现跨平台了。

袁震的博客 6657

String不可变特性在JVM中的作用与实现机制详解

摘要:String的不可变性是Java核心特性,指一旦创建其值不可更改。JVM通过final类设计、private final字符数组及字符串常量池机制实现这一特性,确保线程安全、支持哈希缓存优化。JDK9后采用紧凑字符串节省内存。不可变性带来安全性优势和性能提升,但修改操作需创建新对象,频繁拼接应使用StringBuilder。理解此特性对编写高效Java程序及掌握JVM内存管理至关重要。

weixin_47242663的博客 697

JVM详解+String类详解

面试Java时必须要搞懂的基础知识点2.0再谈字符串String二级目录三级目录 再谈字符串String 前面1.0已经说到一点关于String类的知识点,现在再说的更详细一点 二级目录 三级目录

qq_35706097的博客 444

【面试精讲】String是如何实现的?String源码分析

【面试精讲】String是如何实现的?String源码分析

小明的Java问道之路 1万+
上一篇: JDK监控和诊断工具——jstat
下一篇: Linux查看系统信息的命令
编程还未
博客等级 码龄5年 4293粉丝 31原创
评论 8
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

打赏作者

编程还未

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

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

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

打赏作者

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

抵扣说明:

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

余额充值