String&& StringBuffer学习

String&& StringBuffer学习

一、String

1、String认识

String是字符串

String类的特点:字符串是常量,字符串对象一旦被初始化就不会被改变(不可变)

因为 String 对象是不可变的,所以可以共享(共享性)。

扩充:

字符串缓冲区(StringBuffer,StringBuilder)支持可变的字符串。

2、String构造

基本实现:普通字符串的生成

额外功能:可以将字节数组或者字符数组转成字符串,使用String类的构造函数完成

3、String四大功能

按照面向对象的思想对字符串进行功能分类

我们可以思考下,对于字符串:"abcd",它应该具备哪些功能?

1】获取:

首先,对于字符串自身而言,它应该知道自身的数据是什么!

数据量的多少?

在某个位置的字符是什么?

以及某个位置区间的字符串内容是什么?(位置—>内容)

反过来说,我们根据字符或者字符串,也可知道字符或者字符串的出现位置在哪? (内容—>位置)

2】转换:

String的构造器可以实现将字节数组或者字符数组转成字符串,因此,我们可以想到,一定存在这样一个逆过程,将字符串转成字节数组或者字符数组!字符串内容的修改呢?添加字符串的内容?删除字符串两端不必要的空白内容呢?修改字符串的某段内容或者全部内容?

备注:无论对String做了什么修改,String对象的数值不会改变,因为String是常量,一旦创建并初始化后,里面的内容是不会改变的。

注意:所谓修改String操作的时候,实质:此时创建了一个新的String对象,并且给这个对象赋与了修改后的String对象的值,而原有String对象内容不会改变。

3】判断

一开始我们就说了,String对象知道自己里面有什么内容?我们可以知道它是否包含某些内容?它是以什么开头的?以什么结尾的?你都可以进行判断一番!

当两个String对象存在时,以为我们知道它们里面的内容,因此,我们可以判断它们是否相同?

4】比较

我们已经知道我们可以判断两个String对象里面的内容是否相同?除此之外,我们可以按字典顺序比较两个String对象,比较得出谁在前,谁在后?

(1)获取

1.1 获取字符串中字符的个数(长度).

int length();

特殊:记住空格也是有ascii码的,也是一个字符来的,所以当字符串中出现一个或n个空格时,代表中1个或n个字符,也占用着1个或多个的单位长度

eg:

String  a="";
System.out.println(a.length());//0
String  b=" ";
System.out.println(b.length());//1--表示1个空格的位置

1.2 根据位置获取ch(字符)

char charAt(int index);

注意:字符串越界的问题,字符串的索引要在0-s.length()-1的范围内

说明:s表示字符串,字符串的长度为s.length(),所以字符的索引位置范围为:0 ~ s.length()-1

1.3根据位置获取字符串,获取字符串中一部分字符串,也叫子串

String substring(int beginIndex, int endIndex)//包含begin 不包含end 。

String substring(int beginIndex);

1.4根据字符获取在字符串中的第一次出现的位置.

int indexOf(int ch)

int indexOf(int ch,int fromIndex):从指定位置进行ch(字符)的查找第一次出现位置

int indexOf(String str);

int indexOf(String str,int fromIndex);

备注:因为我们知道字符串的索引范围为:0 ~ s.length()-1,所以我们可以根据索引的位置数值是否为-1,来判断该字符或者字符串是否存在。

【使用好该方法,可以解决存在性问题以及字符或者字符串的位置问题】

1.5根据字符串获取在字符串中的最后一次出现的位置.

int lastIndexOf(int ch)

int lastIndexOf(int ch,int fromIndex):从指定位置进行ch(字符)的查找最后一次出现位置

int lastIndexOf(String str);

int lastIndexOf(String str,int fromIndex);

根据字符串获取在字符串中的最后一次出现的位置.

(2)转换

2.1 将字符串变成字符串数组(字符串的切割)

String[] split(String regex):涉及到正则表达式.

备注:分割问题,应该注意两点:

<1>当分割的正则表达式如果是特殊字符 '.',则应该进行转义后进行分割。原因:因为'.'表示任何字符(与行结束符可能匹配也可能不匹配),所以在正则表达式中不具有分割的意义,只有转义成普通的字符'.'才有分割意义

       结论:使用正则表达式中的特殊字符,比如:'.',需要进行转义,编程普通字符后,才可以进行分割,否则输出空白

