Java基础-IO流

Java基础-IO流

一、File

File 类(磁盘操作)可以用于表示文件和目录的信息,但是它不表示文件的内容。递归地列出一个目录下所有文件:

public static void listAllFiles(File dir) {
    if (dir == null || !dir.exists()) {
        return;
    }
    if (dir.isFile()) {
        System.out.println(dir.getName());
        return;
    }
    for (File file : dir.listFiles()) {
        listAllFiles(file);
    }
}

二、IO 概述

什么是 IO: 磁盘与内存之间的数据交互,可以看做是一种数据的流动,按照流动方向,以内存为基准,分为输入 input 和输出 output,即流向内存的是输入流,流出内存的是输出流。Java IO操作主要是指使用 java.io包下的内容,进行输入、输出操作。输入也叫读取数据,输出也叫写出数据。

IO 的分类: 根据数据的流向分为:输入流和输出流。根据数据的类型分为:字节流(以字节为单位)和字符流(以字符为单位)。

输入流输出流
字节流InputStreamOutputStream
字符流ReaderWriter

三、字节流

一切文件数据(文本、图片、视频)在存储时,都是以二进制数字的形式保存,传输时也是如此。所以,字节流可以传输任意文件数据,在操作流的时候,我们要时刻明确,无论使用什么样的流对象,底层传输始终为二进制数据。

字节输入流InputStreamjava.io.InputStream抽象类是表示字节输入流的所有类的超类,可以读取字节信息到内存。它定义了字节输入流的基本共性。

在这里插入图片描述

public abstract int read()从输入流读取数据的下一个字节
public int read(byte b[] b)从输入流中读取一些字节数,并将它们存储到字节数组b中
public int read(byte b[] b, int off, int len)off 写入数据的位置,数组中的起始偏移量。从输入流读取多达len字节的数据到字节数
public void close()关闭此输入流并释放与此流相关联的任何资源
// 读取数据
public abstract int read()
 
// 将读取到的数据放在 byte 数组中,该方法实际上是根据下面的方法实现的,off 为 0,len 为数组的长度
public int read(byte b[])
 
// 从第 off 位置读取 len 长度字节的数据放到 byte 数组中,流是以 -1 来判断是否读取结束的
public int read(byte b[], int off, int len)
 
// 跳过指定个数的字节不读取,想想看电影跳过片头片尾
public long skip(long n)
 
// 返回可读的字节数量
public int available()
 
// 读取完,关闭流,释放资源
public void close()
 
// 标记读取位置,下次还可以从这里开始读取,使用前要看当前流是否支持,可以使用 markSupport() 方法判断
public synchronized void mark(int readlimit)
 
// 重置读取位置为上次 mark 标记的位置
public synchronized void reset()
 
// 判断当前流是否支持标记流,和上面两个方法配套使用
public boolean markSupported()

字节输出流OutputStreamjava.io.OutputStream抽象类是表示字节输出流的所有类的超类,它定义了字节流的基本共性功能方法。

public abstract void write(int b)将指定的字节写入此输出流
public void write(byte b[])将 b.length字节从指定的字节数组写入此输出流
public void write(byte b[], int off, int len)从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流
public void flush()刷新此输出流并强制任何缓冲的输出字节被写出
public void close()关闭此输出流并释放此流相关联的任务系统资源
//写入一个字节,可以看到这里的参数是一个 int 类型,对应上面的读方法,int 类型的 32 位,只有低 8 位才写入,高 24 位将舍弃。
public abstract void write(int b)
 
// 将数组中的所有字节写入,和上面对应的 read() 方法类似,实际调用的也是下面的方法。
public void write(byte b[])
 
// 将 byte 数组从 off 位置开始,len 长度的字节写入
public void write(byte b[], int off, int len)
 
// 强制刷新,将缓冲中的数据写入
public void flush()
 
// 关闭输出流,流被关闭后就不能再输出数据了
public void close()

四、文件流

FileInputStream把硬盘中的数据读取到内存中

/**
 * java.io.InputStream: 字节输入流。 此抽象类是表示字节输入流的所有类的超类。
 * java.io.FileInputStream extends InputStream: 文件输入流
 * 作用:把硬盘文件中的数据,读取到内存中
 *
 * 构造方法:public FileInputStream(String name)   文件路径
 *          public FileInputStream(File file)    文件
 * 构造方法的作用:
 *      1、创建一个 FileInputStream 对象;
 *      2、会把 FileInputStream对象指向构造方法中创建的文件
 *  java程序--JVM--OS读取方法--读取文件
 *  1个中文 GBK占两个字节 UTF-8占3个字节
 */
