Java基础入门--第十章--I/O

I/O操作主要是指使用Java程序完成输人(Input)、输出(Output)操作。输人是指将文件内容以数据流的形式读人内存,输出是指通过Java程序将内存中的数据写人文件,输入输出操作在实际开发中应用较为广泛。本章将针对1/O的相关操作进行讲解。

10.1 File类

java.io.File 类是文件和目录路径名的抽象表示,主要用于文件和目录的创建、查找和删除等操作。

10.1.1 创建File对象

File类的常用对象构造方法

方法声明功能描述
File(String pathname)通过指定的一个字符串类型的文件路径创建一个File对象
File(String parent,String child)根据指定的一个字符串类型的父路径和一个字符串类型的子路径(包括文件名称)创建一个File对象
File(File parent,String child)根据指定的一个File类的父路径和一个字符串类型的子路径(包括文件名称)创建一个File对象
import java.io.File;

public class Example01 {
    public static void main(String[] args) {
        File f = new File("D:\\file\\a.txt");	//使用绝对路径创建File对象
        File f1 = new File("src\\Hello.java");	//使用相对路径创建File对象
        System.out.println(f);
        System.out.println(f1);
    }
}

在这里插入图片描述

10.1.2 File类的常用方法

File类提供了一系列方法,用于操作File类对象内部封装的路径指向的文件或者目录。File类的常用方法如表所示。

方法声明功能描述
boolean exists()判断File对象对应的文件或目录是否存在。若存在则返回true,否则返回false
boolean delete()删除File对象对应的文件或目录。若删除成功则返回true,否则返回false
boolean createNewFile()当File对象对应的文件不存在时,该方法将新建一个文件。若创建成功则返回true,否则返回false
String getName()返回File对象表示的文件或目录的名称
String getPath()返回File对象对应的路径
String getAbsolutePath()返回File对象对应的绝对路径(在UNIX/Linux等系统上,如果路径以斜线开始,则这个路径是绝对路径;在Windows等系统上,如果路径以盘符开始,则这个路径是绝对路径)
String getParentFile()返回File对象对应目录的父目录(即返回的目录不包含最后一级子目录)
boolean canRead()判断File对象对应的文件或目录是否可读。若可读则返回true,否则返回false
boolean canWrite()判断File对象对应的文件或目录是否可写。若可写则返回true,否则返回false
boolean isFile()判断File对象对应的是否是文件(而不是目录)。若是文件则返回true,否则返回false
boolean isDirectory()判断File对象对应的是否是目录(而不是文件)。若是目录则返回true,否则返回false
boolean isAbsolute()判断File对象对应的文件或目录是否是绝对路径
longlastModified()返回1970年1月1日0时0分0秒到文件最后修改时间的毫秒值
long length()返回文件内容的长度(单位是字节)
String[] list()递归列出指定目录的全部内容(包括子目录与文件),只列出名称
File[] listFiles()返回一个包含File对象所有子文件和子目录的File数组
import java.io.File;

public class Example01 {
    public static void main(String[] args) {
        File file = new File("src/com/test/Test.java");
        System.out.println("文件是否存在:"+file.exists());
        System.out.println("文件名:"+file.getName());
        System.out.println("文件大小:"+file.length()+"bytes");
        System.out.println("文件相对路径:"+file.getPath());
        System.out.println("文件绝对路径:"+file.getAbsolutePath());
        System.out.println("文件的父级对象是否为文件:"+file.isFile());
        System.out.println("文件删除是否成功:"+file.delete());
    }
}

演示了File类一系列方法的调用,首先判断文件是否存在,然后获取文件的名称、文件的大小、文件的相对路径、文件的绝对路径以及文件的父级对象是否为文件等信息,最后将文件删除。
在这里插入图片描述

import java.io.File;
import java.io.IOException;

public class Example01 {
    public static void main(String[] args) throws IOException {
        // 提供临时文件的前缀和后缀
        File f = File.createTempFile("itcast-", ".txt");
        f.deleteOnExit(); // JVM退出时自动删除文件f
        System.out.println("f是否为文件:"+f.isFile());
        System.out.println("f的相对路径:"+f.getPath());
    }
}

