Java基础Day21

day21

  1. 内存输出流

  2. ByteArrayOutputStream : 内存输出流
    案例 : 使用字节输入流读取了带有中文纯文本文件,一边读还将读到的结果打印出来,出现问题,将中文的两个字节拆分开,于是出现了乱码
    解决方法 : 字符流读取文件 ; 内存输出流

  3. 内存输出流的作用 :
    如果一个流在进行字节内容读取时,没有读完,需要一个字节数组容器,将没读完的先放到一个容器中,等到都读完,一起查看结果, 于是内存输出流就是这样一个字节数组容器

  4. 内存输出流实质: 就是一个大小可变的字节数组容器,专门用于承装IO流读取到的字节数据,之后将所有的字节数据显示出来.内存输出流与文件没有交互

  5. 内存输出流的实现

  1. 创建一个内存输出流的对象,需要构造方法,空参数构造即可
  2. ByteArrayOutputStream 也具有写的功能,存储到数组中
  3. toByteArray() : 将内存输出流的字节数组内容获取到,相当于查看了文件内容
  4. toString() : 将字节数组使用平台默认的编码表,转换成字符串,返回值类型String

代码
package com.zgjy.otherStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
public class ByteArrayOutputStreamDemo {

public static void main(String[] args) throws IOException{
	// 1. 使用字节输入流读取带有中文纯文本
	FileInputStream fis = new FileInputStream("chinese.txt");
	// 3. 创建一个内存输出流的容器
	ByteArrayOutputStream  baos = new ByteArrayOutputStream();
	
	// 2. 按照一个字节一个字节的读取中文内容
	int len;
	while((len = fis.read()) != -1) {
		// 4. 向容器中添加字节
		baos.write(len);	
	}
	
	//5. 所有内容读取完毕,查看一下容器中的数据内容
	// 先获取到容器
	byte[] b = baos.toByteArray();
	System.out.println(new String(b));
	// 7. toString(): 将内存输出流中的字节数组内容转化成字符串
	System.out.println(baos.toString());
	
	// 6. 关闭资源
	fis.close();
}

}

  1. 对象操作流(掌握)
    对象操作流的功能 : 将内存中的对象以及对象中的数据写入到文件中 ; 还能将文件中的对象数据信息获取到程序中;

使用原因 :
目前为止,在代码中所有的数据,都不能做持久化存储,例如 : 数据库,会将代码中发生的一些有用数据,存储下来, 那么对象的操作流,可以暂时做数据的持久化的存储,将对象的数据写入文件中,文件存储在磁盘上,只要文件不删除,这些对象中的数据就能长久存在

对象输出流 : 内存中的对象以及对象中的数据写入到文件中,ObjectOutputStream
对象输入流 : 能将文件中的对象数据信息获取到程序中,ObjectInputStream

2.1 对象输出流ObjectOutputStream

  1. ObjectOutputStream : 是OutputStream的子类,主要关注将对象写入文件中,
    将对象写入到文件中的过程,称为对象的序列化过程

  2. ObjectOutputStream 构造方法
    ObjectOutputStream (OutputStream out) : 将一个普通的字节输出流,包装成一个对象的输出流,依靠OutputStream 将对象数据写入到文件中.

  3. 写入对象的方法:
    writeObject(Object obj): 将obj对象写入到指定的文件中

  4. 将自定义的Person类对象,写入到文件中,报错,java.io.NotSerializableException ,没有序列化异常
    发生异常原因: 因为将Person对象写入到文件的过程就叫做序列化过程,因此要求对象所属的类型必须是可以序列化的类型,需要Person实现一个接口,java.io.Serializable

  5. java.io.Serializable 接口 : 表示是一个序列化的接口
    类通过实现 java.io.Serializable 接口以启用序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化

  1. Serializable接口中定义没有任何方法,于是将没有任何方法的接口Serializable称为一个标志性接口
    现在超市卖猪肉,一般猪肉上面都有一个戳,表示检疫合格,看到戳猪肉放心购买,这个戳就是一个标志,类比理解Serializable
  1. 序列化的文件,文件中的对象内容,人类看不懂,主要关注可以通过对象的输入流,将对象的内容获取到
    序列化类型Person定义代码
    package com.zgjy.objectStream;
    import java.io.Serializable;
    public class Person implements Serializable{
    private String name;
    private int 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 Person(String name, int age) {
    super();
    this.name = name;
    this.age = age;
    }
    public Person() {
    super();
    // TODO Auto-generated constructor stub
    }
    }