public class MyInputStream {
    public static void main(String[] args) {
 
        FileInputStream fis = null;
        /**
         * JDK7 新特性【了解】  在 try 后边可以增加一个() ,在括号中定义流对象,那么这个流对象的作用域就在 try中有效,
         * try 中的代码执行完毕,会自动把流对象释放,不用写 finally。多个流用 ;号隔开
         * 例如:  try(FileInputStream fis = new FileInputStream("test.txt");
         *            FileOutputStream fos = new FileOutputStream("test.txt");){}
         *
         * JDK9 新特性【了解】
         *      在 try前边定义流对象
         *      在 try 括号中引入流对象名称 ,不用写 finally
         * 例如:
         *       FileInputStream fis = new FileInputStream("test.txt");
         *       FileOutputStream fos = new FileOutputStream("test.txt");
         *      try(fis ; fos){}
         */
        try {
            //1、创建 FileInputStream对象,参数为绑定的数据源
            fis = new FileInputStream("D:/test.txt");
            //2、读取文件 read ,当读到结尾的时候返回 -1 , 注意需要用一个变量来接收 read返回值,不要读两次
            /*int len = 0;
            while((len = fis.read())!= -1){
                System.out.println((char)len);
            };*/
            /**
             *  一次读取多个字节的方法,byte[] 起到缓冲作用,存储每次读取到的字节个数。数组的长度一般定义为1024(1kb)或整数倍
             */
            byte[] bytes = new byte[2];
            int lens = fis.read(bytes);
            System.out.println(lens);//2 每次读取字节的个数
            System.out.println(Arrays.toString(bytes)); //[97, 98]
            System.out.println(new String(bytes));//ab
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(fis != null){
                //3、关闭流
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

FileOutputStream把内存中的数据写入到硬盘的文件中

/**
 * FileOutputStream: 文件输出流,将内存数据写入到文件中
 * 构造方法: FileOutputStream(String name) 传入一个文件的路径
 *           FileOutputStream(File file)  传入一个文件
 *           FileOutputStream(String name,boolean append) 当 append为true 的时候,就不会覆盖文件,继续在文件的末尾追加写数据
 * 构造方法的作用:
 *     1、创建一个 FileOutputStream 对象
 *     2、会根据构造方法中传递的文件/文件路径,创建一个空的文件
 *     3、会把 FileOutputStream 对象指向创建好的文件
 * 写数据的原理
 *      java程序---JVM--- OS(操作系统)---将数据写入文件中
 *  写换行:换行符
 *      windows \r\n
 *      linux /n
 *      max /r
 */
public class MyOutputStream {
    public static void main(String[] args) {
        FileOutputStream  fos = null;
        {
            try {
                //1、创建一个 FileOutputStream对象
                fos = new FileOutputStream("D:/test.txt");
                /**
                 * 2、调用 FileOutputStream对象的 writer方法,将数据写入文件
                 *  会把十进制的整数,转为二进制存入硬盘.任意的记事本,打开时都会查询编码表,把字节转为字符表示。查询 ASKII码
                 *  所以 test.txt中存的是 d
                 */
                fos.write(100);
                //写出多个字节
                byte[] b = "你好".getBytes();
                fos.write(b);
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                if(fos != null){
                    //3、释放资源,流使用会占用一定的内存,因此需要释放
                    try {
                        fos.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

五、字符流

当使用字节流读取文本时,可能会有一个问题。就是遇到中文字符时,可能不会显示完整的字符,因为一个中文字符可能占用多个字节存储。所以 Java提供一些字符流,以字符为单位读写数据,专门用于处理文本文件。

字符输入流【Reader】:java.io.Reader 抽象类是读取字符流所有类的超类,可以读取字符信息到内存中。它定义了字符输入流的基本共性功能方法:

public int read()从输入流读取一个字符
public int read(char cbuf[])从输入流中读取一些字符,并将它们存储到字符数组 cbuf中
abstract public void close()关闭此流并释放与此流相关联的任何系统资源

FileReader 文件字符输入流,继承 InputStreamReader 。

/**
 * 字符输入流 Reader 输入流的最顶层父类
 * FileReader 构造方法:
 *      FileReader(String fileName)
 *      FileReader(File file)
 * 作用:
 *      1、创建一个 FileReader 对象
 *      2、会把 FileReader 对象指向要读取的文件
 *
 *
 */
public class MyReader {
    public static void main(String[] args) {
        FileReader fr = null;
        try {
            //1、创建 FileReader对象,参数中绑定读取的资源
            fr = new FileReader("test.txt");
            //2、使用 read读取当个字符并返回
            int len = 0;
            /*while((len = fr.read()) != -1){
                System.out.print((char)len); //你好!
            }*/
            //读取多个字符
            char[] cs = new char[1024];
            while((len = fr.read(cs)) != -1){
                System.out.printf(new String(cs,0,len));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(fr != null){
                //3、释放资源
                try {
                    fr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

字符输出流【Writer】:java.io.Writer 抽象类是所有输出字符流的最顶层父类。它定义了字符输入流的基本共性功能方法:

public void write(int c)写入单个字符
public void write(char cbuf[])写入字符数组
abstract public void write(char cbuf[], int off, int len)写入字符数组的某一部分
public void write(String str)写入字符串
public void write(String str, int off, int len)写入字符串的某一部分
abstract public void flush()刷新流的缓冲
abstract public void close()关闭此流,但先要刷新它

FileWriter 文件字符输出流,继承 OutputStreamReader

/**
 * FileWriter 文件字符输出流
 * 作用:把内存中的数据写入到文件中
 * 构造方法:
 *       public FileWriter(String fileName)  传入文件的路径
 *       public FileWriter(File file)  传入一个文件
 *       public FileWriter(String fileName , boolean append)  传入文件的路径   append=true 的时候不覆盖文件,在文件后面 append的输出
 *  *    public FileWriter(File file , boolean append)  传入一个文件
 * 构造方法的作用:
 *      1、会创建一个 FileWriter对象;
 *      2、会创建一个文件,根据传入的参数
 *      3、将 FileWriter对象指向创建好的文件
 */
public class MyWriter {
    public static void main(String[] args) {
        FileWriter fw = null;
        try {
            //1、创建 FileWriter对象,绑定要写入数据的目的地
            fw = new FileWriter("test.txt");
            //2、使用 writer 将数据写入到内存缓冲区中(字符转化为字节的过程)
            char[] cs = {'a','b','c'};
            fw.write(cs);
            fw.write("你好!");
            //3、使用 flush 将缓冲区中的数据刷新到文件中
            //fw.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(fw != null){
                //4、关闭流,会触发 flush方法
                try {
                    fw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

六、Properties 集合

/**
 * java.util.Properties 集合 extends Hashtable<k,v>
 *     Properties 类表示一个持久的属性集。Properties 可保存在流中或从流中加载。
 *     Properties 集合是一个唯一和 IO流相结合的集合,
 *          可以使用集合中的方法 store() ,把集合中的临时数据,持久化到硬盘中
 *                             load(), 把硬盘中的数据读取到集合中
 *     Properties 集合是一个双列集合,key 和 value 都是字符串
 *
 */
public class MyProperties {
    public static void main(String[] args) {
        // 使用 Properties 集合存储数据,遍历取出数据
        //1、创建 Properties 对象
        Properties prop = new Properties();
        //2、添加数据
        prop.setProperty("you","小三");
        prop.setProperty("mine","正房");
        prop.setProperty("she","小四");
        /**
         * 3、将数据写入到磁盘中保存
         * store(OutputStream out, String comments)
         * store(Writer writer, String comments)   可以写中文
         */
        FileReader fr = null;
        FileWriter fw = null;
        try {
            fw = new FileWriter("test.txt");
            fr = new FileReader("test.txt");
            /**
             * 文件中的内容如下:
             * #save data
             * #Sun Nov 01 16:26:13 CST 2020
             * you=小三
             * she=小四
             * mine=正房
             */
            prop.store(fw, "save data");  //第二个参数是注释
 
            /**
             * load 将文件中的数据读取:
             *     存储键值对的文件中,键与值默认的连接符号可以是 =,空格(其它符号)
             *     # 表示注释
             *     键和值默认都是字符串
             */
            Properties propRead = new Properties();
            propRead.load(fr);//将在数据到 Properties
            Set<String> strings = propRead.stringPropertyNames();//获取 key列表
            /**
             * 输入为:
             *      you = 小三
             *      she = 小四
             *      mine = 正房
             */
            for(String key: strings){
                System.out.println(key + " = " + propRead.get(key));
            }
 
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(fw != null && fr != null){
                try {
                    fw.close();
                    fr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

七、缓冲流

缓冲流也叫高效流,是对 4个基本的 Filexxx流的增强,所以也是4个流,按照数据类型分为:
【1】字节缓冲流:BufferedInputStream,BufferedOutputStream
【2】字符缓冲流:BufferedReader,BufferedWriter
缓冲流的基本原理,是在创建流对象时,会创建一个内置的默认大小的缓冲数组,通过缓冲区读写,减少系统 IO次数,从而提高效率。

字节缓冲输出流:BufferedOutputStream

/**
 *  继承 OutputStream 提高写入效率
 *  构造方法:
 *      BufferedOutputStream(OutputStream out) 创建一个新的缓冲区输出流,以将数据写入指定的底层输出流,默认缓冲区大小= 8192
 *      BufferedOutputStream(OutputStream out, int size)  创建一个新的缓冲区输出流,以将具体指定缓冲区大小的数据写入底层输出流
 */
public class MyBufferedOutputStream {
    public static void main(String[] args) {
        FileOutputStream fos = null;
        BufferedOutputStream bos = null;
        try {
            //1、创建字节输出流 FileOutputStream
            fos = new FileOutputStream("test.txt");
            //2、创建缓冲流 BufferedOutputStream
            bos = new BufferedOutputStream(fos);
            //3、使用 writer 方法写入内部缓冲区中
            bos.write("使用内部缓冲区".getBytes());
            //4、flush 将内部缓冲区的数据刷新到文件中
            bos.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(bos != null){
                //5、close 关闭流,会先执行 flush后关闭
                try {
                    bos.close(); //会自动关闭 fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

字节缓冲输入流:BufferedInputStream

/**
 * 继承 InputStream 称为 字节缓冲输入流
 * 构造方法:
 *      BufferedInputStream(InputStream in)
 *      public BufferedInputStream(InputStream in, int size)
 */
public class MyBufferedInputStream {
    public static void main(String[] args) {
        //1、创建 FileInputStream对象
        FileInputStream fis = null;
        BufferedInputStream bis = null;
        try {
            fis = new FileInputStream("test.txt");
            //2、创建 BufferedInputStream 对象,提高 FileInputStream对象读取的效率
            bis = new BufferedInputStream(fis);
            //3、使用 read方法读取数据
            int len = 0;
            byte[] bytes = new byte[1024];
            while((len = bis.read(bytes)) != -1){
                System.out.println(new String(bytes,0,len));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            //4、关闭流
            if(bis != null){
                try {
                    bis.close(); //自动关闭 fis
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

BufferedWriter :新方法 newLine() 表示换行,替换了 “\r\n" BufferedReader:新方法 readLine() 读取一个文本行,读取一行数据。如果到达流末尾,则返回 null。

八、转换流

字符编码 Character Encoding:就是一套自然语言的字符与二进制数之间的对应规则; 字符集 Charset:也叫编码表。是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等。常见字符集有 ASCII、GBK、Unicode字符集。

在这里插入图片描述

在 IDEA中,使用 FileReader读取项目中的文本文件,由于 IDEA 默认使用 UTF-8编码,所有没有任何问题。但是当读取 Windows系统中创建的文本文件时,由于 Windows系统的默认是 GBK编码,就会出现乱码。

转化流 InputStreamReader:是 Reader的子类,是从字节流到字符流的桥梁,他读取字节,并使用指定的字符集将其解码为字符,它的字符集可以由名称指定,也可以接收平台默认的字符集。

/**
 * InputStreamReader:是 Reader的子类,是从字节流到字符流的桥梁,他读取字节,并使用指定的字符集将其解码为字符,它的字符集可以由名称指定,也可以接收平台默认的字符集。
 * 构造方法:
 *      InputStreamReader(InputStream in)
 *      InputStreamReader(InputStream in, String charsetName)
 */
public class MyInputStreamReader {
    public static void main(String[] args) {
        //1、创建 InputStreamReader对象,构造方法中传入字节输入流和指定的编码表名称  指定的编码要与文件的编码相同,否则会发生乱码;
        InputStreamReader isr = null;
        try {
            isr = new InputStreamReader(new FileInputStream("text.txt"), "GBK");
            //2、使用 read 方法读取文件
            int len = 0;
            while((len = isr.read()) != -1){
                System.out.println((char)len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(isr != null){
                //3、释放资源
                try {
                    isr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

OutputStreamWriter:是 Writer的子类,是从字符流到字节流的桥梁,他写入数据,并使用指定的字符集将其编码为字符,它的字符集可以由名称指定,也可以接收平台默认的字符集。

/**
 * java.io.OutputStreamWriter 继承了 Writer
 * OutputStreamWriter 是字符流通向字节流的桥梁,可使用指定的 charset 将要写入流中的字符编码成字节。
 * 构造方法:
 *      OutputStreamWriter(OutputStream out) 使用默认的字符编码
 *      OutputStreamWriter(OutputStream out, Charset cs)  可以指定字符编码,不区分大小写
 *
 */
class MyOutputStreamWriter {
    public static void main(String[] args) {
        //1、创建一个 OutputStreamWriter 对象
        OutputStreamWriter osw = null;
        try {
            osw = new OutputStreamWriter(new FileOutputStream("text.txt"), "GBK");
            //2、使用 writer 写出数据
            osw.write("你好");
            //3、flush 刷新数据
            osw.flush();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            //4、关闭流
            if(osw != null){
                try {
                    osw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

九、序列化流

把对象以流的方式写入到文件中保存,对象中不仅仅是字符,也有字节。就有了 ObjectOutputStream[对象的序列化 writeObject§] 和 ObjectInputStream[对象的反序列化 readObject()]

ObjectOutputStream 对象序列化流:

/**
 * java.io.ObjectOutputStream extends OutputStream
 * 构造器:
 *      ObjectOutputStream(OutputStream out) 创建写入指定 OutputStream 的 ObjectOutputStream
 *      特有的成员方法:writeObject
 */
public class MyObjectOutputStream {
    public static void main(String[] args) {
        //1、创建 ObjectOutputStream 对象,构造方法中传递字节输出流
        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(new FileOutputStream("text.txt"));
            //2、通过 writeObject 把对象写入到文件中
            oos.writeObject(new Person("张三",1));//通过二进制序列化流存储的,无法打开查看。
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            //3、关闭流
            try {
                oos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

ObjectInputStream 对象反序列化流:

/**
 * java.io.ObjectInputStream 继承了 InputStream 将对象从文件中读取使用
 * 构造方法:
 *      ObjectInputStream(InputStream in) 创建从指定 InputStream 读取的 ObjectInputStream
 * 特有的方法:
 *      readObject() 返回一个 object对象
 *
 * transient 关键字:
 *       static 关键字:静态关键字,被 static修饰的成员变量不能被序列化的,序列化的都是对象的默认值。transient 与 static的作用是一样的,但却没有 static的含义
 * Person对象需要实现 Serializable 并且要定义序列化ID
 * 集合类的存储和对象相同
 */
public class MyObjectInputStream {
    public static void main(String[] args) {
        //1、创建 ObjectInputStream对象,构造方法中传递字节输入流
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new FileInputStream("text.txt"));
            //2、使用 readObject 方法读取对象
            Person o = (Person)ois.readObject();
            //4、对象使用
 
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            if(ois != null){
                //3、释放资源
                try {
                    ois.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

十、打印流

PrintStream:我们常用的 System.out.println 就是打印流。

/**
 * 可以打印各种数据值表示形式,不会抛出 IOException,有特有的方法 print println
 * 构造方法:
 *      PrintStream(File file) 输出的是一个文件
 *      PrintStream(OutputStream out) 输出的是一个字节输出流
 *      PrintStream(String fileName) 输出的是一个文件路径
 * 继承了 OutputStream 拥有该成员中的方法
 * 注意事项:如果使用继承自父类中的 write方法写数据,那么会查询编码表。如果使用 print 就会原样使用。
 * 可以改变输出语句的目的地:System.setOut() 目的地通过参数传递
 */
public class MyPrintStream {
    public static void main(String[] args) throws FileNotFoundException {
        //创建打印流,绑定要输出的目的地
        PrintStream ps = new PrintStream("text.txt");
        //使用 write 和 print写数据
        ps.write(97);//文件中为 a
        ps.print(97);//文件中为 97
        //设置输出流打印的目的地
        System.setOut(ps);
        System.out.println("此打印信息会在 text.txt文件中显示");
        //释放资源
        ps.close();
    }
}

十一、网络通信

【1】InetAddress:用于表示网络上的硬件资源,即 IP地址。没有公有的构造函数,只能通过静态方法来创建实例。

InetAddress.getByName(String host);
InetAddress.getByAddress(byte[] address);

【2】URL:统一资源定位符。可以直接从 URL中读取字节流数据。

public static void main(String[] args) throws IOException {
 
    URL url = new URL("http://www.baidu.com");
 
    /* 字节流 */
    InputStream is = url.openStream();
 
    /* 字符流 */
    InputStreamReader isr = new InputStreamReader(is, "utf-8");
 
    /* 提供缓存功能 */
    BufferedReader br = new BufferedReader(isr);
 
    String line;
    while ((line = br.readLine()) != null) {
        System.out.println(line);
    }
 
    br.close();
}

【3】Sockets:使用 TCP协议实现网络通信;ServerSocket:服务器端类,Socket:客户端类。服务器和客户端通过 InputStream 和 OutputStream 进行输入输出。

在这里插入图片描述

  • 15
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值