<2>分割符a如果不存在于被分割的字符串aa中,导致的结果是:aa字符串不会进行以a分隔符为界线进行分割操作,那么所得数组只具有一个元素,即此字符串aa

eg:

    //分割问题
    private static void splitRegex() {
//     String aa="aa,张三,lisi";
//     String[] atr=aa.split(",");
      
       String aa="aa.张三.lisi";
//     String[] atr=aa.split(".");//失败:因为'.'不是普通字符,是正则表达式中的特殊字符,
       //因为'.'表示:任何字符(与行结束符可能匹配也可能不匹配),所以在正则表达式中不具有分割的意义,只有转义成普通的字符'.'才有分割意义
       //因此,使用正则表达式中的特殊字符,比如:'.',需要进行转义,编程普通字符后,才可以进行分割,否则输出空白
      
       String[]  atr=aa.split("wo");//由于分割符"wo"不存在于字符串aa中,所以aa不会进行分割,那么所得数组只具有一个元素,即此字符串
//     String[] atr=aa.split("\\.");
       for (int  i = 0; i < atr.length; i++) {
           System.out.println(atr[i]);
       }
    }

 

2.2 将字符串变成字符数组。

char[] toCharArray();

2.3 将字符串变成字节数组。

byte[] getBytes();

2.4 将字符串中的字母转成大小写。

String toUpperCase():大写

String toLowerCase():小写

2.5  将字符串中的内容进行替换

String replace(char oldch,char newch);

String replace(String s1,String s2);

2.6 将字符串两端的空格去除。

String trim();

2.7 将字符串进行连接。

String concat(string);

(3)判断

3.1 两个字符串内容是否相同啊?

boolean equals(Object obj);

boolean equalsIgnoreCase(string str);忽略大写比较字符串内容。

3.2 字符串中是否包含指定字符串?

boolean contains(string str);

3.3 字符串是否以指定字符串开头。是否以指定字符串结尾。

boolean startsWith(string);

boolean endsWith(string);

备注:应用场景,主要对于文件的名字或者文件的后缀名的判断,会比较长使用。

eg:

//开头内容,结尾内容判断
String  str="ArrayDemo.java";
System.out.println("开头:"+str.startsWith("Array"));
System.out.println("结尾:"+str.endsWith(".java"));

(4)比较

4.1按字典顺序比较两个字符串

compareTo(StringanotherString)

eg:

//比较问题
private static void compare() {
 
//ascii表中,ascii值大的字典顺序为大
//在一系列的字符串比较中,当两者中(a方,b方)的第i位,a的第i位的字典顺序大于
//b的第i位的字典顺序,即表示a大,反之则小,如果比较到最后仍然相同,表示相等
//【一旦出现大,或者小的情况,即比较停止】
 
//     如果参数字符串等于此字符串,则返回值 0;
//     如果此字符串按字典顺序小于字符串参数,则返回一个小于 0的值;
//     如果此字符串按字典顺序大于字符串参数,则返回一个大于 0的值。
 
String  a="ada";
String  b="abk";
System.out.println(a.compareTo(b));//2
//解析:a的码值:97 b的码值:98   d的码值:100
//因为"ada"与"abk"的第一字符都是'a',即:此时顺序相同,所以两者继续比较,
//然后,比较d'和'b',因为'd'比'b'码值大2,即'd'的字典顺序大于'b'的字典顺序,此时,
//无需继续比较了,可以终止比较了。得出结论:"ada"比"abk"的字典顺序大
//【码值大的,字典顺序大】
}

4、Stringintern的方法

(1)说明

   返回字符串对象的规范化表示形式。

   一个初始为空的字符串池,它由类String 私有地维护

   当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(用 equals(Object) 方法确定),则返回池中的字符串。

   否则,将此 String 对象添加到池中,并返回此 String 对象的引用。

(2)案例与分析

备注:案例与分析来源:

文章:Stringintern的方法

地址:

http://www.cnblogs.com/wanlipeng/archive/2010/10/21/1857513.html

案例

