十、字节缓冲流、字符流、转换流、对象操作流、对象序列化流

字节缓冲流

构造方法

  • 字节缓冲流介绍

    • BufferedOutputStream:该类实现缓冲输出流.通过设置这样的输出流,应用程序可以向底层输出流写入字节,而不必为写入的每个字节导致底层系统的调用
    • BufferedInputStream:创建BufferedInputStream将创建一个内部缓冲区数组.当从流中读取或跳过字节时,内部缓冲区将根据需要从所包含的输入流中重新填充,一次很多字节
  • 构造方法:

    方法名说明
    BufferedOutputStream(OutputStream out)创建字节缓冲输出流对象
    BufferedInputStream(InputStream in)创建字节缓冲输入流对象
  • 读写和关闭流的方法 同字节输入输出流

代码

  • 用BufferedOutputStream写数据到文件
  • 用BufferedInputStream把刚才写入的文件读出来
//缓冲字节输出流对象 创建
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("module01/aaa.txt"));

//写入
bos.write(99);
bos.write(100);
bos.write(101);
//关闭
bos.close();


//缓冲字节输入流对象 创建   用来读取aaa.txt
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("module01/aaa.txt"));

//循环读取aaa.txt
int b;
while ((b = bis.read()) != -1) {
    System.out.println((char)b);
}
//关闭流
bis.close();

缓冲流原理

  • 减少了对硬盘的操作,写入数据到数组

  • 1 数组满了之后,才会自动写入到硬盘

  • 2 主动调用flush后 也会写入到硬盘 注意close方法中 已经调用了flush
    在这里插入图片描述

  • 每次从数组里读,这样操作的是内存,效率高,直到把数组内容读完,数组在去硬盘获取

在这里插入图片描述

flush方法

  • 调用flush方法,可以主动把数据写入到硬盘
  • close方法里也调用了flush方法

字节缓冲流复制一个视频练习

一次一个字节代码
    public static void main(String[] args) throws IOException {
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("module01/music/无间道.mp3"));
        //缓冲字节输出流对象 创建
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("module01/music/无间道1.mp3"));
        int b;
        while ((b = bis.read()) != -1) {
            //把读取的内容写入到无间道1.mp3
            bos.write(b);
        }
        bos.close();
        bis.close();
    }
一次一个数组代码
    public static void main(String[] args) throws IOException {
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("module01/music/无间道.mp3"));
        //缓冲字节输出流对象 创建
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("module01/music/无间道1.mp3"));
        byte[] b = new byte[1024];
        int len;
        while ((len = bis.read(b)) != -1) {
            //把读取的内容写入到无间道1.mp3
            bos.write(b, 0, len);
        }
        bos.close();
        bis.close();
    }

字符流

介绍

  • 由于字节流操作中文不是特别的方便,所以Java就提供字符流
    • 字符流 = 字节流 + 编码表
  • 中文的字节存储方式
    • 用字节流复制文本文件时,文本文件也会有中文,但是没有问题,原因是最终底层操作会自动进行字节拼接成中文,如何识别是中文的呢?
    • 汉字在存储的时候,无论选择哪种编码存储,第一个字节都是负数

编码表(了解)

  • 什么是字符集

    是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等

    l计算机要准确的存储和识别各种字符集符号,就需要进行字符编码,一套字符集必然至少有一套字符编码。常见字符集有ASCII字符集、GBXXX字符集、Unicode字符集等

  • 常见的字符集

    • ASCII字符集:

      lASCII:是基于拉丁字母的一套电脑编码系统,用于显示现代英语,主要包括控制字符(回车键、退格、换行键等)和可显示字符(英文大小写字符、阿拉伯数字和西文符号)

      基本的ASCII字符集,使用7位表示一个字符,共128字符。ASCII的扩展字符集使用8位表示一个字符,共256字符,方便支持欧洲常用字符。是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等

    • GBXXX字符集:

      GBK:最常用的中文码表。是在GB2312标准基础上的扩展规范,使用了双字节编码方案,共收录了21003个汉字,完全兼容GB2312标准,同时支持繁体汉字以及日韩汉字等

    • Unicode字符集:

      UTF-8编码:可以用来表示Unicode标准中任意字符,它是电子邮件、网页及其他存储或传送文字的应用 中,优先采用的编码。互联网工程工作小组(IETF)要求所有互联网协议都必须支持UTF-8编码。它使用一至四个字节为每个字符编码