序列化过程代码:
package com.zgjy.objectStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class ObjecOutputStreamDemo {
// 向文件中写入Person对象
public static void main(String[] args) throws IOException{
// 1.创建一个ObjectOutputStream对象
FileOutputStream fos = new FileOutputStream(“person.txt”);
ObjectOutputStream oos = new ObjectOutputStream(fos);
// 2. 向person.txt写入对象
//java.io.NotSerializableException: com.zgjy.objectStream.Person
Person p = new Person(“二麻子”, 20);
oos.writeObject§;

	oos.close();
}

}

2.2 对象输入流ObjectInputStream
ObjectInputStream : 来自于java.io包.是InputStream的子类
功能:
ObjectInputStream 对以前使用 ObjectOutputStream 写入的基本数据和对象进行反序列化
反序列化 : 将对象从文件中读取出来的过程就是反序列化

  1. ObjectInputStream 构造方法:
    ObjectInputStream(InputStream in) : 将一个普通的字节输入流封装成一个对象的输入流,可以读取文件中的对象数据,通过InputStream读
  2. 读取对象的方法:
    readObject() : 将文件中的对象读取出来,返回值Object类型,方法每次只能读取一个对象

代码
package com.zgjy.objectStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
public class ObjectInputStreamDemo {
public static void main(String[] args) throws IOException, ClassNotFoundException{
// 1. 确定从哪里读取序列化的文件,person.txt
FileInputStream fis = new FileInputStream(“person.txt”);
ObjectInputStream ois = new ObjectInputStream(fis);

	// 2. 通过readObject方法,从person.txt文件中读取对象
	// 方法每次从文件中读取出一个对象
	Object obj = ois.readObject();
	
	// 3. 看一下获取到的对象内容
	// 将Object类型的对象转换成Person对象,多态的向下转型
	Person p = (Person)obj;
	System.out.println(p.getName() + p.getAge());
	
	ois.close();
}

}

  1. 对象输入流读取文件的异常
    java.io.EOFException
    EOFException : End Of File Exception 文件到末尾的异常
    发生异常的原因是 : 文件中已经没有对象了,还在继续使用readobject()获取对象,但是目前并不知道文件中的对象个数有多少,代码需要优化,参见2.3

代码
package com.zgjy.objectStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
public class ObjectInputStreamDemo {
public static void main(String[] args) throws IOException, ClassNotFoundException{
// 1. 确定从哪里读取序列化的文件,person.txt
FileInputStream fis = new FileInputStream(“person.txt”);
ObjectInputStream ois = new ObjectInputStream(fis);

	// 2. 通过readObject方法,从person.txt文件中读取对象
	// 方法每次从文件中读取出一个对象
	Object obj = ois.readObject();
	Object obj1 = ois.readObject();
	Object obj2 = ois.readObject();
	// java.io.EOFException 
	// EOFException : End Of File Exception 文件到末尾的异常
	// obj3发生异常的原因是 : 文件中已经没有对象了,还在继续获取对象,抛异常
	Object obj3 = ois.readObject();
	
	// 3. 看一下获取到的对象内容
	// 将Object类型的对象转换成Person对象,多态的向下转型
	Person p = (Person)obj;
	System.out.println(p.getName() + p.getAge());
	
	Person p1 = (Person)obj1;
	System.out.println(p1.getName() + p1.getAge());
	
	Person p2 = (Person)obj2;
	System.out.println(p2.getName() + p2.getAge());
	
	Person p3 = (Person)obj3;
	System.out.println(p3.getName() + p3.getAge());
	
	ois.close();
}

}

