【韩老师零基础30天学会Java 08】异常体系结构 包装类 String详细 StringBuffer详细 StringBuilder,实现千分位展示

异常

异常体系图

java.lang.ArithmeticException: / by zero

System.out.println("出现异常的原因=" + e.getMessage());//输出异常信息
出现异常的原因=/ by zero

执行过程中所发生的异常事件可分为两大类

  1. Error(错误):Java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源耗尽等严重情况。比如: StackOverflowError[栈溢出]和OOM(out of
    memory).Error是严重错误,程序会崩溃。
  2. Exception:其它因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。例如空指针访问,试图读取不存在的文件,网络连接中断等等,
  • Exception分为两大类:运行时异常[程序运行时,发生的异常]和
  • 编译时异常[编程时,编译器检查出的异常]。

Serializable
0bject

  • Throwable

    • Exception

      • RuntimeException

      • NullPointerException

      • ClassCast Exception

      • ArrayIndex0ut0fBounds Exception

      • Arithmetic Exception

      • NumberFormat Exception

      • lllegalArgumentException

      • illegalstateException

      • NoSuchElementException

      • CloneNotsupportedException

      • ClassNotFoundException

      • IOEXception

        • EOFException 文件结束符(end of file)
        • FileNotFoundException
        • MalformedURLException
        • UnknownHostException
    • Error

    • OutOfMemoryError

    • StackOverflowError

蓝色:非受检(unchecked)异常(运行异常) RuntimeException
红色:受检(checked)异常(编译异常) IOException 和 ClassNotFound

异常测试

try {
    FileInputStream fis;
    fis = new FileInputStream("d:\\aa.jpg");
    int len;
    while ((len = fis.read()) != -1) {
        System.out.println(len);
    }
    fis.close();
} catch (IOException e) {
    e.printStackTrace();
}
  1. ClassCastException类型转换异常
    当试图将对象强制转换为不是实例的子类时,
    抛出该异常。例如,以下代码
    将生成一个ClassCastException
public class ClassCastException_ {
    public static void main(String[] args) {
        A b = new B(); //向上转型
        B b2 = (B)b;//向下转型,这里是OK
        C c2 = (C)b;//这里抛出ClassCastException
    }
}
class A {}
class B extends A {}
class C extends A {}
  1. NumberFormatException数字格式不正确异常
    当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常=>使用异常我们可以确保输入是满足条件数字.
String name="韩顺平教育";
int num = Integer.parselnt(name);

编译异常

编译异常是指在编译期间,就必须处理的异常否则代码不能通过编译。

常见的编译异常

√SQLException/操作数据库时,查询表可能发生异常

IOException//操作文件时,发生的异常

√FileNotFoundException//当操作一个不存在的文件时,发生异常

√ClassNotFoundException//加载类,而该类不存在时,异常

√EOFException/操作文件,到文件末尾,发生异常

√IllegalArguementException//参数异常

异常处理

·异常处理的方式
·1) try-catch-finally
程序员在代码中捕获发生的异常,自行处理
2) throws
将发生的异常抛出,交给调用者(方法)来处理,最顶级的处理者就是JVM

2.如果程序员,没有显示是处理异常,默认throws

5)可以进行 try-finally 配合使用,这种用法相当于没有捕获异常,因此程序会直接崩掉。


        /*
        可以进行 try-finally 配合使用, 这种用法相当于没有捕获异常,
        因此程序会直接崩掉/退出。应用场景,就是执行一段代码,不管是否发生异常,
        都必须执行某个业务逻辑
         */
        try{
            int n1 = 10;
            int n2 = 0;
            System.out.println(n1 / n2);
        }finally {
            System.out.println("执行了finally..");
        }
        System.out.println("程序继续执行..");//异常后,程序 不会继续执行。

例题输入一个整数

