Java中的I/O

目录

1.I/O流

1.1I/O概述

 1.2.基本用法

 1.3.字节输出流写数据的细节

 1.4.FileOutPutStream写数据的三种方式

1.5. 换行和续写 

1.6.字节输入流的基本操作

​1.7.字节输入流循环读取

1.8.文件拷贝

1.9. I/O流中不同JDK版本捕获异常的方式

2.字符集详解 

2.1.字符集的来历

 2.2.字和字母的编码特点

 2.3.Unicode

​ 2.4.乱码​编辑

2.5.Java中的编码和解码

3.字符流

3.1.FileReader类

3.2.FileWriter类

3.3. FileWriter写的注意事项

3.4.字符输入流底层原理

3.5.字符输出流底层原理

4.缓冲流

4.1.缓冲字节流

4.2.字符缓冲流

5.转换流

5.1.InputStreamReader类

5.2.OutputStreamWriter类

6.打印流

6.1.打印流基本使用

6.2.重定向输出语句

7.数据流

 7.1.DataOutputStream类

7.2. DataInputStream类

8.序列化流

 8.1.ObjectOutputStraem类

 8.2.ObjectInputStream类

9.I/O框架


1.I/O流

1.1I/O概述

 1.2.基本用法

 1.3.字节输出流写数据的细节

 1.4.FileOutPutStream写数据的三种方式

1.5. 换行和续写 

 

1.6.字节输入流的基本操作

 1.7.字节输入流循环读取

1.8.文件拷贝

 

 

1.9. I/O流中不同JDK版本捕获异常的方式

 

2.字符集详解 

2.1.字符集的来历

我们知道计算机是美国人发明的,由于计算机能够处理的数据只能是0和1组成的二进制数据,为了让计算机能够处理字符,于是美国人就把他们会用到的每一个字符进行了编码(**所谓编码,就是为一个字符编一个二进制数据**),如下图所示:

美国人常用的字符有英文字母、标点符号、数字以及一些特殊字符,这些字符一共也不到128个,所以他们用1个字节来存储1字符就够了。 美国人把他们用到的字符和字符对应的编码总结成了一张码表,这张码表叫做ASCII码表(也叫ASCII字符集)。  

 

 2.2.字和字母的编码特点

需要我们注意汉字和字母的编码特点:

  • 如果是存储字母,采用1个字节来存储,一共8位,其中第1位是0
  • 如果是存储汉字,采用2个字节来存储,一共16位,其中第1位是1

 

 2.3.Unicode

为了解决各个国家字符集互不兼容的问题,由国际化标准组织牵头,设计了一套全世界通用的字符集,叫做Unicode字符集。在Unicode字符集中包含了世界上所有国家的文字,一个字符采用4个自己才存储。在Unicode字符集中,采用一个字符4个字节的编码方案,又造成另一个问题:如果是说英语的国家,他们只需要用到26大小写字母,加上一些标点符号就够了,本身一个字节就可以表示完,用4个字节就有点浪费。于是又对Unicode字符集中的字符进行了重新编码,一共设计了三种编码方案。分别是UTF-32、UTF-16、UTF-8; 其中比较常用的编码方案是UTF-8.

 

 

 2.4.乱码

 第二,采用不同的编码解码方式。

拓展:字节流读取中文会乱码,但是为什么 拷贝不会乱码呢?

2.5.Java中的编码和解码

3.字符流

使用字节流可以读取文件中的字节数据。但是如果文件中有中文,使用字节流来读取,就有可能读到半个汉字的情况,这样会导致乱码。虽然使用读取全部字节的方法不会出现乱码,但是如果文件过大又不太合适。

所以Java专门为我们提供了另外一种流,叫字符流,字符流是专门为读取文本数据而生的。

3.1.FileReader类

先来学习字符流中的FileReader类,这是字符输入流,用来将文件中的字符数据读取到程序中来

FileReader读取文件的步骤如下:  

