Java - IO流

目录

一、字节流

1. InputStream和OutputStream

1. FileInputStream和FileOutputStream

2.  BufferedInputStream和BufferedOutputStream

3.  ObjectInputStream和ObjectOutputStream

4.  ZipInputStream和ZipOutputStream

5. PrintStream

二、字符流

1. Reader

1. FileReader和FileWriter

2. BufferedReader和BufferedWriter

3. InputStreamReader和OutputStreamWriter

4. PrintWriter

 三、习题测试

1. 拷贝一个文件夹(考虑子文件夹)

2. 文件加密

3. 实现一个验证程序运行次数的小程序


一、字节流

字节流可以操作所有类型的文件

1. InputStream和OutputStream

InputStream,字节输入流,负责读取数据,他和OutputStream,Reader,Writer都是抽象类,子目录下的都是它的子类

1. FileInputStream和FileOutputStream

FileInputStream是InputStream类的子类,OutputStream是OutputStream的子类,负责向本地文件中读取数据和写入数据,很多相关的细节都在代码的注释当中,相关类的实现步骤是

1. 创建字节输出流或输入流对象

2. 读取数据或者写入数据

3. 释放资源

字节输出流细节
1. 创建字节节输出流对象
    细节1:参数是字符串表示的路径或者是File对象都是可以的
    细节2:如果文件不存在,或创建一个新的文件,但是父级路径要存在
    细节3:如果文件已经存在,会清空文件,覆盖掉
2. 写数据
    细节:write 方法的参数是整数,但是实际写到文件上的是整数对应的ascll值
3. 释放资源
    每次使用完之后,都要释放资源,如果不释放,程序会一直占用该文件
    public static void main5(String[] args) throws IOException {
        FileInputStream fis =new FileInputStream("C:\\Users\\Lenovo\\Desktop\\code\\Java\\Learn\\IO\\Test.txt");
        int a = fis.read();  //整形接收,会有隐式类型转换
        System.out.println(a);
        int b = fis.read();
        System.out.println(b);
        int c = fis.read();
        System.out.println(c);
        int d = fis.read();
        System.out.println(d);

        //读到-1就是文件读取结束了
        fis.close();
    }

    /*
    如果没有这个文件,会直接报错
    并不是直接创建,因为没有意义
    读的是数据在ascll上对应的数据
    读到文件末尾返回-1,空格也会返回,ascll值是32

     */
    //循环读取
    public static void main6(String[] args) throws IOException{
        FileInputStream fis =new FileInputStream("C:\\Users\\Lenovo\\Desktop\\code\\Java\\Learn\\IO\\Test.txt");
        int b;
        while ((b= fis.read())!=-1){
            System.out.println((char) b);
        }
        fis.close();
    }
    //写一个数据
    public static void main1() throws IOException {
        FileOutputStream fos  = new FileOutputStream(你所操作的文件的路径);
        fos.write(97);
        fos.close();
    }
    //将一个数组写入
    public static void main2() throws IOException {
        FileOutputStream fos = new FileOutputStream(你所操作的文件的路径);
        byte[] bytes = {97,98,99,100};
        fos.write(bytes);
        fos.close();
    }
    //将一个数组的部分数据写入
    public static void main3(String[] args) throws IOException {
        FileOutputStream fos = new FileOutputStream(你所操作的文件的路径);
        byte[] bytes = {97,98,99,100};
        fos.write(bytes,1,2); //相当于添加数组的起始索引和要写入的长度
        fos.close();
    }
    //换行写,续写
    /*
    换行写
        windows  \r \n
     */
    public static void main4() throws IOException {
        FileOutputStream fos = new FileOutputStream(你所操作的文件的路径,true);

        String string  = "abc";
        byte[] arr = string.getBytes(); //把字符串变成了字节数组
        System.out.println(Arrays.toString(arr));

        String string1 = "def";
        byte[] arr2 = string1.getBytes();

        String string2 = "\r\n";
        byte[] arr3 = string2.getBytes();
        fos.write(arr);
        fos.write(arr3);//写入\r\n  换行写
        fos.write(arr2);
        fos.close();

        //FileOutputStream第二个参数默认为false,就是是否续写
        //如果不续写,那么下次就会先清空,所以我们只要再路径后写一个true
        //打开续写,就可以续写了
    }