public class TryCatchExercise04 {
    public static void main(String[] args) {

        //如果用户输入的不是一个整数,就提示他反复输入,直到输入一个整数为止
        //思路
        //1. 创建Scanner对象
        //2. 使用无限循环,去接收一个输入
        //3. 然后将该输入的值,转成一个int
        //4. 如果在转换时,抛出异常,说明输入的内容不是一个可以转成int的内容
        //5. 如果没有抛出异常,则break 该循环
        Scanner scanner = new Scanner(System.in);
        int num = 0;
        String inputStr = "";
        while (true) {

            System.out.println("请输入一个整数:"); //
            inputStr = scanner.next();
            try {
                num = Integer.parseInt(inputStr); //这里是可能抛出异常
                break;
            } catch (NumberFormatException e) {
                System.out.println("你输入的不是一个整数:");
            }
        }

        System.out.println("你输入的值是=" + num);
    }
}
class Father { //父类
    public void method() throws RuntimeException {
    }
}

class Son extends Father {//子类
    //3. 子类重写父类的方法时,对抛出异常的规定:子类重写的方法,
    //   所抛出的异常类型要么和父类抛出的异常一致,要么为父类抛出的异常类型的子类型
    //4. 在throws 过程中,如果有方法 try-catch , 就相当于处理异常,就可以不必throws
    @Override
    public void method() throws ArithmeticException {
    }
}//运行时 异常,无需处理

自定义异常

2)如果继承Exception,属于编译异常
3)如果继承RuntimeException,属于运行有常(一般来说,继承RuntimeException)

public class CustomException {
    public static void main(String[] args) /*throws AgeException*/ {

        int age = 180;
        //要求范围在 18 – 120 之间,否则抛出一个自定义异常
        if(!(age >= 18 && age <= 120)) {
            //这里我们可以通过构造器,设置信息
            throw new AgeException("年龄需要在 18~120之间");
        }
        System.out.println("你的年龄范围正确.");
    }
}
//自定义一个异常
//老韩解读
//1. 一般情况下,我们自定义异常是继承 RuntimeException
//2. 即把自定义异常做成 运行时异常,好处时,我们可以使用默认的处理机制
//3. 即比较方便
class AgeException extends RuntimeException {
    public AgeException(String message) {//构造器
        super(message);
    }
}

包装类

基本数据类型
包装类

  • boolean

  • Boolean

  • char

    • Character
  • byte 从这里开始,父类都是 Number

    • Byte
  • short

    • Short
  • int

    • lnteger
  • long

    • Long
  • float

    • Float
  • double

    • Double

3)自动装箱底层调用的是valueOf方法,比如Integer.valueOf)

Object obj1 = true? new Integer(1) : new Double(2.0)

答案为:1.0,三元运算符,会提升优先级。

常用方法

Integer.MAX_VALUE);//返回最大值

Character.isDigit('a');//判断是不是数字
Character.isLetter('a'))://判断是不是字母

Character.isUpperCase('a'));//判断是不是大写
Character.isLowerCase('a'))://判断是不是小写

Character.isWhitespace('a'))://判断是不是空格

Character.toUpperCase('a'));//转成大写

ValueOf

    public static Integer valueOf(int i) {
        //-128 to 127
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }
        //示例六
        Integer i11=127;
        int i12=127;
		//只有有基本数据类型,判断的是
		//值是否相同
        System.out.println(i11==i12); //T
		//示例七
        Integer i13=128;
        int i14=128;
        System.out.println(i13==i14);//T

String

简介

String01.java

  1. String对象用于保存字符串,也就是一组字符序列
    2)字符串常量对象是用双引号括起的字符序列。例如:“你好”、“12.97”."boy"等

3)字符串的字符使用Unicode字符编码,一个字符(不区分字母还是汉字)占两个字节。

4)String类较常用构造器(其它看手册);
String s1 = new String0;1
String s2 = new String(String original);String s3 = new String(charl a);
String s4 = new String(charl a,int startlndex,int count)

String(byte[], Charset)

String(byte[]], int, int)

String(byte[])

String(StringBuffer)

String实现了Serializable,说明String可以串行化

可以网络传输

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];//底层是char数组
    //一定要注意:value是一个final类型,不可以修改。是这个 char数组的地址 不可修改了。数组里的内容,还是可以改的
   }
创建

方式一:直接赋值 String s = “hsp”;
方式二:调用构造器 String s2 = new String(“hsp”);
1.方式一:先从常量池查看是否有"hsp"数据空间,如果有,直接指向;如果
没有则重新创建,然后指向。S最终指向的是常量池的空间地址
2.方式二:先在堆中创建空间,里面维护了value属性,指向常量池的hsp空间。
如果常量池没有"hsp",重新创建,如果有,直接通过value指向。最终指向的是堆中的空间地址。