String  a = new String("ab");
String  b = new String("ab");
String  c = "ab";
String  d = "a" + "b";
String  e = "b";
String  f = "a" + e;
System.out.println(b.intern()== a);
System.out.println(b.intern()== c);
System.out.println(b.intern()== d);
System.out.println(b.intern()== f);
System.out.println(b.intern()== a.intern());

运行结果:

false  true   true  false  true

分析:

由运行结果可以看出来,

b.intern() == ab.intern()== c可知,采用new创建的字符串对象不进入字符串池,并且通过b.intern()== db.intern()== f可知,字符串相加的时候,都是静态字符串的结果会添加到字符串池,如果其中含有变量(如f中的e)则不会进入字符串池中。

但是字符串一旦进入字符串池中,就会先查找池中有无此对象。如果有此对象,则让对象引用指向此对象。如果无此对象,则先创建此对象,再让对象引用指向此对象。

二、String对象创建的两类方法总结

1、创建实例

package  stringDemo;
//字符串创建的两类方式使用
public class FirstString {
    public static void main(String[] args) {
       StringDemo1();
       StringDemo2();
    }
 
    /**
     * 字符串定义的第一种方式,并明确字符串常量池的特点:
     * 常量池中没有就建立对象,池中有,直接拿来用(对象的引用)
     */
    public static void StringDemo1() {
       String a="abc";//"abc"存储在字符串常量池中
       //由于常量池中没有对象“abc”,此时需要创建一个“abc”对象
       a="nba";//重新创建了一个“nba”对象,并将a指向这个对象
       String a2="abc";//由于常量池中有对象“abc”,所以直接拿来用即可,不用再创建
       System.out.println("a:"+a);
       System.out.println(a==a2);//true因为,a和a2都指向常量池中的同一个对象,所以地址相同
    }
 
    public static void StringDemo2() {
       String a1="abc";//创建一个字符串对象在常量池中
       String a2=new String("abc");//创建两个对象,一个在字符串常量池中,一个字符串对象在堆内存(堆区)中
       System.out.println(a1==a2);//false
       System.out.println(a1.equals(a2));//true
       //String类中的equals方法复写了Object中的equals方法,建立了String类自己判断字符串对象
       //是否相同的依据。实质:比较字符串的内容是否相同
       System.out.println("a1:"+a1);
       System.out.println("a2:"+a2);
    }
 
}

2、总结

备注:String对象创建的两类方法总结来源:

文章:深入理解Java中的String

地址:http://www.cnblogs.com/xiaoxi/p/6036701.html

创建字符串的方式归纳起来有两类:

(1)使用""引号创建字符串;

(2)使用new关键字创建字符串。

结合上面例子,总结如下:

(1)单独使用""引号创建的字符串都是常量,编译期就已经确定存储到String Pool

(2)使用new String("")创建的对象会存储到heap(堆)中,是运行期新创建的;

new创建字符串时首先查看池中是否有相同值的字符串,如果有,则拷贝一份到堆中,然后返回堆中的地址;如果池中没有,则在堆中创建一份,然后返回堆中的地址(注意,此时不需要从堆中复制到池中,否则,将使得堆中的字符串永远是池中的子集,导致浪费池的空间)!

(3)使用只包含常量的字符串连接符如"aa" + "aa"创建的也是常量,编译期就能确定,已经确定存储到String Pool中;

(4)使用包含变量的字符串连接符如"aa" + s1创建的对象是运行期才创建的,存储在heap中;

三、String使用的四个案例

1、字符串数组排序(compareTo妙用)

(1)实现代码

package  stringImportantDemo;
/*
 * 案例:
 * 给定一个字符串数组。按照字典顺序进行从小到大的排序。
 * {"nba","abc","cba","zz","qq","haha"}
 * 1.利用CompareTo方法进行字典顺序比较
 * 2.输出数组中的内容
 */
public class StringArraySort {
    public static void main(String[] args) {
 
       String[] str={"nba","abc","cba","zz","qq","haha"};
       //排序前
       System.out.println("排序前的内容:");
       print(str);//输出数组的内容
       //[nba,abc,cba,zz,qq,haha]
       sort(str);//字符串数组排序
      
       //排序后
       System.out.println("排序后数组的内容:");
       print(str);//输出数组的内容
       //[abc,cba,haha,nba,qq,zz]
      
    }
 
