Java基础(十二)——IO流



1,流的分类

按照流的流向划分:
输入流:只能从中读取数据,而不能向其写数据。输入流基类,InputStream和Reader。
输出流:只能向其写入数据,而不能从中读取数据。输出流基类,OutputStream和Writer。

按照操作数据单元划分:
字节流:操作最小数据单元是8位的字节。字节流基类,InputStream和OutputStream。
字符流:操作最小数据单元是16位字节。字符流基类,Reader和Writer。

2,Java IO体系
图1 IO体系图


图2 IO体系分类图

3,IO流抽象基类
3.1,输入流的抽象基类:InputStream和Reader
        InputStream和Reader是所有输入流的基类,它们都是抽象类,本身不能创建实例来执行输入,但它们是所有输入流的模板,所有输入流都使用它们提供的方法。
在InputStream里包含如下三个方法:
》》int read():从输入流中读取单个字节,返回所读取的字节数据(字节数据可直接转换为int类型)。
》》int read(byte[] b):从输入流中读取最多b.length个字节的数据,并将其存储在字节数组b中,赶回实际读取的字节数。
》》int read(byte[] b,int off,int len):从输入流中读取最多len字节的数据,并将其从off位置开始存储在数组b中,返回实际读取的字节数。
在Reader里包含如下三个方法:
》》int read():从输入流中读取单个字符,返回所读取的字符数据(字符数据可直接转换为int类型)。
》》int read(char[] cbuf):从输入流中读取最多cbuf.length个字符的数据,并将其存储在字符数组cbuf中,赶回实际读取的字符数。
》》int read(char[] cbuf,int off,int len):从输入流中读取最多len字符的数据,并将其从off位置开始存储在数组cbuf中,返回实际读取的字符数。

3.2,输出流的抽象基类:OutputStream和Writer
    OutputStream和Writer和前面的输入流相似,两个流都提供了如下三个方法:
》》void write(int c):将指定的字节/字符输出到输出流中,其中c代表字节或者字符。
》》void write(byte[]/char[] buf):将字节数组/字符数组中的数据输出到指定的输出流中。
》》void write(byte[]/char[] buf,int off,int len):将字节数组/字符数组中从off位置开始,长度为len的字节/字符输出到指定的输出流中。
    因为字符流直接以字符作为操作单位,所以Writer可以用字符串来代替字符数组,即以String对象作为参数。Writer里还包含如下两个方法:
》》void write(String str):将字符串str里包含的字符输出到指定的输出流中。
》》void write(String str,int off,int len):将字符串str从off位置开始,长度为len的字符输出到指定输出流中。

4,IO流程序异常处理的标准方式

import java.io.*;

public class IOExcepDemo {
	public static void main(String[] args) {
		FileWriter fw = null;
		try {
			fw = new FileWriter("c:\\demo.txt");
			fw.write("abcde");
		} catch (IOException e) {
			// 异常处理方式
			System.out.println(e.toString());
		} finally {
			try {
				if (fw != null)
					fw.close();
			} catch (IOException e) {
				// 异常处理方式
				System.out.println(e.toString());
			}
		}
	}
}
5,流与流之间相对应的关系
图5.1 字节输入流与输出流的对应关系


图5.2,字符输入流与输出流的对应关系

6,实现IO的子类
6.1,字符流
1,FileWriter
构造方法:
FileWriter(String fileName):根据给定的文件名构造一个FileWriter对象。
FileWriter(String fileName, boolean append):根据给定的文件名以及指示是否附加写入数据的boolean值来构造FileWriter对象。
常用方法:
void close():关闭此流,但要先刷新它。
void flush():刷新该流的缓冲。
void write(char[] cbuf) :写入字符数组。
void write(char[] cbuf, int off, int len) :写入字符数组的某一部分。
void write(int c) :写入单个字符。
void write(String str) :写入字符串。
void write(String str, int off, int len) :写入字符串的某一部分。

2,FileReader
构造方法:
FileReader(String fileName):在给定从中读取数据的文件名的情况下创建一个新 FileReader。
常用方法:    
void close() :关闭该流并释放与之关联的所有资源。
int read() :读取单个字符。
int read(char[] cbuf) :将字符读入数组。
int read(char[] cbuf, int off, int len) :将字符读入数组的某一部分。