编码规则:

  • 128个US-ASCII字符,只需一个字节编码
  • 拉丁文等字符,需要二个字节编码
  • 大部分常用字(含中文),使用三个字节编码
  • 其他极少使用的Unicode辅助字符,使用四字节编码

字符串中的编码解码问题(会使用)

  • 相关方法

    方法名说明
    byte[] getBytes()使用平台的默认字符集将该 String编码为一系列字节
    byte[] getBytes(String charsetName)使用指定的字符集将该 String编码为一系列字节
    String(byte[] bytes)使用平台的默认字符集解码指定的字节数组来创建字符串
    String(byte[] bytes, String charsetName)通过指定的字符集解码指定的字节数组来创建字符串

代码

  • encode编码 decode解码
String str01 = "我爱我的祖国";
byte[] bytes = str01.getBytes("utf-8");
System.out.println(Arrays.toString(bytes));

byte[] bytes1 = str01.getBytes("gbk");
System.out.println(Arrays.toString(bytes1));


String str02 = new String(bytes,"utf-8");
System.out.println(str02);

String str03 = new String(bytes1,"gbk");
System.out.println(str03);

idea设置编码

在这里插入图片描述

字符流类

  • Writer和Reader

  • 字节流 操作一切文件

  • 字符流 操作文本文件 操作汉字时更方便

FileWriter

  • Writer: 用于写入字符流的抽象父类
  • FileWriter: 用于写入字符流的常用子类

构造方法

方法名说明
FileWriter(File file)根据给定的 File 对象构造一个 FileWriter 对象
FileWriter(File file, boolean append)根据给定的 File 对象构造一个 FileWriter 对象
FileWriter(String fileName)根据给定的文件名构造一个 FileWriter 对象
FileWriter(String fileName, boolean append)根据给定的文件名以及指示是否附加写入数据的 boolean 值来构造 FileWriter 对象

jdk11后构造方法可以指定编码

    public static void main(String[] args) throws IOException {
        //jdk11后 支持指定编码解码方式
        //参数2指定编码为utf-8 注意语法是Charset.forName()
        FileWriter fw = new FileWriter("qqqqqq.txt", Charset.forName("utf-8"));
        fw.write("开心的文字 拆迁啦");
        fw.close();

        //参数2 指定解码方式为utf-8
        FileReader fr = new FileReader("qqqqqq.txt", Charset.forName("utf-8"));
        char[] cs = new char[1000];
        int len = fr.read(cs);
        fr.close();
        System.out.println(new String(cs, 0, len));
    }

成员方法

方法名说明
void write(int c)写一个字符
void write(char[] cbuf)写入一个字符数组
void write(char[] cbuf, int off, int len)写入字符数组的一部分
void write(String str)写一个字符串
void write(String str, int off, int len)写一个字符串的一部分

刷新和关闭的方法

方法名说明
flush()刷新流,之后还可以继续写数据
close()关闭流,释放资源,但是在关闭之前会先刷新流。一旦关闭,就不能再写数据

代码

//FileWriter fw = new FileWriter(new File("module01/ccc.txt"));
FileWriter fw = new FileWriter("module01/ccc.txt");

fw.write(100);
char[] cs = {'a', 'g', '啊', '吃'};
fw.write(cs);

fw.write("饭了,吃烤鸭,吃宫爆鸡丁,吃鱼香肉丝");