在一些特定情况下,程序需要读写一些临时文件,为此,File类提供了createTempFile()方法和deleteOnExit()方法,用于操作临时文件。createTempFile()方法用于创建一个临时文件,deleteOnExit()方法在Java虚拟机退出时自动删除临时文件。
在这里插入图片描述

10.1.3 遍历目录下的文件

File类中提供了list()方法,可以获取目录下所有文件和目录的名称。获取目录下所有文件和目录名称后,可以通过这些名称遍历目录下的文件,按照调用方法的不同,对目录下的文件遍历可分为以下3种方式。
(1)遍历指定目录下的所有文件。
(2)遍历指定目录下指定扩展名的文件。
(3)遍历包括子目录中的文件在内的所有文件。

1.遍历指定目录下的所有文件
File类的list()方法可以遍历指定目录下的所有文件。下面通过一个案例演示如何使用list()方法遍历目录下的所有文件

import java.io.File;

public class Example02 {
    public static void main(String[] args) throws Exception {
        // 创建File对象
        File file = new File("src/com/test");
        if (file.isDirectory()) {           // 判断File对象对应的目录是否存在
            String[] names = file.list (); // 获得目录下的所有文件的文件名
            for (String name : names) {
                System.out.println(name);	   // 输出文件名
            }
        }
    }
}

在这里插入图片描述
2.遍历指定目录下指定扩展名的文件
上述代码实现了遍历一个目录下所有文件的功能,然而有时程序只需要获取指定类型的文件,如获取指定目录下所有扩展名为“.java"的文件。针对这种需求,File类提供了一个重载的list()方法,该方法接收一个FilenameFilter类型的参数。FilenameFilter是一个接口,被称作文件过滤器,其中定义了抽象方法accept()用于依次对指定File的所有子目录或文件进行选代。在调用list()方法时,需要实现FilenameFilter,并在accept()方法中进行筛选,从而获得指定类型的文件。

import java.io.File;
import java.io.FilenameFilter;

public class Example02 {
    public static void main(String[] args) throws Exception {
        // 创建File对象
        File file = new File("src/com/test");
        // 创建过滤器对象
        FilenameFilter filter = new FilenameFilter() {
            // 实现accept()方法
            public boolean accept(File dir, String name) {
                File currFile = new File(dir, name);
                // 如果文件名以.java结尾返回true,否则返回false
                if (currFile.isFile() && name.endsWith(".txt")) {
                    return true;
                } else {
                    return false;
                }
            }
        };
        if (file.exists()) { // 判断File对象对应的目录是否存在
            String[] lists = file.list(filter); // 获得过滤后的所有文件名数组
            for (String name : lists) {
                System.out.println(name);
            }
        }
    }
}

在这里插入图片描述
3.遍历包括子目录下的文件在内的所有文件
前面的两个例子演示的都是遍历当前目录下的文件。有时候在一个目录下,除了文件,还有子目录,如果想获取所有子目录下的文件,list()方法显然不能满足要求,这时可以使用File类提供的另一个方法一listFiles()。该方法返回一个File对象数组,当对数组中的元素进行遍历时,如果元素中还有子目录需要遍历,则可以递归遍历子目录。

import java.io.File;

public class Example02 {
    public static void main(String[] args) {
        // 创建一个代表目录的File对象
        File file = new File("src/com/test");
        fileDir(file);					 // 调用FileDir方法
    }
    public static void fileDir(File dir) {
        File[] files = dir.listFiles();   // 获得表示目录下所有文件的数组
        for (File file : files) {		 // 遍历所有的子目录和文件
            if (file.isDirectory()) {
                fileDir(file);			 // 如果是目录,递归调用fileDir()
            }
            System.out.println(file.getAbsolutePath()); // 输出文件的绝对路径
        }
    }
}

在这里插入图片描述

10.1.3 删除文件及目录

在操作文件时,可能会遇到需要删除一个目录下的某个文件或者删除整个目录的情况,这时可以调用File类的delete()方法。

import java.io.File;

public class Example02 {
    public static void main(String[] args) {
        File file=new File("src/com/test/要删除的文本文件.txt");
        if(file.exists()) {
            System.out.println(file.delete());
        }
    }
}