    /*
     * 字符串数组排序
     */
    public static void sort(String[] str) {
      
       for(int  i=0;i<str.length-1;i++){//比较str.length-1次
           for(int  j=i+1;j<str.length;j++){//比较的位置: i 与 i+1(j)
               if((str[i].compareTo(str[j]))>0){
                  //交换位置
//                changePosition(str[i],str[j]);//错误的做法,以此为鉴
                  changePosition(str,i,j);//交换数组
              }
           }
       }
    }
//  //交换数组元素位置   失败!传递参数失败!!!
//  private static void changePosition(String a,String b) {
//
//     String temp=a;
//     a=b;
//     b=temp;
//  }
 
    //对象数组交换位置
    private static void changePosition(String[]str, inti, intj) {
 
       String temp=str[i];
       str[i]=str[j];
       str[j]=temp;
    }
 
    //输出数组的内容
    public static void print(String[] str) {
       System.out.print("[");
       for(inti=0;i<str.length;i++){
           if(i!=str.length-1){
              System.out.print(str[i]+",");
           }
           else{
              System.out.print(str[i]);
           }
       }
       System.out.println("]");
    }
 
}

(2)分析

【1】字符串数组排序思路

冒泡排序的思路,进行排序,然后利用CompareTo方法进行比较字典顺序大小

1)第i次循环,str[i]>str[i+1]时,进行交换位置

2)第i次循环,把最大的数,放在数组前面第i-1个位置

【2】交换数据失败分析(传递参数使用不熟,不了解)

因为传递过去的都是String类型的参数,而String类型的对象属于状态不可更改的对象

如果一个对象可以在不改变其在内存中的存储位置的情况下而改变其本身的状态,即是状态可转换对象;否则就是状态不可转换对象

状态可转换对象:如StringBuffer类型的对象中存在append方法来改变对象的状态,

所以StringBuffer类型的对象时状态可转换对象

状态不可转换对象:如String类型的对象中不存在任何方法来改变对象的状态,

要改变String类型的对象的状态必须生成新的String对象,所以String类型的对象是状态不可状态对象

详细分析看:

csdn博客:Java参数传递

网址:http://blog.csdn.net/a15920804969/article/details/78370351

2、子串出现次数(indexOf妙用)

(1)实现代码

package  stringImportantDemo;
/*
 * 案例:
 * 一个子串在整串中出现的次数
 * "nbaernbatynbauinbaopnba"中"nba"出现次数
*/
public class SubStringCount {
   
    public static void main(String[] args) {
       String all="nbaernbatynbauinbaopnba";//整串
       String sub="nba";//子串
       int count=subCount(all,sub);//子串次数
       System.out.println("子串出现次数:"+count);
    }
 
    //子串出现次数
    public static int subCount(String s1, String s2) {
       int count=0;//子串出现次数
      
       String allStr;//整串
       String subStr;//子串
       //确保s1为整串,s2为子串
       //实质:s1的字符串长度大于或者等于s2的字符串车行度
//     (s1.length()>s2.length())?(allStr=s1):(subStr=s2);//使用错误
       allStr=(s1.length()>=s2.length())?s1:s2;//整串
       subStr=(s1.length()>=s2.length())?s2:s1;//子串
      
       int index=0;//索引位置
       /*
        * 解决方案:    
        * 方案1.indexOf(s)零角标开始查找,然后整串转换为部分串
        * 方案2.indexOf(s,fromIndex)从指定坐标开始查找,只要记录下出现索引位置,下次就i+sub.length()索引处开始寻找
        * 方案3.分割"nba"----split("nba"),分割成的数组长度,减去1,得到的就是"nba"的出现次数
        */
       //这里采用的是方案1的解决思路
       while((index=allStr.indexOf(subStr))!=-1){
           count++;//记录次数
          
           //从剩余部分进行查找
          
           //整串转换为部分串,即:变为不包括第一次子串的剩余串
           allStr=allStr.substring(index+subStr.length());//i+sub.length()
       }
       return  count;
    }
}

(2)分析

图解分析:


思路:

indexOf的妙用

假设字串为变量 sub,整串为变量 all

1.利用indexOf判断字符串中第一次出现的索引位置,而且可以根据是否返回-1,判断子串是否存在;

如果返回-1,说明子串在整串中不存在;