fw.write("\r\n"); //写入换行
fw.write("abcdefg", 1, 3);//跳过1个写入3个

fw.close();

FileWrite本身就有缓冲区

  • 如果write少量数据,没有flush也没有close,会发现没有写入文件
  • close方法执行后,不能继续使用流对象了

FileReader

  • Reader: 用于读取字符流的抽象父类
  • FileReader: 用于读取字符流的常用子类

构造方法

方法名说明
FileReader(File file)在给定从中读取数据的 File 的情况下创建一个新 FileReader
FileReader(String fileName)在给定从中读取数据的文件名的情况下创建一个新 FileReader

成员方法

方法名说明
int read()一次读一个字符数据
int read(char[] cbuf)一次读一个字符数组数据

代码演示

//FileReader fr = new FileReader(new File("module01/ccc.txt"));
FileReader fr = new FileReader("module01/ccc.txt");

//int a;
//while ((a = fr.read()) != -1) {
//    System.out.println(a);
//    System.out.println((char) a);
//}
//System.out.println("\r\n".toCharArray().length);

char[] cs = new char[3];
int len;
while ((len = fr.read(cs)) != -1) {
    //System.out.println(cs);
    System.out.println(new String(cs, 0, len));//转为字符串 注意要添加len长度,最后一次遍历数组可能装不满
}
fr.close();

字符流用户注册案例

  • 案例需求

    将键盘录入的用户名和密码保存到本地实现永久化存储

  • 实现步骤

    • 1获取用户输入的用户名和密码
    • 2将用户输入的用户名和密码写入到本地文件中
    • 3关流,释放资源
代码
//- 1获取用户输入的用户名和密码
//- 2将用户输入的用户名和密码写入到本地文件中
//- 3关流,释放资源
Scanner sc = new Scanner(System.in);
System.out.println("输入的用户名:");
String name = sc.next();
System.out.println("输入的密码:");
String pwd = sc.next();

try(FileWriter fw = new FileWriter("user.txt");) {
    //写入用户名
    fw.write(name);
    //换行
    fw.write("\r\n");
    //写入密码
    fw.write(pwd);
} catch (IOException e) {
    e.printStackTrace();
} 
FileOutputStream
BufferedOutputStream


FileWrite  字符流本身就有一个缓冲数组  8kb
BufferedWrite

字符缓冲流

  • 介绍

    • BufferedWriter:将文本写入字符输出流,缓冲字符,以提供单个字符,数组和字符串的高效写入,可以指定缓冲区大小,或者可以接受默认大小。默认值足够大,可用于大多数用途
    • BufferedReader:从字符输入流读取文本,缓冲字符,以提供字符,数组和行的高效读取,可以指定缓冲区大小,或者可以使用默认大小。 默认值足够大,可用于大多数用途
  • 构造方法

    方法名说明
    BufferedWriter(Writer out)创建字符缓冲输出流对象
    BufferedReader(Reader in)创建字符缓冲输入流对象

代码

  • BufferedWriter写数据
  • BufferedReader读数据
try (BufferedReader br = new BufferedReader(new FileReader("mymodule/d.txt"));
     BufferedWriter bw = new BufferedWriter(new FileWriter("mymodule/d1.txt"));) {

    //定义字符数组
    char[] cs = new char[1024];
    //定义长度
    int len = 0;
    while ((len = br.read(cs)) != -1) {
        bw.write(cs, 0, len);
    }

} catch (IOException e) {
    e.printStackTrace();
}

字符缓冲流特有功能

  • 方法介绍

    BufferedWriter:

    方法名说明
    void newLine()写一行行分隔符,行分隔符字符串由系统属性定义

    BufferedReader:

    方法名说明
    String readLine()读一行文字。 结果包含行的内容的字符串,不包括任何行终止字符如果流的结尾已经到达,则为null