在这里插入图片描述
File类的delete()方法只能删除一个指定的文件;
假如File对象代表一个目录,并且这个目录下包含子目录或文件,则File类的delete()方法不允许直接删除这个目录。在这种情况下,需要通过递归的方式将整个目录以及目录中的文件全部删除

import java.io.File;

public class Example02 {
    public static void main(String[] args) {
        File file = new File("D:\\Users\\蒋果冻\\Desktop\\old\\test");
        deleteDir(file);                           // 调用deleteDir删除方法
        System.out.println("删除成功!");
    }
    public static void deleteDir(File dir) {
        if (dir.exists()) {                       // 判断传入的File对象是否存在
            File[] files = dir.listFiles();    // 得到File数组
            for (File file : files) {           // 遍历所有的子目录和文件
                if (file.isDirectory()) {
                    deleteDir(file);        // 如果是目录,递归调用deleteDir()
                } else {
                    // 如果是文件,直接删除
                    file.delete();
                }
            }
            // 删除完一个目录里的所有文件后,就删除这个目录
            dir.delete();
        }
    }
}

在这里插入图片描述

10.2 字节流

10.2.1 字节流的概念

在计算机中,无论是文本、图片、音频还是视频,所有文件都是以二进制(字节)形式存在的。对于字节的输人输出,I/O系统提供了一系列流,统称为字节流。字节流是程序中最常用的流,根据数据的传输方向可将其分为字节输人流和字节输出流。JDK提供了两个抽象类一InputStream和OutputStream,它们是字节流的顶级父类,所有的字节输人流都继承InputStream,所有的字节输出流都继承OutputStream。为了方便理解,可以把InputStream和OutputStream比作两个管道
在这里插入图片描述
在图10-9中,InputStream被看成一个输人管道,OutputStream被看成一个输出管道,数据通过InputStream从源设备“流”到程序,通过OutputStream从程序“流”到目标设备,从而实现数据的传输。由此可见,I/O流中的输人输出是相对于程序而言的。在JDK中,InputStream类和OutputStream类提供了一系列与读写数据相关的方法。

方法声明功能描述
int read()从输人流读取一字节(8位),把它转换为0~255的整数,并返回这一整数
int read(byte[] b)从输人流读取若干字节,把它们保存到参数b指定的字节数组中,返回的整数表示读取的字节数
int read(byte[]b,int off,int len)从输人流读取若干字节,把它们保存到参数b指定的字节数组中,off指定字节数组保存数据的起始索引,len表示读取的字节数
void close()关闭输人流并释放与其关联的所有系统资源
void write(int b)向输出流写人一字节
void write(byte[] b)把参数b指定的字节数组的所有字节写到输出流
void write(byte[]b,int off,int len)将指定byte数组中从起始索引off开始的len字节写人输出流
void flush()刷新输出流并强制写出所有缓冲的输出字节
void close()关闭输出流并释放与其关联的所有系统资源

InputStream和OutputStream这两个类虽然提供了一系列和读写数据有关的方法,但是这两个类是抽象类,不能被实例化,因此,针对不同的功能,InputStream类和OutputStream类提供了不同的子类,形成了体系结构,分别如图10-10和图10-11所示。
在这里插入图片描述

10.2.2 字节流读文件

InputStream就是JDK提供的基本输人流,它是所有输人流的父类,FileInputStream是InputStream的子类,它是操作文件的字节输人流,专门用于读取文件中的数据。因为从文件读取数据是重复的操作,所以需要通过循环语句实现数据的持续读取。下面通过一个案例实现字节流对文件数据的读取。在实现案例之前,首先在Java项目的根目录下创建文本文件test.txt,在文件中输人内容“itcast”并保存。然后使用字节输人流对象读取test.txt文本文件。

import java.io.FileInputStream;

public class Example03 {
    public static void main(String[] args) throws Exception {
        // 创建一个文件字节输入流,并指定源文件名称
        FileInputStream inPut = new FileInputStream("src/com/test/test.txt");
        int b = 0;           // 定义一个int类型的变量b,记住每次读取的一个字节
        while (true) {
            b = inPut.read(); // 变量b记住读取的一个字节
            if (b == -1) { // 如果读取的字节为-1,跳出while循环
                break;
            }
            System.out.println(b); // 否则将b写出
        }
        inPut.close();
    }
}