2.3优化序列化的过程
如果向文件中存储对象,可以向文件中写入多个对象,但是读取文件中的对象时,不知道文件中对象个数是多少,于是读取可能有问题,多读就会报出EOFException

优化思路:
创建多个对象,将多个对象放置到一个ArrayList容器中, 然后将容器对象写入到文件中
,后期再进行对象读取时,只需要读一次,读到了ArrayList容器,遍历容器中的内容,将所有对象数据获取到

代码
package com.zgjy.objectStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
public class ObjectYouHua {

public static void main(String[] args) throws Exception{
	writeObject();
	readObject();
}

// 向文件中写入容器对象,容器中橙装了很多其他对象内容
public static void writeObject() throws IOException{
	// 1. 创建一个ObjectOutputStream
	FileOutputStream fos = new FileOutputStream("personArr.txt");
	ObjectOutputStream oos = new ObjectOutputStream(fos);
	
	// 2.创建一个容器
	ArrayList<Person> list = new ArrayList<>();
	// 3. 向容器中添加Person对象数据
	list.add(new Person("张三", 15));
	list.add(new Person("李四", 16));
	list.add(new Person("海岛", 17));
	list.add(new Person("QQ", 18));
	
	//4. 向文件中写入容器对象
	oos.writeObject(list);
	
	// 5. 关闭资源
	oos.close();
}

// 将文件中的容器对象读出来
public static void readObject() throws IOException, ClassNotFoundException{
	// 1.创建一个ObjectInputStream
	FileInputStream fis = new FileInputStream("personArr.txt");
	ObjectInputStream ois = new ObjectInputStream(fis);
	// 2. 从personArr.txt读取出容器对象
	Object obj= ois.readObject();
	// 3. 将obj转换 成ArrayList类型,多台向下转型
	ArrayList<Person> list = (ArrayList<Person>)obj;
	// 4. 遍历集合,获取到集合中的每一个Person对象内容
	for(Person p : list) {
		System.out.println(p.getName() + "---"+p.getAge());
	}
	ois.close();
}

}

2.4序列号ID冲突问题

  1. 目前.已经把Person对象写入到文件中,随着需求的推进,客户要求,在Person中添加一个成员变量,性别,使用sex表示
    问题: 当将Person对象写入到文件中(序列化),修改了Person类的内容,再将序列化的内容进行反序列化(讲对象从文件中读取出来),报错,InvalidClassException
    InvalidClassException 发生原因 : 该类的序列版本号与从流中读取的类描述的版本号不匹配

  2. 序列版本号 : 每个类的版本都是有一个版本号的标识
    举例 : 1. 创建一个Person---->name,age 版本号1
    2.修改Person类,系统西东生成一个新的版本号2

如上,版本号1和版本号2不一致,导致了Person的反序列化失败

  1. 如何解决: 在Person类中,定义一个固定的不能被修改的序列号,如下代码

/**
* serialVersionUID Person类的版本号,也称为序列号
* 使用一个long类型的数值表示
*/
private static final long serialVersionUID = 1L;

  1. 随机访问流
    RandomAccessFile : 叫做随机访问流,不属于IO流体系,只是
    一个普通的类文件,父类直接是Object,功能比IO流还强大

为什么使用随机访问流

  1. 可以对文件进行读和写
  2. 可以在文件的任意位置进行读和写
  1. RandomAccessFile的构造方法
    RandomAccessFile(File f ,String mode) : 参数f确定一个文件,随机访问流就对f所表示的文件进行操作; 参数mode ,表示可以对文件做的操作是什么
    mode : r 只读 rw 读写
    RandomAccessFile(String path ,String mode) :参数path确定一个文件,随机访问流就对path所表示的文件进行操作; 参数mode ,表示可以对文件做的操作是什么

  2. 随机访问流的读写功能参见API

  3. seek(long pos): RandomAccessFile类可以将要读取的文件,看做是一个字节的数组,定义了一个指针,指针可以定位在数组中的任意位置,可以使用seek方法,定位指针的位置,在该位置发生下一个读取或写入操作