2.  BufferedInputStream和BufferedOutputStream

不同于FileInputStream,FileOutStream,FileReader,FileWriter这些基本流的缓冲流
    字节缓冲流
    因为字符流的体系中已经有了缓冲区,所以BufferedReader和BufferedWriter的效率提升并不明显
    但是他们两个有很好用的方法

    高级流
        缓冲流
            字节缓冲流
                BufferedInputStream  拷贝文件时,高效的读取数据
                BufferedOutStream
            字符缓冲流
                BufferedReader
                BufferedWriter

        构造方法
            BufferedInputStream(InputStream is)
            BufferedOutputStream(OutputStream os)  本质的效果都是提高性能

字节缓冲流提高效率的原理

        拷贝的时候,从硬盘上读取数据源
            通过流,来关联内存
            数据源将数据通过基本流放入缓冲区中
            缓冲输入流放入内存中

    但是如果是缓冲输入流,就跳过了基本留着一步骤
    尽量将消耗时间的步骤放到内存中,内存的速度是非常快的,效率会更高
    //1. 利用字节缓冲流拷贝文件
    public static void main1() throws IOException {
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("chinese.txt"));
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("copy.txt"));
        //默认缓冲区的大小是8192
        //缓冲流中的基本流不需要关闭,在其方法内部已经帮我们关闭

        //循环读取并写到目的地

        int b;
        while((b = bis.read())!=-1){
            bos.write(b);
        }

        bos.close();
        bis.close();
    }

3.  ObjectInputStream和ObjectOutputStream

反序列化流
    ObjectInputStream
序列化流
    ObjectOutputStream
    可以把java中的对象写到本地文件中,但是读不懂  作用是让玩家无法再本地文件修改对象数据
    但是可以用反序列化流读取到程序中

    与对象有关,所以叫ObjectStream
        构造方法
            ObjectOutputStream(OutputStream out)  把基本流包装成高级流
        成员方法
            public final void writeObject(Object obj) 把对象序列化到文件中去

序列化流和反序列化流是操作对象的,我们就先写一个Student对象

public class Student implements Serializable {

    String name;
    int age;
    int Chinese;
    int Math;
    int English;
    int physic;
    private String address;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

我们就可以通过以下这段代码,将创建的对象写入本地文件中

        Student stu1 = new Student("肖颖琦",18);
        stu1.setChinese(120);
        stu1.setMath(130);
        stu1.setEnglish(140);
        ObjectOutputStream oos = new ObjectOutputStream(Files.newOutputStream(Paths.get("src/Object/Object.txt")));
        oos.writeObject(stu1);
        oos.close();

我们写入到本地文件中后,会发现写入后的文件,我们根本读不懂,但是这是正常现象,你也不想你辛辛苦苦创建的游戏角色,玩家可以轻松修改里面的数值吧。

我们还可以把这个对象读取出来

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("src/Object/Object.txt"));
        Student o = (Student) ois.readObject();
        System.out.println(o.getEnglish());
        ois.close();

这里注意readObject的返回值是Object,我们必须将他强转为Student类型,才能继续读取

假设,第一步先将对象写到序列化流中
之后呢,我在程序中为这个对象多加了一个属性如何呢?

编译器会报错,具体的原理是什么?

原理
    写入本地文件时,序列化流会把所有的属性都先转化为类型为long的序列号
    如果修改了类中的代码,会有一个新的序列号,当序列号不一样时,文件中的序列号跟JavaBean的序列号不匹配
    所以我们可以固定版本号(序列号)
    private static final long 版本号 = 1L;
    版本号的写法是serialVersionUID

所以,刚才我们的Student对象开头第一个属性,应该是版本号

private static final long serialVersionUID = -9031108275538710161L;

这样的话,我们就可以在对象里面新加属性值了

如果想要一次写入多个对象
有时候读取时会忘记读取多少个对象,可以将要写入的对象放入一个ArrayList中,将List写入本地文件中
打印时,将ObjectInputStream.readObject传回来的Object强转成ArrayList<Student>即可
最后打印ArrayList<List>即可