第一步:创建FileReader对象与要读取的源文件接通
第二步:调用read()方法读取文件中的字符
第三步:调用close()方法关闭流

 需要用到的方法:先通过构造器创建对象,再通过read方法读取数据(注意:两个read方法的返回值,含义不一样

/**
 * 目标:掌握文件字符输入流。
 */
public class FileReaderTest1 {
    public static void main(String[] args)  {
        try (
                // 1、创建一个文件字符输入流管道与源文件接通
                Reader fr = new FileReader("io-app2\\src\\itheima01.txt");
                ){
            // 2、一个字符一个字符的读(性能较差)
//            int c; // 记住每次读取的字符编号。
//            while ((c = fr.read()) != -1){
//                System.out.print((char) c);
//            }
            // 每次读取一个字符的形式,性能肯定是比较差的。

            // 3、每次读取多个字符。(性能是比较不错的!)
            char[] buffer = new char[3];
            int len; // 记住每次读取了多少个字符。
            while ((len = fr.read(buffer)) != -1){
                // 读取多少倒出多少
                System.out.print(new String(buffer, 0, len));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 3.2.FileWriter类

FileWriter往文件中写字符数据的步骤如下:

第一步:创建FileWirter对象与要读取的目标文件接通
第二步:调用write(字符数据/字符数组/字符串)方法读取文件中的字符
第三步:调用close()方法关闭流

需要用到的方法如下:构造器是用来创建FileWriter对象的,有了对象才能调用write方法写数据到文件。

 接下来,用代码演示一下:

/**
 * 目标:掌握文件字符输出流:写字符数据出去
 */
public class FileWriterTest2 {
    public static void main(String[] args) {
        try (
                // 0、创建一个文件字符输出流管道与目标文件接通。
                // 覆盖管道
                // Writer fw = new FileWriter("io-app2/src/itheima02out.txt");
                // 追加数据的管道
                Writer fw = new FileWriter("io-app2/src/itheima02out.txt", true);
                ){
            // 1、public void write(int c):写一个字符出去
            fw.write('a');
            fw.write(97);
            //fw.write('磊'); // 写一个字符出去
            fw.write("\r\n"); // 换行

            // 2、public void write(String c)写一个字符串出去
            fw.write("我爱你中国abc");
            fw.write("\r\n");

            // 3、public void write(String c ,int pos ,int len):写字符串的一部分出去
            fw.write("我爱你中国abc", 0, 5);
            fw.write("\r\n");

            // 4、public void write(char[] buffer):写一个字符数组出去
            char[] buffer = {'黑', '马', 'a', 'b', 'c'};
            fw.write(buffer);
            fw.write("\r\n");

            // 5、public void write(char[] buffer ,int pos ,int len):写字符数组的一部分出去
            fw.write(buffer, 0, 2);
            fw.write("\r\n");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

3.3. FileWriter写的注意事项

FileWriter写完数据之后,必须刷新或者关闭,写出去的数据才能生效。比如:下面的代码只调用了写数据的方法,没有关流的方法。当你打开目标文件时,是看不到任何数据的。

//1.创建FileWriter对象
Writer fw = new FileWriter("io-app2/src/itheima03out.txt");

//2.写字符数据出去
fw.write('a');
fw.write('b');
fw.write('c');

而下面的代码,加上了flush()方法之后,数据就会立即到目标文件中去。

//1.创建FileWriter对象
Writer fw = new FileWriter("io-app2/src/itheima03out.txt");

//2.写字符数据出去
fw.write('a');
fw.write('b');
fw.write('c');

//3.刷新
fw.flush(); 

下面的代码,调用了close()方法,数据也会立即到文件中去。因为close()方法在关闭流之前,会将内存中缓存的数据先刷新到文件,再关流。

//1.创建FileWriter对象
Writer fw = new FileWriter("io-app2/src/itheima03out.txt");

//2.写字符数据出去
fw.write('a');
fw.write('b');
fw.write('c');

//3.关闭流
fw.close(); //会先刷新,再关流

但是需要注意的是,关闭流之后,就不能在对流进行操作了。否则会出异常

 3.4.字符输入流底层原理

 需要注意的是:字节输入流是没有缓冲区的!!

(1)如果文件中的数据超过8192字节(比如说有8193字节),那么在读取8193的时候,会把第8193字节的数据加载到缓冲区的缓冲数组当中,注意:第8193字节会覆盖缓冲数组里面第一个位置的数据。

(2)

3.5.字符输出流底层原理

4.缓冲流

缓冲流有四种,如下图所示:

 缓冲流的作用可以对原始流进行包装,提高原始流读写数据的性能。

4.1.缓冲字节流

先来学习字节缓冲流是如何提高读写数据的性能的,原理如下图所示。是因为在缓冲流的底层自己封装了一个长度为8KB(8129byte)的字节数组但是缓冲流不能单独使用,它需要依赖于原始流。

  • 读数据时:它先用原始字节输入流一次性读取8KB的数据存入缓冲流内部的数组中(ps: 先一次多囤点货),再从8KB的字节数组中读取一个字节或者多个字节(把消耗屯的货)。

    • 写数据时: 它是先把数据写到缓冲流内部的8KB的数组中(ps: 先攒一车货),等数组存满了,再通过原始的字节输出流,一次性写到目标文件中去(把囤好的货,一次性运走)。

    在创建缓冲字节流对象时,需要封装一个原始流对象进来。构造方法如下:

     如果我们用缓冲流复制文件,代码写法如下:

    public class BufferedInputStreamTest1 {
        public static void main(String[] args) {
            try (
                    InputStream is = new FileInputStream("io-app2/src/itheima01.txt");
                    // 1、定义一个字节缓冲输入流包装原始的字节输入流
                    InputStream bis = new BufferedInputStream(is);
    
                    OutputStream os = new FileOutputStream("io-app2/src/itheima01_bak.txt");
                    // 2、定义一个字节缓冲输出流包装原始的字节输出流
                    OutputStream bos = new BufferedOutputStream(os);
            ){
    
                byte[] buffer = new byte[1024];
                int len;
                while ((len = bis.read(buffer)) != -1){
                    bos.write(buffer, 0, len);
                }
                System.out.println("复制完成!!");
    
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    4.2.字符缓冲流

    它的原理和字节缓冲流是类似的,它底层也会有一个8KB的数组,但是这里是字符数组。字符缓冲流也不能单独使用,它需要依赖于原始字符流一起使用。

    • BufferedReader读数据时:它先原始字符输入流一次性读取8KB的数据存入缓冲流内部的数组中(ps: 先一次多囤点货),再从8KB的字符数组中读取一个字符或者多个字符(把消耗屯的货)。

    创建BufferedReader对象需要用到BufferedReader的构造方法,内部需要封装一个原始的字符输入流,我们可以传入FileReader.  

     而且BufferedReader还有特有的方法,一次可以读取文本文件中的一行:

     

    使用BufferedReader读取数据的代码如下:

    public class BufferedReaderTest2 {
        public static void main(String[] args)  {
            try (
                    Reader fr = new FileReader("io-app2\\src\\itheima04.txt");
                    // 创建一个字符缓冲输入流包装原始的字符输入流
                    BufferedReader br = new BufferedReader(fr);
            ){
    //            char[] buffer = new char[3];
    //            int len;
    //            while ((len = br.read(buffer)) != -1){
    //                System.out.print(new String(buffer, 0, len));
    //            }
    //            System.out.println(br.readLine());
    //            System.out.println(br.readLine());
    //            System.out.println(br.readLine());
    //            System.out.println(br.readLine());
    
                String line; // 记住每次读取的一行数据
                while ((line = br.readLine()) != null){
                    System.out.println(line);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    • BufferedWriter写数据时: 它是先把数据写到字符缓冲流内部的8KB的数组中(ps: 先攒一车货),等数组存满了,再通过原始的字符输出流,一次性写到目标文件中去(把囤好的货,一次性运走)。

    创建BufferedWriter对象时需要用到BufferedWriter的构造方法,而且内部需要封装一个原始的字符输出流,我们这里可以传递FileWriter。

    而且BufferedWriter新增了一个功能,可以用来写一个换行符  

    接下来,用代码演示一下,使用BufferedWriter往文件中写入字符数据。  

    public class BufferedWriterTest3 {
        public static void main(String[] args) {
            try (
                    Writer fw = new FileWriter("io-app2/src/itheima05out.txt", true);
                    // 创建一个字符缓冲输出流管道包装原始的字符输出流
                    BufferedWriter bw = new BufferedWriter(fw);
            ){
    
                bw.write('a');
                bw.write(97);
                bw.write('磊');
                bw.newLine();
    
                bw.write("我爱你中国abc");
                bw.newLine();
    
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    5.转换流

    前面我们学习过FileReader读取文件中的字符,但是注意了,FileReader默认只能读取UTF-8编码格式的文件。如果使用FileReader读取GBK格式的文件,可能存在乱码,因为FileReader它遇到汉字默认是按照3个字节来读取的,而GBK格式的文件一个汉字是占2个字节,这样就会导致乱码。

    Java给我们提供了另外两种流InputStreamReader,OutputStreamWriter,这两个流我们把它叫做转换流。它们可以将字节流转换为字符流,并且可以指定编码方案

    5.1.InputStreamReader类

    这个类名就比较有意思,前面是InputStream表示字节输入流,后面是Reader表示字符输入流,合在一起意思就是表示可以把InputStream转换为Reader,最终InputStreamReader其实也是Reader的子类,所以也算是字符输入流。

    InputStreamReader也是不能单独使用的,它内部需要封装一个InputStream的子类对象,再指定一个编码表,如果不指定编码表,默认会按照UTF-8形式进行转换。

    需求:我们可以先准备一个GBK格式的文件,然后使用下面的代码进行读取,看是是否有乱码。

     

    public class InputStreamReaderTest2 {
        public static void main(String[] args) {
            try (
                    // 1、得到文件的原始字节流(GBK的字节流形式)
                    InputStream is = new FileInputStream("io-app2/src/itheima06.txt");
                    // 2、把原始的字节输入流按照指定的字符集编码转换成字符输入流
                    Reader isr = new InputStreamReader(is, "GBK");
                    // 3、把字符输入流包装成缓冲字符输入流
                    BufferedReader br = new BufferedReader(isr);
                    ){
                String line;
                while ((line = br.readLine()) != null){
                    System.out.println(line);
                }
    
    
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    执行完之后,你会发现没有乱码。

    5.2.OutputStreamWriter类

    你看这个类名也比较有意思,前面是OutputStream表示字节输出流,后面是Writer表示字符输出流,合在一起意思就是表示可以把OutputStream转换为Writer,最终OutputStreamWriter其实也是Writer的子类,所以也算是字符输出流。

    OutputStreamReader也是不能单独使用的,它内部需要封装一个OutputStream的子类对象,再指定一个编码表,如果不指定编码表,默认会按照UTF-8形式进行转换

    需求:我们可以先准备一个GBK格式的文件,使用下面代码往文件中写字符数据。

    public class OutputStreamWriterTest3 {
        public static void main(String[] args) {
            // 指定写出去的字符编码。
            try (
                    // 1、创建一个文件字节输出流
                    OutputStream os = new FileOutputStream("io-app2/src/itheima07out.txt");
                    // 2、把原始的字节输出流,按照指定的字符集编码转换成字符输出转换流。
                    Writer osw = new OutputStreamWriter(os, "GBK");
                    // 3、把字符输出流包装成缓冲字符输出流
                    BufferedWriter bw = new BufferedWriter(osw);
                    ){
                bw.write("我是中国人abc");
                bw.write("我爱你中国123");
    
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    6.打印流

    6.1.打印流基本使用

    打印流,这里所说的打印其实就是写数据的意思,它和普通的write方法写数据还不太一样,一般会使用打印流特有的方法叫print(数据)或者println(数据),它打印啥就输出啥。打印流有两个,一个是字节打印流PrintStream,一个是字符打印流PrintWriter,如下图所示:

    PrintStream和PrintWriter的用法是一样的,所以这里就一块演示了。

    public class PrintTest1 {
        public static void main(String[] args) {
            try (
                    // 1、创建一个打印流管道
    //                PrintStream ps =
    //                        new PrintStream("io-app2/src/itheima08.txt", Charset.forName("GBK"));
    //                PrintStream ps =
    //                        new PrintStream("io-app2/src/itheima08.txt");
                    PrintWriter ps =
                            new PrintWriter(new FileOutputStream("io-app2/src/itheima08.txt", true));
                    ){
                    ps.print(97);	//文件中显示的就是:97
                    ps.print('a'); //文件中显示的就是:a
                    ps.println("我爱你中国abc");	//文件中显示的就是:我爱你中国abc
                    ps.println(true);//文件中显示的就是:true
                    ps.println(99.5);//文件中显示的就是99.5
    
                    ps.write(97); //文件中显示a,发现和前面println方法的区别了吗?
    
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

     

    6.2.重定向输出语句

    System里面有一个静态变量叫out,out的数据类型就是PrintStream,它就是一个打印流,而且这个打印流的默认输出目的地是控制台,所以我们调用System.out.pirnln()就可以往控制台打印输出任意类型的数据,而且打印啥就输出啥。而且System还提供了一个方法,可以修改底层的打印流,这样我们就可以重定向打印语句的输出目的地了。我们玩一下, 直接上代码。

    public class PrintTest2 {
        public static void main(String[] args) {
            System.out.println("老骥伏枥");
            System.out.println("志在千里");
    
            try ( PrintStream ps = new PrintStream("io-app2/src/itheima09.txt"); ){
                // 把系统默认的打印流对象改成自己设置的打印流
                System.setOut(ps);
    
                System.out.println("烈士暮年");	
                System.out.println("壮心不已");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    此时打印语句,将往文件中打印数据,而不在控制台。

    7.数据流

    接下我们再学习一种流,这种流在开发中偶尔也会用到。比如,我们想把数据和数据的类型一并写到文件中去,读取的时候也将数据和数据类型一并读出来。这就可以用到数据流,有两个DataInputStream和DataOutputStream.

     7.1.DataOutputStream类

    创建DataOutputStream对象时,底层需要依赖于一个原始的OutputStream流对象。然后调用它的wirteXxx方法,写的是特定类型的数据。

     代码如下:读取文件中特定类型的数据(整数、小数、字符串等)

    public class DataOutputStreamTest1 {
        public static void main(String[] args) {
            try (
                    // 1、创建一个数据输出流包装低级的字节输出流
                    DataOutputStream dos =
                            new DataOutputStream(new FileOutputStream("io-app2/src/itheima10out.txt"));
                    ){
                dos.writeInt(97);
                dos.writeDouble(99.5);
                dos.writeBoolean(true);
                dos.writeUTF("黑马程序员666!");
    
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    7.2. DataInputStream类

    学习完DataOutputStream后,再学习DataIntputStream类,它也是一种包装流,创建DataInputStream对象时,底层需要依赖于一个原始的InputStream流对象。然后调用它的readXxx()方法就可以读取特定类型的数据。

    代码如下:读取文件中特定类型的数据(整数、小数、字符串等)  

    public class DataInputStreamTest2 {
        public static void main(String[] args) {
            try (
                    DataInputStream dis =
                            new DataInputStream(new FileInputStream("io-app2/src/itheima10out.txt"));
                    ){
                int i = dis.readInt();
                System.out.println(i);
    
                double d = dis.readDouble();
                System.out.println(d);
    
                boolean b = dis.readBoolean();
                System.out.println(b);
    
                String rs = dis.readUTF();
                System.out.println(rs);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    8.序列化流

    序列化流是干什么用的呢? 我们知道字节流是以字节为单位来读写数据、字符流是按照字符为单位来读写数据、而对象流是以对象为单位来读写数据。也就是把对象当做一个整体,可以写一个对象到文件,也可以从文件中把对象读取出来。

    这里有一个新词 序列化,第一次听可能还比较陌生,来解释一下:

    序列化:意思就是把对象写到文件或者网络中去。(简单记:写对象)
    反序列化:意思就是把对象从文件或者网络中读取出来。(简单记:读对象)

     8.1.ObjectOutputStraem类

    接下来,先学习ObjectOutputStream流,它也是一个包装流,不能单独使用,需要结合原始的字节输出流使用。

    代码如下:将一个User对象写到文件中去

    • 第一步:先准备一个User类,必须让其实现Serializable接口

    // 注意:对象如果需要序列化,必须实现序列化接口。
    public class User implements Serializable {
        private String loginName;
        private String userName;
        private int age;
        // transient 这个成员变量将不参与序列化。
        private transient String passWord;
    
        public User() {
        }
    
        public User(String loginName, String userName, int age, String passWord) {
            this.loginName = loginName;
            this.userName = userName;
            this.age = age;
            this.passWord = passWord;
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "loginName='" + loginName + '\'' +
                    ", userName='" + userName + '\'' +
                    ", age=" + age +
                    ", passWord='" + passWord + '\'' +
                    '}';
        }
    }
    • 第二步:再创建ObjectOutputStream流对象,调用writeObject方法对象到文件。

    public class Test1ObjectOutputStream {
        public static void main(String[] args) {
            try (
                    // 2、创建一个对象字节输出流包装原始的字节 输出流。
                    ObjectOutputStream oos =
                            new ObjectOutputStream(new FileOutputStream("io-app2/src/itheima11out.txt"));
                    ){
                // 1、创建一个Java对象。
                User u = new User("admin", "张三", 32, "666888xyz");
    
                // 3、序列化对象到文件中去
                oos.writeObject(u);
                System.out.println("序列化对象成功!!");
    
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    注意:写到文件中的对象,是不能用记事本打开看的。因为对象本身就不是文本数据,打开是乱码 的

     8.2.ObjectInputStream类

    接下来,学习ObjectInputStream流,它也是一个包装流,不能单独使用,需要结合原始的字节输入流使用。接着前面的案例,文件中已经有一个Student对象,现在要使用ObjectInputStream读取出来。称之为反序列化。

    public class Test2ObjectInputStream {
        public static void main(String[] args) {
            try (
                // 1、创建一个对象字节输入流管道,包装 低级的字节输入流与源文件接通
                ObjectInputStream ois = new ObjectInputStream(new FileInputStream("io-app2/src/itheima11out.txt"));
            ){
                User u = (User) ois.readObject();
                System.out.println(u);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    9.I/O框架

    apache开源基金组织提供了一组有关IO流小框架,可以提高IO流的开发效率。这个框架的名字叫commons-io:其本质是别人写好的一些字节码文件(class文件),打包成了一个jar包。我们只需要把jar包引入到我们的项目中,就可以直接用了。这里介绍一个jar包中提供的工具类叫FileUtils,它的部分功能如下,很方便,你一看名字就知道怎么用了。

     在写代码之前,先需要引入jar包,具体步骤如下:

    1.在模块的目录下,新建一个lib文件夹
    2.把jar包复制粘贴到lib文件夹下
    3.选择lib下的jar包,右键点击Add As Library,然后就可以用了。

    public class CommonsIOTest1 {
        public static void main(String[] args) throws Exception {
            //1.复制文件
            FileUtils.copyFile(new File("io-app2\\src\\itheima01.txt"), new File("io-app2/src/a.txt"));
            
            //2.复制文件夹
            FileUtils.copyDirectory(new File("D:\\resource\\私人珍藏"), new File("D:\\resource\\私人珍藏3"));
            
            //3.删除文件夹
            FileUtils.deleteDirectory(new File("D:\\resource\\私人珍藏3"));
    
            // Java提供的原生的一行代码搞定很多事情
             Files.copy(Path.of("io-app2\\src\\itheima01.txt"), Path.of("io-app2\\src\\b.txt"));
            System.out.println(Files.readString(Path.of("io-app2\\src\\itheima01.txt")));
        }
    }

    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

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

    抵扣说明:

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

    余额充值