3,BufferedWriter

构造方法:
BufferedWriter(Writer out):创建一个使用默认大小输出缓冲区的缓冲字符输出流。
常用方法:        
void close():关闭此流,但要先刷新它。
void flush():刷新该流的缓冲。
void newLine():写入一个行分隔符,该方法具有跨平台性。
void write(char[] cbuf) :写入字符数组。
void write(char[] cbuf, int off, int len):写入字符数组的某一部分。
void write(int c):写入单个字符。
void write(String str) :写入字符串。
void write(String s, int off, int len):写入字符串的某一部分。

4,BufferedReader

构造方法:
BufferedReader(Reader in):创建一个使用默认大小输入缓冲区的缓冲字符输入流。
常用方法:
void close():关闭该流并释放与之关联的所有资源。
int read():读取单个字符。
int read(char[] cbuf):将字符读入数组。
int read(char[] cbuf, int off, int len):将字符读入数组的某一部分。
String readLine():读取一个文本行。当读取到文件末尾时,返回null。

5,LineNumberReader:是BufferedReader的子类,带行号操作的类
构造方法:
LineNumberReader(Reader in):使用默认输入缓冲区的大小创建新的行编号 reader。
常用方法:
int getLineNumber():获得当前行号。
void mark(int readAheadLimit):标记该流中的当前位置。
int read():读取单个字符。
int read(char[] cbuf, int off, int len):将字符读入数组中的某一部分。
String readLine():读取文本行。  
void setLineNumber(int lineNumber):设置当前行号。
void close():关闭流。

6,代码演示:复制纯文本文件
import java.io.*;
public class CopyTextDemo {
	public static void main(String[] args) throws IOException {
		// copy_one();
		// copy_two();
		copy_three();
	}
	// 使用字符缓冲流,提高效率
	public static void copy_three() throws IOException {
		BufferedReader bfr = new BufferedReader(new FileReader("c:\\demo.java"));
		BufferedWriter bfw = new BufferedWriter(new FileWriter("c:\\demo.txt"));
		String line = null;
		while ((line = bfr.readLine()) != null) {
			bfw.write(line);
			// 换行,因为readLine方法写入时,不包含换行符
			bfw.newLine();
			bfw.flush();
		}
		bfr.close();
		bfw.close();
	}
	// 使用字符数组进行缓冲,一次读取,一次写入,效率高
	public static void copy_two() throws IOException {
		FileReader fr = new FileReader("c:\\demo.java");
		FileWriter fw = new FileWriter("c:\\demo_copy.txt");
		char[] buf = new char[1024];
		int len = 0;
		while ((len = fr.read(buf)) != -1) {
			fw.write(buf, 0, len);
		}
		fr.close();
		fw.close();

	}
	// 读一个字符,写一个字符,这种方法效率很低下
	public static void copy_one() throws IOException {
		FileReader fr = new FileReader("c:\\demo.java");
		FileWriter fw = new FileWriter("c:\\demo_copy.txt");
		int len = 0;
		while ((len = fr.read()) != -1) {
			fw.write((char) len);
		}
		fr.close();
		fw.close();
	}
}
6.2,字节流
1,FileOutputStream
构造方法:
FileOutputStream(String name):创建一个向具有指定名称的文件中写入数据的输出文件流。
FileOutputStream(String name, boolean append):创建一个向具有指定 name 的文件中写入数据的输出文件流
常用方法:
void close():关闭此文件输出流并释放与此流有关的所有系统资源。
void write(byte[] b):将 b.length 个字节从指定 byte 数组写入此文件输出流中。
void write(byte[] b, int off, int len):将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此文件输出流。
void write(int b):将指定字节写入此文件输出流。

2,FileInputStream
构造方法:
FileInputStream(String name):通过打开一个到实际文件的连接来创建一个 FileInputStream,该文件通过文件系统中的路径名 name 指定。
常用方法:
int available():返回下一次对此输入流调用的方法可以不受阻塞地从此输入流读取(或跳过)的估计剩余字节数。
void close():关闭此文件输入流并释放与此流有关的所有系统资源。
int read():从此输入流中读取一个数据字节。
int read(byte[] b):从此输入流中将最多 b.length 个字节的数据读入一个 byte 数组中。
int read(byte[] b, int off, int len):从此输入流中将最多 len 个字节的数据读入一个 byte 数组中。