4.  ZipInputStream和ZipOutputStream

有了前面的代码经验,这个其实非常简单了

解压文件

public class Main {
    public static void main(String[] args) throws IOException {
        //创建要解压的压缩包File
        File src = new File("C:\\Users\\Lenovo\\Documents\\WeChat Files\\wxid_6f4pst98k15k22\\FileStorage\\File\\2024-06\\肖颖琦补交作业.zip");
        File dest = new File("C:\\Users\\Lenovo\\Desktop\\code\\Java\\Learn\\learnIO");
        unzip(src,dest);
    }

    //定义一个方法来解压
    public static void unzip(File src,File dest) throws IOException {
        ZipInputStream zip = new ZipInputStream(Files.newInputStream(src.toPath()));
        //能够获取到压缩包中的每个文件对象


        //压缩包中的每一个文件或者每一个文件夹,其实就是一个entry对象
        ZipEntry entry;
        while((entry = zip.getNextEntry() )!= null){
            System.out.println(entry);
            if(entry.isDirectory()){
                File file = new File(dest,entry.toString());
                file.mkdirs();
            }else{
                FileOutputStream fos = new FileOutputStream(new File(dest,entry.toString()));
                int b;
                while((b = zip.read())!=-1){
                    fos.write(b);
                }
                fos.close();
                zip.closeEntry();
            }
        }
    }
}

压缩流和解压流,可以压缩文件或者压缩文件夹

压缩文件

public class ZipFile {
    //压缩的本质就是把每一个文件或者文件夹看作是ZipEntry对象放入压缩包中
    public static void main(String[] args)throws IOException {
        java.io.File src = new java.io.File("IMG_20240614_182231.jpg");
        java.io.File dest = new java.io.File("C:\\Users\\Lenovo\\Desktop\\code\\Java\\Learn\\learnIO");
        toZip(src,dest);
    }

    public static void toZip(java.io.File src, java.io.File dest) throws IOException {
        //1. 创建压缩流
        ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(new java.io.File(dest, "a.zip")));
        //
        //2. 创建ZipEntry对象
        ZipEntry entry = new ZipEntry("IMG_20240614_182231.jpg");
        //可以通过ZipEntry在压缩包中创建子文件加
        //ZipEntry也表示压缩包中的文件夹路径

        //3. 把ZipEntry对象放到压缩包中
        zos.putNextEntry(entry);
        //4. 把src中的数据写到压缩包中
        FileInputStream fis = new FileInputStream(src);
        int b;
        while ((b = fis.read())!=-1){
            zos.write(b);
        }

        zos.closeEntry();
        zos.close();
    }
}

压缩文件夹

public class ZipDictionary {
    public static void main(String[] args) throws IOException {
        //1. 创建File对象表示要压缩的文件夹
        File src = new File("C:\\Users\\Lenovo\\Desktop\\code\\Java\\Learn\\learnIO\\aaa");
        //2. 创建Field、le对象表示压缩包放在哪里,压缩包的父级路径
        File destParent = src.getParentFile();
        //3. 创建File对象,表示压缩包的路径
        File dest = new File(destParent,src.getName()+".zip");
        //4. 创建压缩流串联压缩包
        ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(dest));
        //5. 获取src中的每一个文件,变成ZipEntry对象,放到压缩包中
        toZip(src,zos,src.getName());
        //6. 释放资源
        zos.close();
    }
    //1. 数据源
    //2. 压缩流
    //3. 压缩包内部的路径
    public static void toZip(File src,ZipOutputStream zos,String name) throws IOException {
        File[] files = src.listFiles();
        assert files != null;
        for (File file:files){
            if(file.isFile()){
                ZipEntry entry = new ZipEntry(name+"\\"+file.getName());
                zos.putNextEntry(entry);//压缩包内部的路径
                FileInputStream fis = new FileInputStream(file);
                int b;
                while((b = fis.read())!=-1){
                   zos.write(b);
                }
                fis.close();
                zos.closeEntry();
            }else{
                toZip(file,zos,name+"\\"+file.getName());
            }
        }
    }
}

5. PrintStream