代码
package com.zgjy.otherStream;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;

public class RandomAccessFileDemo {

public static void main(String[] args) throws IOException {
	// 1. 创建一个随机访问流对象,对文件进行操作
	RandomAccessFile raf = new RandomAccessFile("random.txt", "rw");
	// 2. 先读一下文件内容
	int i = raf.read();
	System.out.println((char)i);
	
	// seek方法将文件中的指针设置在3的位置上,就从3的位置开始接下来的读操作
	raf.seek(3);
	int ii = raf.read();
	System.out.println((char)ii);
	
	// seek方法将文件中的指针设置在4的位置上,就从4的位置开始接下来的写操作
	raf.seek(4);
	raf.write(101);//e	
	
	raf.close();
}

}

  1. 打印流
    字节打印流 : PrintStream
    字符打印流 : PrintWriter

打印流本身也是具有文件的读写功能,但是不关注.关注打印流进行数据的输出打印
PrintStream和PrintWriter类中有很多的print和println方法,这些方法都是方法的重载
主要是将任意的数据类型转换成字符串,然后将字符串输出到指定的设备上

4.1 PrintStream字节打印流

  1. System.out : 系统的标准输出流,结果就是PrintStream,默认将数组打印在控制台上
  2. PrintStream中有方法,叫做println和print.能将任意数据类型转化成称字符串进行输出
  3. PrintStream单独进行对象创建,可以将数据打印到指定的文件中

代码
package com.zgjy.otherStream;
import java.io.IOException;
import java.io.PrintStream;
public class PrintStreamDemo {

public static void main(String[] args) throws IOException {
	// 1. 将System.out返回的字节打印流,默认关联输出在控制台上
	System.out.println(true);
	int[] arr = {1,2,3};
	char[] ch = {'1','2','3'};
	System.out.println(arr);// [I@15db9742
	/*
	 * PrintStream中的println方法,重载
	 * 但是在数组中,只有char类型的数组,println在定义char类型数组打印结果时
	 * 不是打印地址值,而是将char数组中的内容进行输出
	 * 
	 * */
	System.out.println(ch);// 123
	
	// 2. 自己创建一个字节的打印流,将数据打印到指定文件中
	PrintStream ps = new PrintStream("print.txt");
	ps.print(false);
	ps.print('国');
	ps.println('家');
	ps.println("12345");
	
	ps.close();
}

}

4.2 PrintWriter字符打印流
PrintWriter 类,是Writer字符输出流的子类,具有针对于字符的写的功能,主要关注的中的print和println方法,与PrintStream中的打印方法功能基本一致

PrintWriter作为Writer的子类,具有数组的缓冲1024大小缓冲,写数据是,先存储在数组中,然后再向文件中打印同步

PrintWriter中有方法可以实现自动的刷出,直接将数据同步到文件中(println,printf,format)

  1. PrintWriter的构造方法
    可以进行自动刷出功能的构造方法:
  1. PrintWriter(OutputStream out , boolean autoFlush) : 将一个字节的输出流包装成一个打印流,可以使用打印的方法; autoFlush这个布尔类型参数,设置为true,那么方法的功能就可以自动将数据同步到文件中
  2. PrintWriter(Writer out , boolean autoFlush):将一个字符的输出流包装成一个打印流,可以使用打印的方法; autoFlush这个布尔类型参数,设置为true,那么方法的功能就可以自动将数据同步到文件中