代码

  • 1循环写入10次hello world到文件,每次都换行
  • 2按行读取文件内容
//    - 1循环写入10次hello world到文件,每次都换行
//创建缓冲字符写入流
try(bw = new BufferedWriter(new FileWriter("module01/xxx.txt"));){
    for (int i = 0; i < 10; i++) {
        bw.write("hello world");
        //换行
        bw.newLine();
    }

} catch (IOException e) {
    e.printStackTrace();
} 

// - 2按行读取文件内容

try( //创建缓冲字符读取流
    BufferedReader br = new BufferedReader(new FileReader("module01/xxx.txt"));) {

    //定义字符串接收读取的内容
    String line;
    //每次读取一行  内容全部读完后,如果再读 就会返回null 把循环结束
    while ((line = br.readLine()) != null) {
        System.out.println(line);
    }
} catch (IOException e) {
    e.printStackTrace();
} 

字符缓冲流操作文件中数据排序案例

需求

  • 使用字符缓冲流读取文件中的数据,排序后再次写到本地文件
  • 文件内容是 5 32 47 7 5 22 2 12 4 8 6

步骤

  • 1将文件中的数据读取到程序中
  • 2对读取到的数据进行分割转换排序
  • 3将排序后的数据写入到文件中

代码

//1 读取文件内容  按行读取
try (
    BufferedReader br = new BufferedReader(new FileReader("mymodule/ee.txt"));
) {
    String line = br.readLine();//读取数据5 32 47 7 5 22 2 12 4 8 6
    //2 把读取的字符串 按空格切割 得到一个字符串数组
    String[] ss = line.split(" ");
    System.out.println(Arrays.toString(ss));
    //把字符串的数组转为int数组
    int[] arr = new int[ss.length];
    for (int i = 0; i < ss.length; i++) {
        arr[i] = Integer.parseInt(ss[i]);
    }
    //3 对字符串数组排序
    Arrays.sort(arr);
    System.out.println(Arrays.toString(arr));


    //4 把排序完的数据写回文件
    BufferedWriter bw = new BufferedWriter(new FileWriter("mymodule/ee.txt"));
    for (int i : arr) {
        //遍历写回内容 注意加空格
        bw.write(i + " ");
    }
    //关闭流
    bw.close();
} catch (IOException e) {

}

#转换流(理解)

两个类(可以在读写时,指定编码集)

  • InputStreamReader
  • OutputStreamWriter

jdk11后使用

  • FileWriter 和FileReader

构造方法

方法名说明
InputStreamReader(InputStream in)使用默认字符编码创建InputStreamReader对象
InputStreamReader(InputStream in,String chatset)使用指定的字符编码创建InputStreamReader对象
OutputStreamWriter(OutputStream out)使用默认字符编码创建OutputStreamWriter对象
OutputStreamWriter(OutputStream out,String charset)使用指定的字符编码创建OutputStreamWriter对象

代码演示

  • 用OutputStreamWriter 写入汉字 指定编码
  • 用InputStreamReader 读取汉字 指定编码
public class ConversionStreamDemo {
    public static void main(String[] args) throws IOException {
        
        //创建写入的转换流对象 使用默认编码
        //OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("myCharStream\\osw.txt"));
        //创建写入的转换流对象  指定编码为gbk
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("myCharStream\\osw.txt"),"GBK");
        osw.write("中国");
        osw.close();

        //创建读取的转换流对象 使用默认编码
        //InputStreamReader isr = new InputStreamReader(new FileInputStream("myCharStream\\osw.txt"));
        //创建读取的转换流对象  指定编码为gbk
        InputStreamReader isr = new InputStreamReader(new FileInputStream("myCharStream\\osw.txt"),"GBK");
        //一次读取一个字符数据
        int ch;
        while ((ch=isr.read())!=-1) {
            System.out.print((char)ch);
        }
        isr.close();
    }
}