打印流只能写不能读,因为打印流只操作文件目的地,不操作数据源
特有的写出方法可以实现数据原样写出
    不走字节流,是原样与控制台一样的打印
特有的写出方法可以实现自动刷新,自动换行
    打印一次数据 = 写出+换行+刷新
我们经常用的打印sout,其实也是一种打印流,System.out是一个打印流,我们经常使用它的println方法和print方法
构造方法
    PrintStream(OutputStream  /  File  /  String)  关联字节输出流,文件,文件路径
               (String fileName,Charset charset)   指定字符编码
               (OutputStream out,boolean autoFlush)  自定刷新
               (OutputStream out,boolean autoFlush,String encoding) 指定字符编码且自动刷新
成员方法
    public void write(int b)
                println(Xxx xx) 特有方法,打印任意数据,自定刷新自动换行
                print(Xxx,xx)   特有方法,打印任意数据,不换行
                printf(String format,Object args) 特有方法,带有占位符的打印语句,不换行
        PrintStream ps = System.out;
        ps.println(1);

二、字符流

字符流只可以操作纯文本文件,即windows系统自带的记事本打开并且能读懂的文件

相关的有txt文件,md文件,xml文件,lrc文件等

IO流体系
    字节流
        InputStream  字节输入流
        OutputStream 字节输出流
    字符流
        Reader 字符输入流
            FileReader 操作本地文件的字符输入流
        writer 字符输出流
            FileWriter 操作本地文件的字符输出流


上述四个类都是抽象类,我们要看他们的子类FileReader和FileWriter

1. 创建字符输入流对象(构造方法)
    public FileReader(File file)创建字符输入流关联本地文件
    public FileReader(String pathname) 创建字符输入流关联本地文件
2. 读取数据(成员方法)
    public int read() 读取数据,督导末尾返回-1  按照字节流读取,遇到中文,一次读取多个字节

    public int read(char[] buffer) 读取多个数据,督导末尾返回-1 放入char类型的数组中

3. 释放资源
    public int close() 关流

1. Reader

1. FileReader和FileWriter

什么是字符流
    字符流其实是字节流+字符集

特点:
    输入流:一次读一个字节,遇到中文是,一次读多个字节,具体读多少字节,与字符流有关

使用场景
    对于纯文本文件进行读写操作
如果我们想要用字节流读取文件,但是文件中有中文的话会如何呢?

我们会发现只会读取出一堆乱码,是什么原因?

想要解决乱码就要学习字符集和计算机存储规则

任意数据都是以二进制的形式储存的
我们把8个bit称为1个字节,储存中文需要4个字节
但是储存英文只需要一个字节
ascll有128个数据,对于英语来说已经够用了
    如果要储存a,ascll是97,97的二进制0110 0001,计算机存储补码,只有7位只需要一个字节
    那么汉字是如何储存的呢?
    其实也需要一张类似ascll的表
        国家标准中文编码GBK规定一个汉字使用两个字节来存储,2的16次方,可以存6万5千多个汉字
如果每个国家都有自己的字符集的话,将难以兼容
不利于软件的发展,所以美国研究了万国码,unicode编码系统
unicode完全兼容ascll字符集,utf就是Unicode transfer format
utf-16规定使用2-4个字节
utf-32规定固定使用4个字节

为了避免简单英文字母使用过多的内存,就诞生了utf-8的版本,使用1-4个字节
在utf-8中,中文是用3个字节来保存的
    utf-8不是一种字符集,而是unicode字符集的一种编码方式
那么乱码的出现是为什么?
    1. 读取数据时未读完整个汉字
    2. 编码和解码时的方式不统一

解决方法
    统一编码解码方式,默认统一为utf-8的编解码方式

如何避免产生乱码?
    1. 不要使用字节流读取文本文件
    2. 编码解码时使用同一个码表,同一个编码方式
java中是可以看到编码的结果和解码的结果的
编码方法
public byte[] getBytes()                    使用默认的编码方式
public byte[] getBytes(String charsetName)  使用指定的编码方式