如果不是返回-1,说明子串在整串中存在,并且根据值,可以得到字串在整串中的第一次出现位置为i;

2、当不返回-1时,进行第二次indexOf判断,不过,这时候不是从整串的0索引处开始,而是从i+sub.length()

备注:

i+sub.length()说明:因为是要从整串all中寻找子串sub的出现次数。所以,当找出第一个子串位置后,

下一次寻找,就从不包括第一次子串的索引位置开始寻找,即:i+sub.length();

因为子串的长度位sub.length(),而索引位置为i,

所以,i的位置向右移动子串长度,就是下一次开始寻找的位置。

3、最大相同子串

(1)实现代码

package  stringImportantDemo;
/*
 * 案例:
 * 两个字符串中最大相同的子串
*/
public class MaxEqualSubString {
    public static void main(String[] args) {
       String s1="mkopmorningcdkoe";
       String s2="cdefmorningwokasd";
       String equalStr=findMax(s1,s2);
       System.out.println("最大相同的子串:"+equalStr);
       //最大相同的子串:morning
    }
 
    public static String findMax(String s1, Strings2) {
   
       String big=(s1.length()>=s2.length())?s1:s2;//大串
       String small=(s1.length()>=s2.length())?s2:s1;//小串
       //目的:最大相同子串subString(begin,end)
       //begin:0开始 子串开头
       //end:字符串的长度结束 子串结尾
       for(int  i=0;i<small.length();i++){//假设循环次数:小串small的长度
           //每次外层循环结束后,内层循环什么东西变了呢?好好思考!!!
           //就是小串small的长度减少了1
           //for(begin=0,end=small.length()-i;end<=big.length();begin++,end++){//错误
           //注意:终止条件end<small.length() 是在小串范围的end,不是大串范围的end(大串范围会溢出)!!
           for(int  begin=0,end=small.length()-i;end<=small.length();begin++,end++){
              String sub=small.substring(begin,end);
              if(big.contains(sub)){
                  return  sub;
              }
           }
       }
       return null;
    }
}

(2)分析


思路:

变量说明:1个字符串为s1,一个字符串为s2,

1.假设一个字符串为大串big,一个字符串为小串small(如果两个字符串长度相等,就选择big为大串,small为小串)

2.我们采用逆向思维方式去思考并解决这个问题!

3.假设小串s2是s1和s2的最大相同子串,成立,最大子串则为s2;不成立,则最大子串不是s2;

4.当上述假设不成立时;我们把子串的长度减少1个单位长度,并把s2.length()-1长度的一些字符串,

假设为是s1和s2的相同字符串,如果假设成立,则找到了;

如果没找到,则s2.length()-1长度的一些字符串都不是最大相同子串;

5.以此类推,重复4的假设,直到找到最大相同子串为止。

 

4、去掉两端空白(模拟trim方法)

(1)实现代码

package  stringImportantDemo;
/*
 * 案例:
 * 模拟一个trim功能一致的方法:
 * 去掉两端空白(模拟trim方法)
 * 原理:截取字符串,把两端的空白字符去掉,直接取中间的字符,达到要求
*/
public class DeleteTwoBlank {
    public static void main(String[] args) {
 
       String goal="      abdeoasdkf          ";
       System.out.println("处理前字符串goal:"+goal);
       String newGoal=myTrim(goal);//模拟一个trim功能一致的方法
       System.out.println("处理后字符串goal:"+newGoal);
    }
 
    //模拟一个trim功能一致的方法
    private static String myTrim(String s) {
       int i=0;//正向查找记录
       int j=s.length()-1;//反向查找记录
 
       //1、正向查找
       //while(s.indexOf(" ",i)!=-1){ 失败,思路有问题,想错了
      
//     while(s.charAt(i)=='0'){//表示空字符(Null)     //失败!
       while(i<=j&&s.charAt(i)==' '){ //空白字符           //成功!
//     while(s.charAt(i)==32){//空白字符ascii为32    //成功!
           i++;
       }
       //2、反向查找
//     while(s.lastIndexOf(" ",j)!=-1){
       while(i<=j&&s.charAt(j)==32){
           j--;
       }
       System.out.println("i:"+i+"   j:"+j);
       //3、得出结果
       s=s.substring(i,j+1);
       return s;
    }
}

(2)分析