注意:jdk11后用文件读写流就可以指定编码了

    public static void main(String[] args) throws IOException {
        //jdk11后 支持指定编码解码方式
        //参数2指定编码为utf-8 注意语法是Charset.forName()
        FileWriter fw = new FileWriter("qqqqqq.txt", Charset.forName("utf-8"));
        fw.write("开心的文字 拆迁啦");
        fw.close();

        //参数2 指定解码方式为utf-8
        FileReader fr = new FileReader("qqqqqq.txt", Charset.forName("utf-8"));
        char[] cs = new char[1000];
        //读取数据到数组
        int len = fr.read(cs);
        //关闭流
        fr.close();
        System.out.println(new String(cs, 0, len));

    }

在这里插入图片描述

对象操作流

Serializable  可序列化的  形容词
Serializer    序列化器   名词
Serialize     序列化    动词


对象 -- 》 字节序列  就可以保存 在文件里  也可以在网络中传输
 新闻
class News Implements SerializableString  tilte;
Strint content;
Date  date;News n = new News();


 //创建对象输出流
 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("news.txt"));

 //把对象写入
 oos.writeObject(n);
 //关闭流
 oos.close();
 
 
 javasscript
前端收到的是新闻的字节数据  
把字节数据 转回成对象   反序列化

对象序列化流

  • 对象序列化介绍

    • 对象序列化:把对象转换为可以存储或传输的形式的过程
    • 这种机制就是使用一个字节序列表示一个对象,该字节序列包含:对象的类型、对象的数据和对象中存储的属性等信息
    • 字节序列写到文件之后,相当于文件中持久保存了一个对象的信息。反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化
  • 对象序列化流: ObjectOutputStream

    • 将Java对象的原始数据类型和图形写入OutputStream。 可以使用ObjectInputStream读取(重构)对象。 可以通过使用流的文件来实现对象的持久存储。 如果流是网络套接字流,则可以在另一个主机上或另一个进程中重构对象
  • 构造方法

    方法名说明
    ObjectOutputStream(OutputStream out)创建一个写入指定的OutputStream的ObjectOutputStream
  • 序列化对象的方法

    方法名说明
    void writeObject(Object obj)将指定的对象写入ObjectOutputStream

代码

  • 动物类
public class Anima implements Serializable {

    public String name;
    protected int age;

    public Anima() {
    }

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

    @Override
    public String toString() {
        return "Anima{" +
                "name='" + name + '\'' +
                ", age=" + age;
    }
}

测试类

public class Demo01 {
    public static void main(String[] args) throws IOException {
        //创建对象输出流
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("animal.txt"));
        //Anima类要实现Serializable接口
        Anima a = new Anima("大象", 3);
        //把对象写入
        oos.writeObject(a);
        //关闭流
        oos.close();
    }
}

注意事项

  • 一个对象要想被序列化,该对象所属的类必须必须实现Serializable 接口
  • Serializable是一个标记接口,实现该接口,不需要重写任何方法

对象反序列化流

  • 对象反序列化流: ObjectInputStream

    • ObjectInputStream反序列化先前使用ObjectOutputStream编写的原始数据和对象
  • 构造方法

    方法名说明
    ObjectInputStream(InputStream in)创建从指定的InputStream读取的ObjectInputStream
  • 反序列化对象的方法

    方法名说明
    Object readObject()从ObjectInputStream读取一个对象

代码

//创建对象输出流
ObjectInputStream oos = new ObjectInputStream(new FileInputStream("animal.txt"));

//读取出对象 然后强转为Anima对象  这个过程也叫反序列化
Anima a = (Anima) oos.readObject();
oos.close();
//打印读取的对象
System.out.println(a);

serialVersionUID&transient

  • serialVersionUID
    • 用对象序列化流序列化了一个对象后,假如我们修改了对象所属的类文件,读取数据会不会出问题呢?
      • 会出问题,会抛出InvalidClassException异常