例题
String a = "abc";String b ="abc";
System.out.println(a.equals(b)):/T
System.out.println(a==b);//T

当调用intern方法时,如果池已经包含与equals(Object)方法确定的相当于此string对象的字符串,则返回来自池的字符串。否则,此string对象将添加到池中,并返回对此string对象的引用。

b.intern()方法最终返回的是常量池的地址(对象)

        String a = "hsp"; //a 指向 常量池的 “hsp”
        String b =new String("hsp");//b 指向堆中对象
        System.out.println(a.equals(b)); //T
        System.out.println(a==b); //F
        //b.intern() 方法返回常量池地址
        System.out.println(a==b.intern()); //T //intern方法自己先查看API
        System.out.println(b==b.intern()); //F

String s1 = "hspedu";
String s2 = "java";
String s4 = "java";
String s3 = new String("java");

System.out.println(s2 == s3);//false
System.out.println(s2 == s4);//true
System.out.println(s2.equals(s3));//true
System.out.println(s1 == s2);//false
public class StringExercise05 {
    public static void main(String[] args) {
        Person p1 = new Person();
        p1.name = "hspedu";
        Person p2 = new Person();
        p2.name = "hspedu";

        System.out.println(p1.name.equals(p2.name));//比较内容: True
        System.out.println(p1.name == p2.name);  //T
        System.out.println(p1.name == "hspedu");   //T

        String s1 = new String("bcde");
        String s2 = new String("bcde");
        System.out.println(s1==s2); //False
    }
}
class Person {
    public String name;
}
创建对象数量
String s1 = "hello";
s1="haha"; //创建2个对象
String a = "hello" +"abc"; //底层优化,创建一个对象。 helloabc
        String a = "hello"; //创建 a对象
        String b = "abc";//创建 b对象
        //老韩解读
        //1. 先 创建一个 StringBuilder sb = StringBuilder()
        //2. 执行  sb.append("hello");
        //3. sb.append("abc");
        //4. String c= sb.toString()
        //最后其实是 c 指向堆中的对象(String) value[] -> 池中 "helloabc"
        String c = a + b;

        String d = "helloabc";
        System.out.println(c == d);//真还是假? 是false。因为C是 new 的,断点代码,可以看出。
        //断点的代码 如下
        /*public String toString() {
            // Create a copy, don't share the array
            return new String(value, 0, count);
        }*/

        String e = "hello" + "abc";//直接看池, e指向常量池
        System.out.println(d == e);//真还是假? 是true。两个都是常量池。

老韩小结:底层是

StringBuilder sb = new StringBuilder();

sb.append(a);

sb是在堆中,并且append是在原来字符串的基础上追加的.
重要规则,String c1 = “ab” + “cd”;常量相加,看的是记。String c1=a+b;变量相加,是在堆中

class Test1 {
    String str = new String("hsp");
    final char[] ch = {'j', 'a', 'v', 'a'};

    public void change(String str, char ch[]) {
        //常量池中 新生成了 java 串
        str = "java";
        ch[0] = 'h';
    }

    public static void main(String[] args) {
        Test1 ex = new Test1();
        ex.change(ex.str, ex.ch);
        //hsp and hava 
        System.out.print(ex.str + " and ");
        System.out.println(ex.ch);
    }
}
常用方法

equals

equalslgnoreCase

length

indexOf

lastlndexOf

substring

trim

charAt:获取某索引处的字符

toUpperCase

toLowerCase

concat

compareToto

CharArrayformat

常用方法1

indexOf //获取字符在字符串中第1次出现的索引,索引从0开始,如果找不到,返回-1

lastIndexOf//获取字符在字符串中最后1次出现的索引,索引从0开始,如找不到,返回-1

substring//截取指定范围的子串

trim//去前后空格

charAt:获取某索引处的字符,注意不能使用Str[index]这种方式.

  • str.charAt(0);