3,BufferedOutputStream
构造方法:
BufferedOutputStream(OutputStream out):创建一个新的缓冲输出流,以将数据写入指定的底层输出流。
常用方法:
void close():关闭此输出流并释放与此流有关的所有系统资源。
void flush():刷新此缓冲的输出流。
void write(byte[] b, int off, int len):将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此缓冲的输出流。
void write(int b):将指定的字节写入此缓冲的输出流。
void write(byte[] b):将 b.length 个字节写入此输出流。

4,BufferedInputStream
构造方法:
BufferedInputStream(InputStream in):创建一个 BufferedInputStream 并保存其参数,即输入流 in,以便将来使用。
常用方法:
int available():返回可以从此输入流读取(或跳过)、且不受此输入流接下来的方法调用阻塞的估计字节数。
void close():关闭此输入流并释放与该流关联的所有系统资源。
int read():从此输入流中读取下一个数据字节。
int read(byte[] b):从此输入流中将 byte.length 个字节的数据读入一个 byte 数组中。
int read(byte[] b, int off, int len):从此字节输入流中给定偏移量处开始将各字节读取到指定的 byte 数组中。

6.3,转换流
1,InputStreamReader:读取转换流。字节流通往字符流的桥梁。
常用方法:InputStreamReader(InputStream in):创建一个使用默认字符集的 InputStreamReader。
         沿用java.io.Reader类中的方法,但是基本上只起桥梁作用,实际上该流最终会被缓冲区操作。BufferedReader。

2,OutputStreamWriter:写入转换流。字符流通往字节流的桥梁。
常用方法:OutputStreamWriter(OutputStream out):创建使用默认字符编码的 OutputStreamWriter。
         沿用java.io.Writer类中的方法,但是基本上只起桥梁作用,实际上最终被缓冲区操作。
3,演示代码
/*
 需求1
 在控制台录入信息,将录入的文字存入到文件
 源:键盘
 目的:文件

 需求2
 在控制台打印文件的内容
 源:文件
 目的:控制台

 */
import java.io.*;

class TransStreamDemo{
	public static void main(String[] args) throws IOException {
		// readKeywordToFile();
		readFileToConsole();
	}
	public static void readKeywordToFile() throws IOException {
		// 创建数据源
		InputStream in = System.in;
		InputStreamReader isr = new InputStreamReader(in);
		BufferedReader bfr = new BufferedReader(isr);
		// 创建数据目的
		FileOutputStream fos = new FileOutputStream("c:\\out.txt");
		OutputStreamWriter osr = new OutputStreamWriter(fos);
		BufferedWriter bfw = new BufferedWriter(osr);
		// 操作
		String line = null;
		while ((line = bfr.readLine()) != null) {
			if ("over".equals(line))
				break;
			bfw.write(line.toUpperCase());
			bfw.newLine();
			bfw.flush();
		}
		bfr.close();
		bfw.close();
	}
	public static void readFileToConsole() throws IOException {
		// 数据源
		FileInputStream fis = new FileInputStream("c:\\demo.java");
		InputStreamReader isr = new InputStreamReader(fis);
		BufferedReader bfr = new BufferedReader(isr);
		// 数据目的
		OutputStream out = System.out;
		OutputStreamWriter osw = new OutputStreamWriter(out);
		BufferedWriter bfw = new BufferedWriter(osw);
		// 操作
		String line = null;
		while ((line = bfr.readLine()) != null) {
			bfw.write(line);
			bfw.flush();
			bfw.newLine();
		}
		bfr.close();
		bfw.close();
	}
}
6.4,File类
属性:
static String separator:与系统有关的默认名称分隔符
构造方法:
File(File parent, String child):根据 parent 抽象路径名和 child 路径名字符串创建一个新 File 实例。
File(String pathname):通过将给定路径名字符串转换为抽象路径名来创建一个新 File 实例。
File(String parent, String child):根据 parent 路径名字符串和 child 路径名字符串创建一个新 File 实例。