在这里插入图片描述
由图可知,控制台打印的结果分别为105、116、99、97、115和116。在本章的开头讲过,计算机中的数据都是以字节的形式存在的。在test.txt文件中,字符i、t、c、a’、's、t各占一字节,所以最终结果显示的就是文件test.txt中的6字节对应的十进制数(即这6个字母的ASCII码值)。

10.2.3 字节流写文件

OutputStream是JDK提供的基本输出流,与InputStream类似,OutputStream是所有输出流的父类。OutputStream是一个抽象类,如果使用此类,则必须先通过子类实例化对象。OutputStream类有多个子类,其中FileOutputStream子类是操作文件的字节输出流,专门用于把数据写人文件。下面通过一个案例演示如何使用FileOutputStream将数据写人文件

import java.io.FileOutputStream;
import java.io.OutputStream;

public class Example03{
    public static void main(String[] args) throws Exception {
        // 创建一个文件字节输出流,并指定输出文件名称
        OutputStream out = new FileOutputStream("src/com/test/example.txt");
        String str = "传智教育";
        byte[] b = str.getBytes();
        for (int i = 0; i < b.length; i++) {
            out.write(b[i]);
        }
        out.close();
    }
}

在这里插入图片描述
若希望在已存在的文件内容之后追加新内容,则可使用FileOutputStream的构造函数FileOutputStream(StringfileName,booleanappend)创建文件输出流对象,并把append参数的值设置为true。下面通过一个案例演示文件内容的追加

import java.io.FileOutputStream;
import java.io.OutputStream;

public class Example03{
    public static void main(String[] args) throws Exception {
        //创建文件输出流对象,并指定输出文件名称和开启文件内容追加功能
        OutputStream out = new FileOutputStream("src/com/test/example.txt ", true);
        String str = "欢迎你!";
        //将字符串存入byte类型的数组中
        byte[] b = str.getBytes();
        for (int i = 0; i < b.length; i++) {
            out.write(b[i]);
        }
        out.close();
    }
}

在这里插入图片描述

10.2.4 文件的复制

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;

public class Example03 {
    public static void main(String[] args) throws Exception {
        // 创建一个字节输入流,用于读取当前目录下source文件夹中的"堆糖下载的风景图.jpg"文件
        InputStream in = new FileInputStream("src/source/堆糖下载的风景图.jpg");
        // 创建一个文件字节输出流,用于将读取的数据写入target目录下的文件中
        OutputStream out = new FileOutputStream("src/target/b.jpg");
        int len; // 定义一个int类型的变量len,记住每次读取的一个字节
        // 获取复制文件前的系统时间
        long beginTime = System.currentTimeMillis();
        while ((len = in.read()) != -1) { // 读取一个字节并判断是否读到文件末尾
            out.write(len); // 将读到的字节写入文件
        }
        // 获取文件复制结束时的系统时间
        long endTime = System.currentTimeMillis();
        System.out.println("复制文件所消耗的时间是:" + (endTime - beginTime) + "毫秒");
        in.close();
        out.close();
    }
}

在这里插入图片描述
实现的文件复制过程是逐字节读写,需要频繁地操作文件,效率非常低。这就好比从北京运送烤鸭到上海,如果有一万只烤鸭,每次运送一只,就必须运输一万次,这样的效率显然非常低。为了减少运输次数,可以先把一批烤鸭装在车厢中,这样就可以成批地运送烤鸭,这时的车厢就相当于一个缓冲区。在通过流的方式复制文件时,为了提高效率,也可以定义一个字节数组作为缓冲区。在复制文件时,可以一次性读取多个字节的数据,并保存在字节数组中,然后将字节数组中的数据一次性写人文件。程序中的缓冲区就是一块内存,它主要用于暂时存放输人输出的数据,由于使用缓冲区减少了对文件的操作次数,所以可以提高数据的读写效率。

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;