解码方法(构造方法)
String(byte[] bytes)                      使用默认的解码方式
String(byte[] bytes, String charsetName)  使用指定的解码方式
        String str = "ai你哦";
        byte[] bytes1 = str.getBytes(); //使用默认的utf-8的方式
        System.out.println(Arrays.toString(bytes1)); //数组的长度是8

        byte[] bytes2 = str.getBytes("GBK");
        System.out.println(Arrays.toString(bytes2));
        //解码
        String str2 = new String(bytes1);
        System.out.println(str2);

        String str3 = new String(bytes1,"GBK");
        System.out.println(str3);//编码解码方式不一样,产生了乱码
FileWriter的构造方法
    public FileWriter(File file)  创建字符输出流关联本地文件,无续写
    public FileWriter(String pathname)

    public FileWriter(File file,boolean append) 创建字符输出流关联本地文件,续写
    public FileWriter(String pathname,boolean append) 创建字符输出流关联本地文件
FileWriter的成员方法
    void write(int c) 写一个字符
    void write(String s)写一个字符串
    void write(String str,int off,int len)  写出一个数组的一部分
    void write(char[] chars) 写一个字符数组
    void write(char[] chars,int off,int len) 写一个字符数组的一部分
        FileWriter fw = new FileWriter("chinese.txt",true);
        fw.write("无有因\n慈悲颠倒何故");
        fw.close();

字符流原理解析
    程序输入进来的数据是数据源,idea由UTF-8编码
    创建的流,可以为内存,和数据源建立一个桥梁
    内存中,由一个大小为8192的字节数组作为缓冲区
    创建了变量,内存中有一块儿变量区域

    read函数执行时,会先判断缓冲区是由有数据可以读取,没有的话会从文件中读取
    每次尽可能装满数据
    当read发现缓冲区是多个字节时,就会读取为正确的字符为不是字节
    但是字节流没有缓冲区
        空参read的方法,一次读取一个字节,遇到中文一次读取多个字节,把字节解码并转成十进制返回
        有参的read方法,把读取字节解码,强转三步合并了,强转之后的字符放到数组中

2. BufferedReader和BufferedWriter

底层自带了长度为8192的缓冲区提高性能
但是由于字符流自带缓冲区
所有效率提升不太明显
但是字符缓冲流又两个很好用的方法值得学习

    字符缓冲输入流特有方法
        public String readLine() 读取一行行数据,遇到回车换行结束,但是不会把换行回车读取到
                                 如果没有数据可读,将返回null
    字符缓冲输出流特有方法
        public void newLine()跨平台的换行
public static void main2() throws IOException{
        BufferedReader br = new BufferedReader(new FileReader("chinese.txt"));
        BufferedWriter bw = new BufferedWriter(new FileWriter("copy.txt"));
        String line = br.readLine();
        System.out.println(line);
        bw.close();
        br.close();
    }


    public static void main(String[] args)throws IOException {
        BufferedWriter bw = new BufferedWriter(new FileWriter("chinese.txt",true));

        bw.write("无有因");
        bw.newLine();
        bw.write("伏案青背何故");
        bw.newLine();
        //bw是不续写的,存在就清空,不存在就新建,要开启续写
        //开启续写的方法是向FileWriter的第二个参数赋值为true

        bw.close();
    }

3. InputStreamReader和OutputStreamWriter

转换流是字符流和字节流之间的桥梁
字节流通过转换流,就可以成为字符流  InputStreamReader
转换输出流还可以将字符流转换为字节流向外写出 OutputStreamWriter
转换流本身是字符流的一员,父类都是Reader和Writer
应用场景
    字节流中想要使用字符流中的方法
package InputReader;
import java.io.*;

public class Test1 {
    //需求1:手动创建一个GBK的文件,把文件中的中文读取到内存中,不能出现乱码
    //需求2:把一段中文按照GBK的方式写到本地文件中
    //需求3:把本地文件的GBK文件,转为UTF-8文件
    //idea默认的读写方式是UTF-8

    public static void main(String[] args) throws IOException {
        //利用转换流按照指定字符编码读取
        InputStreamReader isr = new InputStreamReader(new FileInputStream("GBK1.txt"),"GBK");
        int ch;
        while((ch = isr.read())!=-1){
            System.out.print((char)ch);
        }
        isr.close();
        //在JDK11中,这种方式被淘汰了,我们可以使用它的替代方案

        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("b.txt"),"GBK");
        osw.write("你好你好");
        osw.close();
    }