// 4.indexOf 获取字符在字符串对象中第一次出现的索引,索引从0开始,如果找不到,返回-1
        String s1 = "wer@terwe@g";
        int index = s1.indexOf('@');
        System.out.println(index);// 3
        System.out.println("weIndex=" + s1.indexOf("we"));//0
        // 5.lastIndexOf 获取字符在字符串中最后一次出现的索引,索引从0开始,如果找不到,返回-1
        s1 = "wer@terwe@g@";
        index = s1.lastIndexOf('@');
        System.out.println(index);//11
        System.out.println("ter的位置=" + s1.lastIndexOf("ter"));//4
        // 6.substring 截取指定范围的子串
        String name = "hello,张三";
        //下面name.substring(6) 从索引6开始截取后面所有的内容
        System.out.println(name.substring(6));//截取后面的字符

        //name.substring(0,5)表示从索引0开始截取,截取到索引 5-1=4位置。就是截取5个数,从0开始。不包括 结束索引。

        System.out.println(name.substring(2,5));//llo 。截取 2 3 4 三个字符。截取3个数,从2开始。
常用方法2

toUpperCase

toLowerCaseconcat

replace替换字符串中的字符

split 分割字符串,对于某些分割字符,我们需要转义比如|I等

案例: String poem=“锄禾日当午,汗滴禾下土,谁知盘中餐,粒粒皆辛苦”;和文件路径.

compareTo 1/比较两个字符串的大小

toCharArray 1/转换成字符数组

format //格式字符串,%s字符串%c字符%d 整型%.2f 浮点型案例,将一个人的信息格式化输出.

// 1.toUpperCase转换成大写
        String s = "heLLo";
        System.out.println(s.toUpperCase());//HELLO
        // 2.toLowerCase
        System.out.println(s.toLowerCase());//hello
        // 3.concat拼接字符串
        String s1 = "宝玉";
        s1 = s1.concat("林黛玉").concat("薛宝钗").concat("together");
        System.out.println(s1);//宝玉林黛玉薛宝钗together
        // 4.replace 替换字符串中的字符
        s1 = "宝玉 and 林黛玉 林黛玉 林黛玉";
        //在s1中,将 所有的 林黛玉 替换成薛宝钗
        // 老韩解读: s1.replace() 方法执行后,返回的结果才是替换过的.
        // 注意对 s1没有任何影响
        String s11 = s1.replace("宝玉", "jack");
        System.out.println(s1);//宝玉 and 林黛玉 林黛玉 林黛玉
        System.out.println(s11);//jack and 林黛玉 林黛玉 林黛玉
        // 5.split 分割字符串, 对于某些分割字符,我们需要 转义比如 | \\等
        String poem = "锄禾日当午,汗滴禾下土,谁知盘中餐,粒粒皆辛苦";
        //老韩解读:
        // 1. 以 , 为标准对 poem 进行分割 , 返回一个数组
        // 2. 在对字符串进行分割时,如果有特殊字符,需要加入 转义符 \
        String[] split = poem.split(",");
        poem = "E:\\aaa\\bbb"; //真实为 E:\aaa\bbb
        split = poem.split("\\\\");//真实为:\\,但是写不了。\\\ 也不行,只有\\\\
        System.out.println("==分割后内容===");
        for (int i = 0; i < split.length; i++) {
            System.out.println(split[i]);
        }


        // 6.toCharArray 转换成字符数组
        s = "happy";
        char[] chs = s.toCharArray();
        for (int i = 0; i < chs.length; i++) {
            System.out.println(chs[i]);
        }
        // 7.compareTo 比较两个字符串的大小,如果前者大,
        // 则返回正数,后者大,则返回负数,如果相等,返回0
        // 老韩解读
        // (1) 如果长度相同,并且每个字符也相同,就返回 0
        // (2) 如果长度相同或者不相同,但是在进行比较时,可以区分大小
        //     就返回 if (c1 != c2) {
        //                return c1 - c2;
        //            }
        // (3) 如果前面的部分都相同,就返回 str1.len - str2.len
        String a = "jcck";// len = 3
        String b = "jack";// len = 4
        System.out.println(a.compareTo(b)); // 返回值是 'c' - 'a' = 2的值