一般方法:
1)创建
boolean createNewFile():在指定位置创建文件,如果文件已经存在,则不创建,返回false。 和输出流不一样,输出流对象一定创建文件,如果文件已存在,会覆盖旧文件。
boolean mkdir():创建单级目录。
boolean mkdirs():创建多级目录。

2)删除
boolean delete():删除此抽象路径名表示的文件或目录,若文件或目录不存在,返回false。注意:当删除某个目录时,若目录里有文件存在,则不可删除,返回false。
void deleteOnExit():在虚拟机终止时,请求删除此抽象路径名表示的文件或目录。

3)判断
boolean exists():测试此抽象路径名表示的文件或目录是否存在。
boolean isDirectory():判断是否是目录。
boolean isFile():判断是否是文件。  注意:判断文件对象是否是文件或者目录时,必须先通过exists()方法判断该文件对象是否存在。
boolean isAbsolute():测试此抽象路径名是否为绝对路径名。
boolean isHidden():测试此抽象路径名指定的文件是否是一个隐藏文件。
boolean canRead():判断File对象所对应的文件或者目录是否可读。
boolean canWrite():判断File对象所对应的文件或者目录是否可写。

4)获取信息
File getAbsoluteFile():返回此抽象路径名的绝对路径名形式。
String getAbsolutePath():返回此抽象路径名的绝对路径名字符串。
File getCanonicalFile():返回此抽象路径名的规范形式。
String getCanonicalPath():返回此抽象路径名的规范路径名字符串。
long getFreeSpace():返回此抽象路径名指定的分区中未分配的字节数。
String getName():返回由此抽象路径名表示的文件或目录的名称。
String getParent():返回此抽象路径名父目录的路径名字符串;如果此路径名没有指定父目录,则返回 null。
File getParentFile():返回此抽象路径名父目录的抽象路径名;如果此路径名没有指定父目录,则返回 null。
String getPath():将此抽象路径名转换为一个路径名字符串。
long getTotalSpace():返回此抽象路径名指定的分区大小。
long getUsableSpace():返回此抽象路径名指定的分区上可用于此虚拟机的字节数。
long length():返回由此抽象路径名表示的文件的长度。
static File[] listRoots():列出可用的文件系统根。
String[] list():列出File对象所有的子文件和路径名,返回String数组。
String[] list(FilenameFilter filter):返回一个字符串数组,这些字符串指定此抽象路径名表示的目录中满足指定过滤器的文件和目录。
File[] listFiles():列出File对象所有的子文件和路径,返回File数组。
演示代码:
/*
 将指定目录下的所有Java文件的绝对路径全部输出到文本文件中保存
 方法:递归
 用集合存储
 */
import java.util.*;
import java.io.*;

class JavaFileList {
	public static void main(String[] args) throws IOException {
		File file = new File("e:\\JavaStudy");
		List<File> list = new ArrayList<File>();
		fileToList(file, list);
		File files = new File(file, "java.txt");
		writeToFile(list, files.toString());
	}
	public static void fileToList(File file, List<File> list)
			throws IOException {
		File[] dir = file.listFiles();
		for (int x = 0; x < dir.length; x++) {
			if (dir[x].isDirectory())
				fileToList(dir[x], list);
			else {
				if (dir[x].getName().endsWith(".java"))
					;
				list.add(dir[x]);
			}
		}
	}
	public static void writeToFile(List<File> list, String javaListFile)
			throws IOException {
		BufferedWriter bfw = new BufferedWriter(new FileWriter(javaListFile));
		for (File f : list) {
			String path = f.getAbsolutePath();
			bfw.write(path);
			bfw.newLine();
			bfw.flush();
		}
		bfw.close();
	}
}