//    public static void main(String[] args) throws IOException{
//        FileReader fr = new FileReader("GBK1.txt");//在JDK11中,这个方法可以有两个参数
//        //Charset.forName("GBK")
//    }

}

4. PrintWriter

打印流只能写不能读,因为打印流只操作文件目的地,不操作数据源
特有的写出方法可以实现数据原样写出
    不走字节流,是原样与控制台一样的打印
特有的写出方法可以实现自动刷新,自动换行
    打印一次数据 = 写出+换行+刷新
构造方法
    字节打印流的构造方法与字节打印流的构造方法和成员方法是一样的
    都大同小异
我们经常用的打印sout,其实也是一种打印流,System.out是一个打印流,我们经常使用它的println方法和print方法

字符打印流与字节打印流大同小异,成员方法也比较简单

就不多赘述


        System.out.close();

        System.out.println(1);

        //上述的代码就会是我们经常用的打印流关闭之后就无法打印了

 三、习题测试

1. 拷贝一个文件夹(考虑子文件夹)

public class Test1 {
    //1.拷贝一个文件夹(考虑子文件夹)  "C:\Users\Lenovo\Desktop\a"
    public static void main(String[] args) throws IOException {
        File src = new File("C:\\Users\\Lenovo\\Desktop\\a");
        File dest = new File("C:\\Users\\Lenovo\\Desktop\\v");
        copydir(src,dest);
    }

    public static void copydir(File src,File dest) throws IOException {
        dest.mkdirs();
        File[] files = src.listFiles();
        for (File f:files){
            if(f.isFile()){
                FileInputStream fis = new FileInputStream(f);
                FileOutputStream fos = new FileOutputStream(new File(dest,f.getName()));//以文件开始,以文件结束
                byte[] bytes = new byte[1024];
                int len;
                while((len = fis.read(bytes))!=-1){
                    fos.write(bytes,0,len);
                }
                fos.close();
                fis.close();
            }else{
                copydir(f,new File(dest,f.getName()));
            }
        }
    }
}

2. 文件加密

文件加密
    加密原理:对于原始文件中的每一个字节数据进行更改,然后将更改以后的数据存储到新的文件中
    解密原理:读取加密后的文件,按照加密的规则反向操作,变为原始文件
public class Test2 {
    /*
    2. 文件加密
        加密原理:对于原始文件中的每一个字节数据进行更改,然后将更改以后的数据存储到新的文件中
        解密原理:读取加密后的文件,按照加密的规则反向操作,变为原始文件
     */
    public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream("C:\\Users\\Lenovo\\Pictures\\Saved Pictures\\1.jpg");
        FileOutputStream fos = new FileOutputStream("C:\\Users\\Lenovo\\Pictures\\Saved Pictures\\2.jpg");

        int b;
        while((b = fis.read())!=-1){
            fos.write(b^2);
        }
        fos.close();
        fis.close();


    }
}

3. 实现一个验证程序运行次数的小程序

当程序运行超过3此时给出提示,本软件只能免费试用三次,欢迎您注册会员后继续使用
public class Test3 {
    //3. 实现一个验证程序运行次数的小程序
    /*
    1. 当程序运行超过3此时给出提示,本软件只能免费试用三次,欢迎您注册会员后继续使用
     */
    public static void main(String[] args) throws IOException {
        BufferedReader bufferedReader = new BufferedReader(new FileReader("Test.txt"));
        String b = bufferedReader.readLine();
        int count = Integer.parseInt(b);
        count++;
        if(count <= 3){
            System.out.println("欢迎使用本软件,第"+count+"次使用免费");
        }
        else{
            System.out.println("本软件只能使用三次,欢迎您注册会员后继续使用");
        }
        BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("Test.txt"));
        //注意这里的bw不能创建在上面
        /*
            输出流在关联文件的时候,如果文件不存在就创建,如果文件已经存在但没有续写,就会将文件清空
         */

        bufferedWriter.write(count+""); //+""这样变成字符串
        bufferedWriter.close();
        bufferedReader.close();
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值