代码
package com.zgjy.otherStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
public class PrintWriterDemo {
public static void main(String[] args) throws IOException{
// 1. 创建一个PrintWriter对象
FileOutputStream fos = new FileOutputStream(“writer.txt”);
PrintWriter pw = new PrintWriter(fos, true);

	// 2. 使用字符的输出流向文件writer.txt输出数据
	pw.print(true);
	pw.write("你好".toCharArray());
    // println中的方法能将数组缓冲中的数据都同步到文件中
	pw.println(123);
	
	pw.close();
}

}

  1. 标准输入输出流

  2. 标准输入流: System.in ,结果是InputStream类型,字节输入流的抽象父类
    默认关联 : 键盘输入数据. InputStream 将通过键盘输入的数据读取到程序中
    Scanner sc = new Scanner(System.in);

  3. 标准输出流 : System.out ,结果PrintStream,字节打印流,主要功能就是进行数据的输出
    默认输出 : 到控制台上
    System.out.println();

  4. Properties(掌握)
    Properties类来自于java.util包,是Hashtable的子类,Hashtable<K,V>的键值对映射关系集合
    Properties表示一个持久性的属性集,也是具有两个泛型,也是一个映射关系集合
    Properties类的定义上没有看到key和value的泛型,Properties的key和value都是固定的,都是String类型

Properties的功能:
就是可以读取配置文件中的内容,将配置文件中的键值对关系直接的读到Properties集合中

  1. 配置文件 : 文件中写的内容是一些键值对,一般用于表示一些固定的数据内容,例如 : 数据库的配置文件

举例配置文件: 后缀名一般都是使用properties
配置文件内容 :
userName=root
userPassword=123456

6.1 Properties类的特有方法

  1. setProperty(String key,String value) : 将key和value键值对映射关系添加到properties类中
  2. getProperty(String key): 通过key的值获取到对应的value值,返回值类型String类型
  3. stringPropertyNames(): 将properties集合中的所有key获取到,放置到一个Set集合中,Set

代码
package com.zgjy.pro;
import java.util.Properties;
import java.util.Set;
public class PropertiesMethod {
public static void main(String[] args) {
// 1. 创建一个properties对象
Properties p = new Properties();
// 2. 向集合中添加String类型的键值对关系
// setProperty类比map集合中的put方法
p.setProperty(“1”, “a”);
p.setProperty(“2”, “b”);
p.setProperty(“3”, “c”);
//3.遍历集合中的每一个key和value的元素
// stringPropertyNames(): 将properties集合中的所有key获取到,放置到一个Set集合中,Set
// stringPropertyNames 类比map中keySet方法
Set set = p.stringPropertyNames();
// 4. 遍历Set集合
for(String key : set) {
// 5. getProperty(String key): 通过key的值获取到对应的value值,返回值类型String类型
String value = p.getProperty(key);
System.out.println(key+"—"+value);
}

	// p.put(key, value);方法可以使用的
	// p.keySet();方法可以使用的	
}

}

6.2 Properties类读取配置文件内容

  1. 创建一个配置文件,主要是以properties为后缀名
    file.properties
    在这里插入图片描述
  2. load(InputStream in) : 使用in这个字节的输入流读取文件file.properties,load方法将流中读到的文件内容,放置到集合的键值对关系上
  3. load(Reader r) : 使用r这个字符的输入流读取文件file.properties,load方法将流中读到的文件内容,放置到集合的键值对关系上

代码
package com.zgjy.pro;
import java.io.FileReader;
import java.io.IOException;
import java.util.Properties;
import java.util.Set;
public class ReadFile {
// 使用Properties读取配置文件内容
public static void main(String[] args) throws IOException{
// 1. 创建一个properties对象
Properties p = new Properties();
// 2. 创建一个字符的输入流,绑定读取哪个配置文件
FileReader fr = new FileReader(“file.properties”);
// 3. load方法将配置文件中的键值对已经放置到集合中
p.load(fr);
// 4. 遍历集合,查看文件内容
Set set= p.stringPropertyNames();
for(String key : set) {
String value = p.getProperty(key);
System.out.println(key+"----"+value);
}
}
}

6.3 Properties类修改配置文件内容
file.properties
name=zhangsan
age=20
修改配置文件:
name=lisi
age=25