public class Example03 {
    public static void main(String[] args) throws Exception {
        // 创建一个字节输入流,用于读取当前目录下source文件夹中的"堆糖下载的风景图.jpg"文件
        InputStream in = new FileInputStream("src/source/堆糖下载的风景图.jpg");
        // 创建一个文件字节输出流,用于将读取的数据写入target目录下的文件中
        OutputStream out = new FileOutputStream("src/target/b.jpg");
        // 以下是用缓冲区读写文件
        byte[] buff=new byte[1024]; //定义一个字节数组,作为缓冲区

        int len; // 定义一个int类型的变量len读取读入缓冲区的字节流
        long beginTime = System.currentTimeMillis();
//        while ((len = in.read()) != -1) { // 读取一个字节并判断是否读到文件末尾
//            out.write(len); // 将读到的字节写入文件
//        }
        while((len=in.read(buff)) != -1) {
            out.write(buff,0,len);  //从第一个字节开始,向文件写入len个字节
        }
        // 获取文件复制结束时的系统时间
        long endTime = System.currentTimeMillis();
        System.out.println("复制文件所消耗的时间是:" + (endTime - beginTime) + "毫秒");
        in.close();
        out.close();
    }

}

在这里插入图片描述
复制文件所消耗的时间明显减少了,这说明使用缓冲区读写文件可以有效地提高程序的读写效率。

10.3 字符流

10.3.1 字符流定义及基本用法

同字节流一样,字符流也有两个抽象的顶级父类,分别是Reader类和Writer类。其中,Reader类是字符输人流,用于从某个源设备读取字符;Writer类是字符输出流,用于向某个目标设备写人字符。

Reader类的常用方法

方法声明功能描述
int read()以字符为单位读数据
int read(char cbuf[])将数据读人char类型的数组,并返回数据长度
int read(char cbuf],int off,int len)将数据读人char类型的数组的指定区间,并返回数据长度
void close()关闭数据流
long transferTo(Writer out)将数据直接读人字符输出流

Writer类的常用方法

方法声明功能描述
void write(int c)以字符为单位写数据
void write(char cbuf[])将char类型的数组中的数据写出
void write(char cbuf[],int off,int len)将char类型的数组中指定区间的数据写出
void write(String str)将String类型的数据写出
void wirte(String str,int off,int len)将String类型指定区间的数据写出
void flush()强制将缓冲区的数据同步到输出流中
void close()关闭数据流

Reader类和Writer类作为字符流的顶级父类,也有许多子类,形成了体系结构,分别如图10-19和图10-20所示。
在这里插入图片描述
从图10-19和图10-20可以看到,字符流的继承关系与字节流的继承关系类似,Reader类和Writer类的很多子类都是成对出现的。例如,FileReader和FileWriter用于读写文件;BufferedReader和BufferedWriter是具有缓冲功能的字符流,使用它们可以提高读写效率。
在这里插入图片描述

10.3.2 字符流读文件

在程序开发中,经常需要对文本文件的内容进行读取。如果想从文件中直接读取字符,便可以使用字符输人流FileReader,通过它可以从关联的文件中读取一个或一组字符。下面通过一个案例演示如何使用FileReader读取文件中的字符。首先在chapterl0项目的根目录下新建文本文件reader.txt并在其中输人字符“itcast”,然后在src目录中创建名称为Examplel4的类,在该类中创建字符输人流FileReader对象以读取reader.txt文件中的内容

import java.io.FileReader;

public class Example03 {
    public static void main(String[] args) throws Exception {
        //创建一个FileReader对象用R读取文件中的字符
        FileReader reader = new FileReader("src/com/test/reader.txt");
        int ch;     // 定义一个变量用于记录读取的字符
        while ((ch = reader.read()) != -1) {    // 循环判断是否读取到文件的末尾
            System.out.print((char)ch);   // 不是字符流末尾就转为字符打印
        }
        reader.close();// 关闭文件读取流,释放资源
    }
}

在这里插入图片描述
FileReader对象的read()方法返回的是int类型的值。如果想获得字符,就必须进行强制类型转换

10.3.3 字符流写文件

在10.3.2节中讲解了字符流对文本文件内容的读取。本节讲解通过字符流向文本文件中写人内容,此时需要使用FileWriter类,该类可以一次向文件中写人一个或一组字符。下面通过一个案例演示如何使用FileWriter将字符写人文件。在该类中使用FileWriter将字符写人writer.txt文件中