6.5,Properties类
Properties是hashtable的子类。
它具备Map集合的特点,而且它里面存储的键值对都是字符串,是集合中和IO流技术相结合的集合容器。
特点:可以用于键值对形式的配置文件。
常用方法:
String getProperty(String key):用指定的键在此属性列表中搜索属性。
String getProperty(String key, String defaultValue):用指定的键在属性列表中搜索属性。
void list(PrintStream out):将属性列表输出到指定的输出流。
void list(PrintWriter out):将属性列表输出到指定的输出流。
void load(InputStream inStream):从输入流中读取属性列表(键和元素对)。
void load(Reader reader):按简单的面向行的格式从输入字符流中读取属性列表(键和元素对)。
Object setProperty(String key, String value):调用 Hashtable 的方法 put,设置键值对。
void store(OutputStream out, String comments)以适合使用 load(InputStream):
方法加载到Properties表中的格式,将此Properties表中的属性列表(键和元素对)写入输出流。
void store(Writer writer, String comments)以适合使用 load(Reader):方法的格式,将此 Properties 表中的属性列表(键和元素对)写入输出字符。
Set<String> stringPropertyNames():返回此属性列表中的键集,其中该键及其对应值是字符串,如果在主属性列表中未找到同名的键,则还包括默认属性列表中不同的键。

6.6,打印流

1,PrintStream
构造方法:
PrintStream(File file):创建具有指定文件且不带自动行刷新的新打印流。
PrintStream(File file, String csn):创建具有指定文件名称和字符集且不带自动行刷新的新打印流。
PrintStream(OutputStream out):创建新的打印流。
PrintStream(OutputStream out, boolean autoFlush):创建新的打印流。
PrintStream(OutputStream out, boolean autoFlush, String encoding):创建新的打印流。
PrintStream(String fileName):创建具有指定文件名称且不带自动行刷新的新打印流。
PrintStream(String fileName, String csn):创建具有指定文件名称和字符集且不带自动行刷新的新打印流。
一般方法:
void close():关闭流。
void flush():刷新该流的缓冲。
print方法:各种数据类型的打印方法。详情见jdk1.6 api
println方法:各种数据类型的带换行的打印方法。详情见jdk1.6 api
注意:
        由于PrintStream类的输出功能非常强大,通常如果我们需要输出文本内容,都应该讲输出流包装成PrintStream后进行输出。
当使用处理流来包装底层节点流之后,关闭输入/输出流资源时,只要关闭最上层的处理流即可。关闭最上层的处理流,系统会自动
关闭被该处理流包装的节点流。对于下面介绍的PrintWrite类,同样适用。

2,PrintWriter
构造方法:
PrintWriter(File file):使用指定文件创建不具有自动行刷新的新 PrintWriter。
PrintWriter(File file, String csn):创建具有指定文件和字符集且不带自动刷行新的新 PrintWriter。
PrintWriter(OutputStream out):根据现有的 OutputStream 创建不带自动行刷新的新 PrintWriter。
PrintWriter(OutputStream out, boolean autoFlush):通过现有的 OutputStream 创建新的 PrintWriter。
PrintWriter(String fileName):创建具有指定文件名称且不带自动行刷新的新 PrintWriter。
PrintWriter(String fileName, String csn):创建具有指定文件名称和字符集且不带自动行刷新的新 PrintWriter。
PrintWriter(Writer out):创建不带自动行刷新的新 PrintWriter。
PrintWriter(Writer out, boolean autoFlush):创建新 PrintWriter。
一般方法:
void close():关闭该流并释放与之关联的所有系统资源。
void flush():刷新该流的缓冲。
void write(char[] buf):写入字符数组。
void write(char[] buf, int off, int len):写入字符数组的某一部分。
void write(int c):写入单个字符。
void write(String s):写入字符串。
void write(String s, int off, int len):写入字符串的某一部分
print方法:打印各种数据类型的方法。详情见jdk1.6 api
println方法:带换行打印各种数据类型的方法。详情见jdk1.6 api

6.7,对象序列化
        对象序列化,也称作对象的持久化。是指将对象从堆内存中转移到硬盘的文件中保存,以方便下次程序运行直接取出来用,
而不需要重新在内存中创建相同的对象。执行对象序列化需要用到两个功能流进行操作,ObjectOutputStream和ObjectInputStream。
1,ObjectOutputStream
构造方法:
ObjectOutputStream(OutputStream out):创建写入指定 OutputStream 的 ObjectOutputStream。
常用方法:
void close():关闭流。
void flush():刷新该流的缓冲。
void writeObject(Object obj):将指定的对象写入 ObjectOutputStream。
其它写入各种数据类型的write方法,详情请查阅jdk1.6 api