修改后的配置文件结果如下图:
在这里插入图片描述

  1. store(OutputStream out,String s) : store方法能将Properties集合中的键值对的关系同步到文件中,同步其实就是向文件中写入内容,写的动作只能IO流进行,因此可以利用out所表示的字节的输出流向文件中覆盖内容; s表示修改说明
  2. store(Writer w,String s): store方法能将Properties集合中的键值对的关系同步到文件中,同步其实就是向文件中写入内容,写的动作只能IO流进行,因此可以利用out所表示的字节的输出流向文件中覆盖内容; s表示修改说明

代码
package com.zgjy.pro;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Properties;
public class WriteFile {
public static void main(String[] args) throws IOException {
// 1. 创建一个properties对象
Properties p = new Properties();
// 2. 设置集合中的键值对元素
p.setProperty(“name”, “lisi”);
p.setProperty(“age”, “25”);
// 3. store(Writer w ,String s)
p.store(new FileWriter(“file.properties”), “change”);
}
}

  1. IO流异常处理标准格式(掌握)
    IO流写作规范:
  2. 使用时先导包,java.io
  3. 使用时,需要抛出异常,需要处理
  4. 流资源使用完毕,一定记得关闭资源

IO流异常的标准处理方式:

  1. 流资源的创建,可能或发生文件找不到异常,因此放在try代码块中
  2. 流资源进行文件的读写时,可能会发生异常,因此放在try代码块中
  3. 流资源必须要关闭,因此放在finally中进行处理
  4. 因为流资源的创建和流的关闭在不同的代码块中,因为变量的作用范围需要提升,将资源的声明放置在整个异常处理之上,设置默认值为null
  5. 因为关闭的流资源有多个,而每一个流资源在关闭时,都有可能发生异常,因此,多个流需要分别进行try和catch的处理
  6. 如果创建资源就发生异常,那么资源创建失败,资源对应的值就为null,null值关闭流的时候,会抛出空指针异常,所以关闭流资源之前,先判断流资源不为null,再关流

标准的流资源关闭代码
package com.zgjy.exception;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileCopy {

public static void main(String[] args) {

}
// 使用字节流进行文件复制
public static void copy1() {
	// 将流资源变量的定义放在try...catch...finally之外,为了提高变量的作用范围
	FileInputStream fis = null;
	FileOutputStream fos = null;
	try {
		// 1. 创建一个字节的输入流,读取文件
		fis = new  FileInputStream("chinese.txt");
		// 2. 创建一个字节的输出流,绑定一个数据目的
		fos = new FileOutputStream("chineseCopy.txt");
		// 3. 文件复制,边读边写
		int len;
		while((len = fis.read()) != -1) {
			fos.write(len);
		}
		
	}catch(FileNotFoundException ex) {
		ex.printStackTrace();
	}catch(IOException ex) {
		ex.printStackTrace();
	}finally {
		// 流资源因为一定要关闭,因此放在finally中
		// 有因为有两个流资源关闭,因此两个资源分开关闭,才能保证两个流资源都关闭掉
		try {
			if(fis!= null) {
				fis.close();
			}
		}catch(IOException ex) {
			ex.printStackTrace();
		}finally {
			try {
				if(fos!= null) {
					fos.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}	
	}
}

// IO流的优化的处理方式
// JDK1.7版本以及以上

}

标准的IO流异常处理比较繁琐,从JDK1.7版本开始,优化了IO流的异常处理:

语法格式:
try (
// 小括号中写流的创建语句,写在try小括号中的流资源的创建,不需要关流
// 不需要close(),try在流使用结束之后,自动关闭资源
){
// 可能会发生问题的代码

	}catch(可能会发生的异常类型  异常名字) {
		
		// 异常的处理方式
 }

代码
public static void copy2() {
public static void main(String[] args) {
try(
FileInputStream fis = new FileInputStream(“chinese.txt”);
FileOutputStream fos = new FileOutputStream(“chineseCopy.txt”);
){
// 可以回发生问题的代码
// 3. 文件复制,边读边写
int len;
while((len = fis.read()) != -1) {
fos.write(len);
}
}catch(IOException ex) {
ex.printStackTrace();
}
}
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值