// 8.format 格式字符串
        /* 占位符有:
         * %s 字符串 %c 字符 %d 整型 %.2f 浮点型
         *
         */
        String name = "john";
        int age = 10;
        double score = 56.857;
        char gender = '男';
        //将所有的信息都拼接在一个字符串.
        String info =
                "我的姓名是" + name + "年龄是" + age + ",成绩是" + score + "性别是" + gender + "。希望大家喜欢我!";

        System.out.println(info);

        //老韩解读
        //1. %s , %d , %.2f %c 称为占位符
        //2. 这些占位符由后面变量来替换
        //3. %s 表示后面由 字符串来替换
        //4. %d 是整数来替换
        //5. %.2f 表示使用小数来替换,替换后,只会保留小数点两位, 并且进行四舍五入的处理
        //6. %c 使用char 类型来替换
        String formatStr = "我的姓名是%s 年龄是%d,成绩是%.2f 性别是%c.希望大家喜欢我!";

        String info2 = String.format(formatStr, name, age, score, gender);

        System.out.println("info2=" + info2);
    public int compareTo(String anotherString) {
        int len1 = value.length;
        int len2 = anotherString.value.length;
        int lim = Math.min(len1, len2);
        char v1[] = value;
        char v2[] = anotherString.value;

        int k = 0;
        while (k < lim) {
            char c1 = v1[k];
            char c2 = v2[k];
            if (c1 != c2) {
                return c1 - c2;
            }
            k++;
        }
        return len1 - len2;
    }


        String a = "jcck";// len = 4
        String b = "jack";// len = 4,长度相同时,某个不相同的字符 相减: c-a = 2
		//长度不同的时候,a的长度 - b的长度。

StringBuffer

        //1. StringBuffer 的直接父类 是 AbstractStringBuilder
        //2. StringBuffer 实现了 Serializable, 即StringBuffer的对象可以串行化
        //3. 在父类中  AbstractStringBuilder 有属性 char[] value,不是final
        //   该 value 数组存放 字符串内容,引出存放在堆中的
        //4. StringBuffer 是一个 final类,不能被继承
        //5. 因为StringBuffer 字符内容是存在 char[] value, 所有在变化(增加/删除)
        //   不用每次都更换地址(即不是每次创建新对象), 所以效率高于 String

        StringBuffer stringBuffer = new StringBuffer("hello");

String VS StringBuffer

  1. String保存的是字符串常量,里面的值不能更改,每次String类的更新实际上就是更改地址,效率较低//private final char value[];
    ·
  2. StringBuffer保存的是字符串变量,里面的值可以更改,每次
    StringBuffer的更新实际上可以更新内容,不用每次更新地址,效率较高//char[] value;/这个放在堆.
  • char value[] 数组的大小,默认16,不够了,才扩展
    public StringBuffer() {
        super(16);
    }

	//指定大小的
    public StringBuffer(String str) {
        super(str.length() + 16);
        append(str);
    }
  • StringBuffer 转为 String
//方式1 使用StringBuffer提供的toString方法
String s = stringBuffer3.toString();//方式2:使用构造器来搞定
String s1 = new String(stringBuffer3);
        //1. 创建一个 大小为 16的 char[] ,用于存放字符内容
        StringBuffer stringBuffer = new StringBuffer();

        //2 通过构造器指定 char[] 大小
        StringBuffer stringBuffer1 = new StringBuffer(100);
        //3. 通过 给一个String 创建 StringBuffer, char[] 大小就是 str.length() + 16

        StringBuffer hello = new StringBuffer("hello");
增删改插
        StringBuffer s = new StringBuffer("hello");
        //增
        s.append(',');// "hello,"
        s.append("张三丰");//"hello,张三丰"
        s.append("赵敏").append(100).append(true).append(10.5);//"hello,张三丰赵敏100true10.5"
        System.out.println(s);//"hello,张三丰赵敏100true10.5"


        //删
        /*
         * 删除索引为>=start && <end 处的字符
         * 解读: 删除 11~14的字符 [11, 14)
         */
        s.delete(11, 14);
        System.out.println(s);//"hello,张三丰赵敏true10.5"
        
        //改
        //老韩解读,使用 周芷若 替换 索引9-11的字符 [9,11)
        s.replace(9, 11, "周芷若");
        System.out.println(s);//"hello,张三丰周芷若true10.5"
        //查找指定的子串在字符串第一次出现的索引,如果找不到返回-1
        int indexOf = s.indexOf("张三丰");
        System.out.println(indexOf);//6
        
        //插
        //老韩解读,在索引为9的位置插入 "赵敏",原来索引为9的内容自动后移
        s.insert(9, "赵敏");
        System.out.println(s);//"hello,张三丰赵敏周芷若true10.5"
        
        //长度
        System.out.println(s.length());//22
        System.out.println(s);