import java.io.FileWriter;

public class Example04 {
    public static void main(String[] args) throws Exception {
        //创建一个FileWriter对象,用于向文件中写入数据
        FileWriter writer = new FileWriter("writer.txt");
        String str="Hello World";
        writer.write(str);
        writer.write("\r\n");
        writer.close();
    }
}

在这里插入图片描述

10.4 转换流

前面提到I/O流分为字节流和字符流,字节流和字符流之间可以进行转换。JDK提供了两个类用于将字节流转换为字符流,分别是InputStreamReader和OutputStreamWriter。InputStreamReader和OutputStreamWriter也称为转换流,其作用如下:
(1)InputStreamReader是Reader的子类,它可以将一个字节输人流转换成字符输人流,方便直接读取字符。
(2)OutputStreamWriter是Writer的子类,它可以将一个字节输出流转换成字符输出流,方便直接写人字符。通过InputStreamReader和OutputStreamWriter将字节流转换为字符流,可以提高文件的读写效率。下面通过一个案例演示如何将字节流转为字符流。首先,在chapter10项目的根目录下新建文本文件src.txt,并在文件中输人“helloitcast”。其次,在src文件夹中创建一个名称为Example的类。在Examplel6类中创建字节输人流FileInputStream对象读取src.txt文件中的内容,并将字节输人流转换成字符输人流。再次,创建一个字节输出流对象,并指定目标文件为des.txt。最后,将字节输出流转换成字符输出流将字符输出到文件中。

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;

public class Example04 {
    public static void main(String[] args) throws Exception {
        // 创建一个字节输入流in,并指定源文件为src.txt
        FileInputStream in = new FileInputStream("src.txt");
        // 将字节输入流in转换成字符输入流isr
        InputStreamReader isr = new InputStreamReader(in);
        // 创建一个字节输出流对象out,并指定目标文件为des.txt
        FileOutputStream out = new FileOutputStream("des.txt");
        // 将字节输出流out转换成字符输出流osw
        OutputStreamWriter osw = new OutputStreamWriter(out);
        int ch; 					// 定义一个变量用于记录读取的字符
        while ((ch = isr.read()) != -1) { 	// 循环判断是否读取到文件的末尾
            osw.write(ch);  // 将字符数据写入des.txt文件中
        }
        isr.close(); // 关闭字符输入流,释放资源
        osw.close(); // 关闭字符输出流,释放资源
    }
}

在这里插入图片描述

10.5 序列化和反序列化

程序在运行过程中,数据都保存在Java对象(内存)中,但很多情况下还需要将一些数据永久保存到磁盘上。为此,Java提供了对象序列化机制,可以将对象中的数据保存到磁盘。对象序列化(serialize)是指将一个Java对象转换成一个I/O流的字节序列的过程。对象序列化机制可以使内存中的Java对象转换成与平台无关的二进制流,通过编写程序,既可以将这种二进制流持久地保存在磁盘上,又可以通过网络将其传输到另一个网络节点。其他程序在获得了二进制流后,还可以将二进制流恢复成原来的Java对象,这种将I/O流中的字节序列恢复为Java对象的过程称为反序列化(deserialize)。如果想让某个对象支持序列化机制,那么这个对象所属的类必须是可序列化的。在Java中,可序列化的类必须实现Serializable或Externalizable两个接口之一。Serializable接口和Externalizable接口实现序列化机制的主要区别如表所示。

Serializable接口Externalizable接口
系统自动存储必要的信息由程序员决定存储的信息
Java内部支持,易于实现,只需实现该接口即可该接口中只提供了两个抽象方法,实现该接口时必须实现,不需要其他代码支持这两个抽象方法
性能较差性能较好

本章主要介绍了I/O流的相关知识。本章主要内容如下:
File类,包括创建File对象、File类的常用方法、遍历目录下的文件和删除文件及目录;
字节流,包括字节流的概念、字节流读文件、字节流写文件和文件的复制;
字符流,包括字符流的定义及基本用法、字符流读文件和字符流写文件;
转换流的使用;
序列化和反序列化。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值