2,ObjectInputStream
构造方法:
ObjectInputStream(InputStream in):创建从指定 InputStream 读取的 ObjectInputStream。
常用方法:
void close():关闭输入流。
Object readObject():从 ObjectInputStream 读取对象。
其它读取各种数据类型的read方法,详情请查阅jdk1.6 api

3,注意:

(1)自定义序列号:public static final long serialVersionUID = 数值;
(2)静态成员不能被序列化
(3)若想让非静态成员不被序列化,在非静态成员前加关键字 transient 声明。

6.8,RandomAccessFile
        该类不算是IO体系中的子类,而是直接继承自Object。但是它是IO包中的成员,因为它具有读写功能。内部封装了一个数组,而且通过指针对数组的元素进行操作。可以通过getFilePointer获取指针的位置,同时可以通过seek改变指针的位置。该类能够完成读写的原理其实是其内部封装了字节的输入流和输出流。通过构造方法可以看出,该类只能操作文件,而且操作文件还有模式。只读"r",读写"rw"等。

而且调用该对象的构造方法时,
如果模式为"r",不会创建文件。会去读取一个已存在的文件,若文件不存在,则报异常。
如果模式为"rw",如果文件不存在,就新建文件,如果文件已经存在,不会覆盖原文件。

构造方法:
RandomAccessFile(File file, String mode):创建从中读取和向其中写入(可选)的随机访问文件流,该文件由 File 参数指定。
RandomAccessFile(String name, String mode):创建从中读取和向其中写入(可选)的随机访问文件流,该文件具有指定名称。

模式:
"r"  :以只读方式打开。调用结果对象的任何 write 方法都将导致抛出 IOException。  
"rw" :打开以便读取和写入。如果该文件尚不存在,则尝试创建该文件。  
"rws":打开以便读取和写入,对于 "rw",还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备。  
"rwd":打开以便读取和写入,对于 "rw",还要求对文件内容的每个更新都同步写入到底层存储设备。

常用方法:
void close():关闭此随机访问文件流并释放与该流关联的所有系统资源。
long length():返回此文件的长度。
int read():从此文件中读取一个数据字节。
String readLine():从此文件读取文本的下一行。
void seek(long pos):设置到此文件开头测量到的文件指针偏移量,在该位置发生下一个读取或写入操作。
int skipBytes(int n):调整文件指针,尝试跳过输入的 n 个字节以丢弃跳过的字节。
void write(byte[] b):将 b.length 个字节从指定 byte 数组写入到此文件,并从当前文件指针开始。
void write(byte[] b, int off, int len):将 len 个字节从指定 byte 数组写入到此文件,并从偏移量 off 处开始。
还有不同数据类型数据的read方法。详情查阅jdk1.6 api
还有不同数据类型数据的write方法。详情查阅jdk1.6 api

7,流的操作规律
1)明确源和目的。
数据源:就是需要读取,可以使用两个体系:InputStream、Reader;
数据目的:就是需要写入,可以使用两个体系:OutputStream、Writer;
2)操作的数据是否是纯文本数据?
如果是:数据源:Reader
                数据目的:Writer
如果不是:数据源:InputStream
                    数据目的:OutputStream
3)虽然确定了一个体系,但是该体系中有太多的对象,到底用哪个呢?
明确操作的数据设备。
数据源对应的设备:硬盘(File),内存(数组),键盘(System.in)
数据汇对应的设备:硬盘(File),内存(数组),控制台(System.out)。
4)需要在基本操作上附加其他功能吗?比如缓冲。
如果需要就进行装饰。

8,重定向标准输入/输出
        Java的标准输入输出分别通过System.in和System.out来代表,默认情况下它们分别代表键盘和控制台,当程序通过System.in来获取输入时,实际上是从键盘读取输入;当程序试图通过System.out执行输出时,程序总是输出到控制台。
    在System类里提供了三个重定向标准输入/输出的方法:
》》static void setErr(PrintStream err):重定向“标准”错误输出流、
》》static void setIn(InputStream in):重定向“标准”输入流。
》》static void setOut(PrintStream out):重定向“标准”输出流。




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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值