插入 null
    public AbstractStringBuilder append(String str) {
        if (str == null)
            return appendNull();
        int len = str.length();
        ensureCapacityInternal(count + len);
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }

    private AbstractStringBuilder appendNull() {
        int c = count;
        ensureCapacityInternal(c + 4);
        final char[] value = this.value;
        value[c++] = 'n';
        value[c++] = 'u';
        value[c++] = 'l';
        value[c++] = 'l';
        count = c;
        return this;
    }
        String str = null;// ok
        StringBuffer sb = new StringBuffer(); //ok
        sb.append(str);//需要看源码 , 底层调用的是 AbstractStringBuilder 的 appendNull
        System.out.println(sb.length());//4

        System.out.println(sb);//null
        //下面的构造器,会抛出NullpointerException
        StringBuffer sb1 = new StringBuffer(str);//看底层源码 super(str.length() + 16);
        System.out.println(sb1);

千分位练习

        String s = "1234567890.59";
        //前面有 7个长度,所以需要 添加2个。如果是6个,只要添加一个。
        int count = s.indexOf(".");

        String[] split = s.split("\\.");

        //需要插入几个 , 逗号
        int i = (count - 1) / 3;

        StringBuffer sb = new StringBuffer(split[0]);

        //比如1234567,长度为7,第一次插入索引的位置为4
        int t = sb.length() - 3;
        for (int j = 1; j <= i; j++) {
            sb.insert(t, ",");
            t = t - 3;
        }
        System.out.println(sb.append(".").append(split[1]));
        String s = "1234567890.59";
        //前面有 7个长度,所以需要 添加2个。如果是6个,只要添加一个。
        int count = s.lastIndexOf(".");

        //需要插入几个 , 逗号
        int i = (count - 1) / 3;

        StringBuffer sb = new StringBuffer(s);

        int t = count - 3;
        for (int j = 1; j <= i; j++) {
            sb.insert(t, ",");
            t = t - 3;
        }
        System.out.println(sb);
        //new Scanner(System.in)
        String price = "8123564.59";
        StringBuffer sb = new StringBuffer(price);
        //先完成一个最简单的实现123,564.59
        //找到小数点的索引,然后在该位置的前3位,插入,即可
//        int i = sb.lastIndexOf(".");
//        sb = sb.insert(i - 3, ",");

        //上面的两步需要做一个循环处理,才是正确的
        for (int i = sb.lastIndexOf(".") - 3; i > 0; i -= 3) {
            sb = sb.insert(i, ",");
        }
        System.out.println(sb);//8,123,564.59
        DecimalFormat df = new DecimalFormat("###,###.00");
		//12,345,678.12
        System.out.println(df.format(12345678.123));

String 对比

String、StringBuffer 和StringBuilder的比较

  1. StringBuilder 和 StringBuffer非常类似,均代表可变的字符序列,而且方法也一样
  2. String:不可变字符序列,效率低,但是复用率高。
  3. StringBuffer:可变字符序列、效率较高(增删)、线程安全4) StringBuilder:可变字符序列、效率最高
    线程不安全
    ·
  4. String使用注意说明:
    string s=“a”;//创建了一个字符串
    S +=“b”;//实际上原来的"a"字符串对象已经丢弃了,现在又产生了一个字符串s+“b”(也就是"ab”)。如果多次执行这些改变串内容的操作,会导致大量副本字符串对象存留在内存中,降低效率。如果这样的操作放到循环中,会极大影响程序的性能=>结论:如果我们对String 做大量修改,不要使用String

builder 是 buffer的2倍,buffer是 String的 1千倍。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值