Exception in thread "main" java.io.InvalidClassException: com.heima1.test3.Anima; local class incompatible: stream classdesc serialVersionUID = 4726774650000051461, local class serialVersionUID = -3497608134273938572
  • 如果出问题了,如何解决呢?

    • 1给对象所属的类加一个serialVersionUID

      • private static final long serialVersionUID = 42L;
    • 2重新序列化,再次测试

transient

  • 如果一个对象中的某个成员变量的值不想被序列化,又该如何实现呢
    • 给该成员变量加transient关键字修饰,该关键字标记的成员变量不参与序列化过程

代码

  • 学生类
public class Anima implements Serializable {

    public static final long serialVersionUID = 42L;

    public String name;
    public int age;

    //transient表示在序列化的时候,忽略当前的字段
    public transient int weight;


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

    public Anima() {
    }

    @Override
    public String toString() {
        return "Anima{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", weight=" + weight +
                '}';
    }
  • 序列化测试
    • Anima对象 有一个400斤的体重,然后执行writeObject保存
        //创建对象输出流
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("xiongda.txt"));

        //Anima类要实现Serializable接口
        Anima a = new Anima("熊大", 7, 400);
        //把对象写入
        oos.writeObject(a);

        oos.close();
  • 反序列化测试
    • 读取对象后打印,发现体重为0.0
    • 应为weight被transient修饰,数据没有被保存
        //创建对象输出流
        ObjectInputStream oos = new ObjectInputStream(new FileInputStream("xiongda.txt"));

        //读取出对象 然后强转为Anima对象  这个过程也叫反序列化
        Anima a = (Anima) oos.readObject();

        oos.close();
        //打印读取的对象
        System.out.println(a);//Anima{name='熊大', age=7, weight=0}

对象操作流练习

案例需求

  • 创建多个学生类对象写到文件中,再次读取到内存中

步骤

  • 1创建序列化流对象
  • 2创建多个学生对象
  • 3将学生对象添加到集合中
  • 4将集合对象序列化到文件中
  • 5创建反序列化流对象
  • 6将文件中的对象数据,读取到内存中

代码实现

  • 学生类
public class Student implements Serializable{
    
    private static final long serialVersionUID = 2L;

    private String name;
    private int age;

    public Student() {
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
  • 测试类
public class Demo01 {
    public static void main(String[] args) {
        //创建多个学生类对象写到文件中,再次读取到内存中

        ObjectOutputStream oos = null;
        ObjectInputStream ois = null;
        try {
            //- 1创建序列化流对象
            oos = new ObjectOutputStream(new FileOutputStream("module01/stu.data"));
            //- 2创建多个学生对象
            Student stu01 = new Student("xiaoming", 20);
            Student stu02 = new Student("xiaohong", 21);
            Student stu03 = new Student("pgo", 30);
            //- 3将学生对象添加到集合中
            ArrayList<Student> stus = new ArrayList<>();
            stus.add(stu01);
            stus.add(stu02);
            stus.add(stu03);

            //- 4将集合对象序列化到文件中
            oos.writeObject(stus);
            //- 5创建反序列化流对象
            ois = new ObjectInputStream(new FileInputStream("module01/stu.data"));
            //- 6将文件中的对象数据,读取到内存中
            ArrayList<Student> ss = (ArrayList<Student>) ois.readObject();
            System.out.println(ss);

        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("读写失败");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            System.out.println("找不到class");
        } finally {
            if (oos != null) {
                try {
                    oos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (ois != null) {
                try {
                    ois.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

小结

字节流
FileOutputStream  FileInputStream   read  write
BufferedOutputStream  BufferedInputStream

字符流
FileReader  FileWriter
BufferedReader  readLine  BufferedWriter  newLine

转换流
InputStreamReader  OutputStreamWriter

对象流
ObjectInputStream  ObjectOutputStream
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值