原理:截取字符串,把两端的空白字符去掉,直接去中间的字符,达到要求

分析:

1、使用charAt方法,把字符串的开头部分的空白字符串区域找出来

2、定义两个变量。

一个变量作为从头开始判断字符串空格的角标。不断++。【正向查找】

一个变量作为从尾开始判断字符串空格的角标。不断--。【反向查找】

3、判断到不是空格为止,取头尾之间的字符串即可。【得出结果】

使用subString方法,subString(i,j+1)即为要截取的字符串

四、StringBuffer

1、StringBuffer认识(底层数组)

 StringBuffer:就是字符串缓冲区。

 用于存储数据的容器。

 特点:

 1,长度的可变的

 2,可以存储不同类型数据。

 3,最终要转成字符串进行使用

 4,可以对字符串进行修改

2、StringBuffer四大功能【增删改查】

既然是一个容器对象。应该具备什么功能呢?

 增删改查 C(create)U(update)R(read)D(delete)

 1、添加:(一个元素和一些元素,添加的位置?)

StringBuffer append(data); append 方法始终将这些字符添加到容器的末端

StringBuffer insert(index,data); insert 方法则在指定的点添加字符

 2、删除:(一个元素和一些元素)

StringBuffer delete(start,end):包含头,不包含尾。(删除一些元素)

StringBuffer deleteCharAt(int index):删除指定位置的元素(删除一个元素)

特殊: sb.delete(0, sb.length());//清空缓冲区

 3、查找:(位置—>元素;元素—>位置)

             char charAt(index);某个位置的元素是什么

             int indexOf(string);  返回指定子字符串在此字符串中第一次出现处的索引。(可以根据位置索引是否是-1,判断某段内容是否存在)

             int lastIndexOf(string); 返回指定子字符串在此字符串中最右边(最后一次)出现处的索引

 4,修改:

             StringBufferreplace(start,end,string);设置一段字符(代替原来位置上的内容)

             void setCharAt(index,char);设置一个字符

 

3、StringBuffer与StringBuilder比较

jdk1.5以后出现了功能和StringBuffer一模一样的对象。就StringBuilder

 不同的是:

 StringBuffer是线程同步的。通常用于多线程

 StringBuilder是线程不同步的。通常用于单线程。 它的出现提高效率。

如果可能,建议优先采用该类,因为在大多数实现中,它比 StringBuffer 要快。

5、将一个int数组变成字符串

package stringBuffer;
/*
 * 将一个int数组变成字符串
 * 1、遍历int数组,然后把数组中的元素拼接成字符串
 * 2、使用StringBuilder(或者StringBuffer)进行拼接比String进行拼接效率高很多
 */
public class IntArrayToString {
 
    public static void main(String[] args) {
 
       int[] in={23,34,22,45,54,87};
       String str=IntArrayToString.change(in);//将一个int数组变成字符串
       System.out.println("将一个int数组变成字符串:"+str);
    }
 
    private static String change(int[]in) {
 
       StringBuilder sb=newStringBuilder();
       sb.append("[");
       for(int i=0;i<in.length;i++){
           if(i!=in.length-1){
              sb.append(in[i]+",");
           }else{
              sb.append(in[i]);
           }
       }
       sb.append("]");
      
       return  sb.toString();//将sb由StringBuiler转变为String
    }
 
}
 

五、可变长度数组分析


当s1数组的容量不够时,就进行数组的扩容。即:数组的重新创建,且数组变为原来的两倍(或许不一定是两倍),然后把原来数组s1的数组复制到s1Copy中,如果此时s1Copy的容量仍然不够,进一步进行扩容操作,知道可以完全容纳元素为止

六、jdk升级思考

(1)简化书写

(2)提高效率

(3)增加安全性

七、String学习参考文章推荐

如果想加深对String的认识,可以看看以下的三个参考文章,可以加深你对String的理解

(1)文章1

文章:JavaString两种不同创建方式的区别及intern的用法

地址:http://www.cnblogs.com/YLsY/p/5729113.html

(2)文章2

文章:(原创)深入研究java对String字符串对象的创建以及管理

地址:http://bbs.csdn.net/topics/270042906

(3)文章3

文章:深入理解Java中的String

地址:http://www.cnblogs.com/xiaoxi/p/6036701.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值