Java IO流篇:File类、IO流原理及流的分类、节点流、缓冲流、数据流、字符流、对象流、对象序列化、转换流。

一、File类的使用

1.1 File类

File对象,是Java中为了操作操作系统中文件而设计的

java.io.File类:文件文件目录路径的抽象表示形式,与平台无关

File类Java提供的一个操作文件及文件夹的类。 File 能新建、删除、重命名文件和目录,但 File 不能访问文件内容本身。 如果需要访问文件内容本身,则需要使用输入/输出流。

想要在Java程序中表示一个真实存在的文件或目录,那么必须有一个File对 象,但是Java程序中的一个File对象,可能没有一个真实存在的文件或目录。

File对象可以作为参数传递给流的构造器

1.2 绝对路径和相对路径

1.2.1 绝对路径

直接可以定位文件的一种路径,是一个固定的路径,从盘符开始。

  • window操作系统:直接使用盘符查找:d:\a\b\c.jpg

  • linux:/ /user/local/bin/java

  • 网络URL:https://www.baidu.com/a.jpg

1.2.2 相对路径

相对于某个参照物(相对于文件夹),进行查找。

  • ./a.jpg ,指的是当前目录下的a.jpg文件

可以用.表示当前目录,..表示上级目录。

1.2.3 路径分隔符

路径中的每级目录之间用一个路径分隔符隔开。路径分隔符和系统有关:

  • windows和DOS系统默认使用“\”来表示

  • UNIX和URL使用“/”来表示

Java程序支持跨平台运行,因此路径分隔符要慎用。

为了解决这个隐患,File类提供了一个常量 (静态的成员属性):

public static final String separator:根据操作系统,动态的提供分隔符

        // Windows
        File file1 = new File("d:\\user\\info.txt");
        // 使用File类的separator分隔符属性
        File file2 = new File("d:" + File.separator + "user" + File.separator + "info.txt");
        // URI
        File file3 = new File("d:/user");

1.3 File类常用构造器

  • public File(String pathname):以pathname为路径创建File对象,可以是绝对路径或者相对路径,如果 pathname是相对路径,则默认的当前路径在系统属性user.dir(本项目的目录)中存储。

  • public File(String parent,String child):以parent为父路径,child为子路径创建File对象。

  • public File(File parent,String child):根据一个父File对象和子文件路径创建File对象

  • public File(URI uri):将URI 转换为一个抽象路径名来创建一个新的 File 实例

        // 构造File对象
        // 1. public File(String pathname)
        File file = new File("D:\\code\\a.txt");
        // 2. public File(String parent,String child)
        File file1 = new File("D:", "code");
        // 3. public File(File parent,String child)
        File file3 = new File(file1,"a.txt");
        // 4. public File(URI uri)
        URI uri = new URI("file:/c:/a.txt");
        File file4 = new File(uri);

1.4 File常用方法

1.4.1 File类的获取功能

  • public String getAbsolutePath():获取绝对路径

  • public String getPath() :获取路径

  • public String getName() :获取名称

  • public String getParent():获取上层文件目录路径。若无,返回null

  • public long length() :获取文件长度(即:字节数)。不能获取目录的长度。

  • public long lastModified() :获取最后一次的修改时间,毫秒值

  • public String[] list() :获取指定目录下的所有文件或者文件目录的名称数组,没有返回null

  • public File[] listFiles() :获取指定目录下的所有文件或者文件目录的File对象(路径)数组

// 构造File对象
File file = new File("D:\\code\\IdeaProjects\\test\\system.txt");
// 获取绝对路径
System.out.println(file.getAbsolutePath());  // D:\code\IdeaProjects\test\system.txt
// 获取路径
System.out.println(file.getPath()); // D:\code\IdeaProjects\test\system.txt
System.out.println(new File("system.txt").getPath()); // system.txt
System.out.println(new File("system.txt").getAbsolutePath()); // D:\code\IdeaProjects\test\system.txt
// 获取名称
System.out.println(file.getName()); // system.txt
// 获取上层文件目录路径
System.out.println(file.getParent()); // D:\code\IdeaProjects\test
System.out.println(new File("D:").getParent()); // null
// 获取文件长度(即:字节数), 不能获取目录的长度
System.out.println(file.length()); // 24
// 获取最后一次的修改时间,毫秒值
System.out.println(file.lastModified()); // 1661150820904
System.out.println(new Date(file.lastModified())); // Mon Aug 22 14:47:00 GMT+08:00 2022
// 获取指定目录下的所有文件或者文件目录的名称数组
System.out.println(Arrays.toString(new File("D:\\code").list())); // [Ideacode, IdeaProjects, 尚硅谷, 欧鹏]
// 获取指定目录下的所有文件或者文件目录的File数组
System.out.println(Arrays.toString(new File("D:\\code").listFiles()));
// [D:\code\Ideacode, D:\code\IdeaProjects, D:\code\尚硅谷, D:\code\欧鹏]

查阅File类的API,在查阅时发现File类中重载的listFiles方法,并且接受指定的过滤器

package com.openlab.day24;

import java.io.File;
import java.io.FilenameFilter;

public class FileDemo {
    public static void main(String[] args) {
        // 获取扩展名为.java所有文件
        // 创建File对象
        File file = new File("E:\\JavaSE1115\\code\\day11_code");
        // 获取指定扩展名的文件,由于要对所有文件进行扩展名筛选,因此调用方法需要传递过滤器
        File[] files = file.listFiles(new FileFilterBySuffix(".java"));
        // 遍历获取到的所有符合条件的文件
        for (File f : files) {
            System.out.println(".........." + f);
        }
    }
}

// 定义类实现文件名称FilenameFilter过滤器
class FileFilterBySuffix implements FilenameFilter {
    private String suffix;

    // 在创建过滤器对象时,明确具体需要过滤的文件名称
    public FileFilterBySuffix(String suffix) {
        this.suffix = suffix;
    }

    public boolean accept(File dir, String name) {
        return name.endsWith(suffix);
    }
}

    @Test
	void test06() {
		File file = new File("I:\\java\\java_se\\2021");
		// 过滤特定的文件
		String[] names = file.list((dir, name) -> name.endsWith(".txt"));
		for (String s : names) {
			System.out.println(s);
		}
	}
	
	@Test
	void test07() {
		File file = new File("I:\\java\\java_se\\2021");
		// 过滤特定的文件
		File[] names = file.listFiles(pathname -> pathname.getName().endsWith(".zip"));
		for (File f : names) {
			System.out.println(f.getAbsolutePath());
		}
	}

1.4.2 File类的重命名功能

  • public boolean renameTo(File dest):把文件重命名为指定的文件路径

File file = new File("system.txt");
System.out.println(file.renameTo(new File("info.txt"))); // true

1.4.3 File类的判断功能

  • public boolea isAbsolute()判断是否是绝对路径

  • public boolean isDirectory():判断是否是文件目录

  • public boolean isFile() :判断是否是文件

  • public boolean exists() :判断是否存在

  • public boolean canRead() :判断是否可读

  • public boolean canWrite() :判断是否可写

  • public boolean canExecute() :判断该文件是否可以通过它的抽象名称可执行

  • public boolean isHidden() :判断是否隐藏

        File file = new File("D:\\code\\IdeaProjects\\test\\info.txt");
        // 判断是否是绝对路径
        System.out.println(file.isAbsolute()); // true
        // 判断是否是文件目录
        System.out.println(file.isDirectory()); // false
        // 判断是否是文件
        System.out.println(file.isFile()); // true
        // 判断是否存在
        System.out.println(file.exists()); // true
        // 判断是否可读
        System.out.println(file.canRead()); // true
        // 判断是否可写
        System.out.println(file.canWrite()); // true
        // 判断该文件是否可以通过它的抽象名称可执行
        System.out.println(file.canExecute()); // true
        // 判断是否隐藏
        System.out.println(file.isHidden()); // false

1.4.4 File类的创建功能

  • public boolean createNewFile() :创建文件。若文件存在,则不创建,返回false

  • public boolean mkdir() :创建文件目录。如果此文件目录存在,就不创建了。如果此文件目录的上层目录不存在,也不创建。

  • public boolean mkdirs() :创建文件目录。如果上层文件目录不存在,一并创建

// 创建文件
File file = new File("a.jpg");
File file1 = new File("a.jpg");
try {
    // 需要做异常处理
    System.out.println(file.createNewFile()); // true
    System.out.println(file1.createNewFile()); // false
} catch (IOException e) {
    e.printStackTrace();
}
// 创建文件目录
System.out.println(new File("D:\\code\\IdeaProjects\\test\\a\\a").mkdir()); // false
System.out.println(new File("D:\\code\\IdeaProjects\\test\\a").mkdir()); // true
System.out.println(new File("D:\\code\\IdeaProjects\\test\\b\\b").mkdirs()); // true

注意:如果你创建文件或者文件目录没有写盘符路径,那么,默认在项目路径下。

当硬盘中真有一个真实的文件或目录存在时,创建File对象时,各个属性会显式赋值。

当硬盘中没有真实的文件或目录对应时,那么创建对象时,除了指定的目录和路径之外,其他的属性都是取成员变量的默认值。

1.4.5 File类的删除功能

public boolean delete():删除文件或者文件夹

// a文件夹中无文件和目录 
System.out.println(new File("D:\\code\\IdeaProjects\\test\\a").delete()); // true
// b文件夹中还有一个b文件夹
System.out.println(new File("D:\\code\\IdeaProjects\\test\\b").delete()); // false

注意:Java中的删除不走回收站,要删除一个文件目录,请注意该文件目录内不能包含文件或者文件目录

例题:遍历指定目录所有文件名称,包括子文件目录中的文件。

    public static void scannFile(String path) {
		File file = new File(path);
		File[] listFiles = file.listFiles();
		
		for (File f : listFiles) {
			if (f.isFile()) {
				System.out.println(f.getAbsolutePath());
			} else if(f.isDirectory()) {
				// 文件夹
				scannFile(path + File.separatorChar + f.getName());
			}
		}
	}

二、IO流原理及流的分类

1. Java IO原理

I/O是Input/Output的缩写,即输入输出流, I/O技术是非常实用的技术,用于 处理设备之间的数据传输。如读/写文件,网络通讯等。

Java程序中,对于数据在内存中输入和输出操作以“流(stream)” 的 方式进行。

从狭义上来说:数据在内存中输入和输出

从广义来说,不同电脑之间的数据流动,也是一种IO流

  • 狭义上的IO流:本地进程间的数据流动

  • 广义上的IO流:远程进程间的数据流动

java.io包下提供了各种“流”类和接口,用以获取不同种类的 数据,并通过标准的方法输入或输出数据。

输入input:读取外部数据(磁盘、光盘等存储设备的数据)到程序(内存)中。

输出output:将程序(内存)数据输出到磁盘、光盘等存储设备中。

2. 流的分类

按数据流的流向不同分为:

  • 输入流

  • 输出流

按操作数据单位不同分为:

  • 字节流(8 bit)

  • 字符流(16 bit)

按流的角色的不同分为:

  • 节点流

  • 处理流:包含①缓存流(装饰流或过滤流);②转换流;③标准输入、输出流;④打印流;⑤数据流;⑥对象流

转换字节和字符流动特殊流:转换流

Java的IO流共涉及40多个类,实际上非常规则,都是从如下4个抽象基类派生的:

抽象基类字节流字符流
输入流InputStreamReader
输出流OutputStreamWriter

由这四个类派生出来的子类名称都是以其父类名作为子类名后缀

IO 流体系

分类字节输入流字节输出流字符输入流字符输出流
抽象基类InputStreamOutputStreamReaderWriter
节点流FileInputStreamFileOutputStreamFileReaderFileWriter
缓冲流BufferInputStreamBufferOutputStreamBufferReaderBufferWriter
转换流InputStreamReaderOutputStreamWriter
对象流ObjectInputStreamObjectOutputStream
打印流PrintStreamPrintWriter
数据流DataInputStreamDataOutputStream

3. InputStream & Reader

InputStream 和 Reader 是所有输入流的基类

字节流 InputStream(典型实现:FileInputStream)

  • int read():从输入流中读取数据的下一个字节。返回 0 到 255 范围内的 int 字节值。如果因 为已经到达流末尾而没有可用的字节,则返回值 -1。

  • int read(byte[] b):从此输入流中将最多 b.length 个字节的数据读入一个 byte 数组中。如果因为已经到达流末尾而没有可用的字节,则返回值 -1。否则以整数形式返回实际读取的字节数。

  • int read(byte[] b, int off, int len):将输入流中最多 len 个数据字节读入 byte 数组。尝试读取 len 个字节,但读取的字节也可能小于该值。以整数形式返回实际读取的字节数。如果因为流位于文件末尾而没有可用的字节,则返回值 -1。

  • public void close() throws IOException:关闭此输入流并释放与该流关联的所有系统资源。

字符流 Reader(典型实现:FileReader)

  • int read():读取单个字符。作为整数读取的字符,范围在 0 到 65535 之间 (0x00-0xffff)(2个 字节的Unicode码),如果已到达流的末尾,则返回 -1

  • int read(char [] c):将字符读入数组。如果已到达流的末尾,则返回 -1。否则返回本次读取的字符数。

  • int read(char [] c, int off, int len):将字符读入数组的某一部分。存到数组cbuf中,从off处开始存储,最多读len个字 符。如果已到达流的末尾,则返回 -1。否则返回本次读取的字符数。

  • public void close() throws IOException:关闭此输入流并释放与该流关联的所有系统资源。

@Test
	void testIStream() {
		InputStream is = null;
		try {
			is = new FileInputStream(new File("./a.txt"));
			byte[] buf = new byte[1024];
			while(is.read(buf) != -1) {
				System.out.write(buf);
			}
			
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			// 关闭流
			if (is != null) {
				try {
					is.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
	
	@Test
	void testIStream01() {
		InputStream is = null;
		try {
			is = new FileInputStream(new File("I:\\java\\java_se\\2022\\day22\\src\\com\\openlab\\day22\\TestCollections.java"));
			byte[] buf = new byte[1024];
			while(is.read(buf) != -1) {
				System.out.write(buf);
			}
			
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			// 关闭流
			if (is != null) {
				try {
					is.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
	
	@Test
	void testIStream02() {
		InputStream is = null;
		try {
			is = new FileInputStream(new File("I:\\java\\java_se\\2022\\day22\\src\\com\\openlab\\day22\\TestCollections.java"));
			byte[] buf = new byte[1024];
			int len = 0;
			while((len = is.read(buf)) != -1) {
				System.out.write(buf, 0, len);
			}
			
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			// 关闭流
			if (is != null) {
				try {
					is.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

程序中打开的文件 IO 资源不属于内存里的资源,垃圾回收机制无法回收该资源,所以应该显式关闭文件 IO 资源,一定要记得关闭流!!!

FileInputStream 从文件系统中的某个文件中获得输入字节。FileInputStream 用于读取非文本数据之类的原始字节流。要读取字符流,需要使用 FileReader

4. OutputStream & Writer

OutputStream 和 Writer 也非常相似:

  • void write(int b/int c)

    • OutputStream:将指定的字节写入此输出流。向输出流写入一个字节。要写 入的字节是参数 b 的八个低位。b 的 24 个高位将被忽略。 即写入0~255范围的。

    • Writer:写入单个字符。要写入的字符包含在给定整数值的 16 个低位中,16 高位被忽略。 即 写入0 到 65535 之间的Unicode码。

  • void write(byte[] b/char[] cbuf)

    • OutputStream:将 b.length 个字节从指定的 byte 数组写入此输出流。write(b) 的常规协定是:应该 与调用 write(b, 0, b.length) 的效果完全相同。

    • Writer:写入字符数组。

  • void write(byte[] b/char[] buff, int off, int len)

    • OutputStream:将 b.length 个字节从指定的 byte 数组写入此输出流。write(b) 的常规协定是:应该 与调用 write(b, 0, b.length) 的效果完全相同。

    • Writer:写入字符数组的某一部分。从off开始,写入len个字符

  • void flush()

    • OutputStream:刷新此输出流并强制写出所有缓冲的输出字节,调用此方法指示应将这些字节立 即写入它们预期的目标。

    • Writer:刷新该流的缓冲,则立即将它们写入预期目标。

  • void close():需要先刷新,再关闭此流

因为字符流直接以字符作为操作单位,所以 Writer 可以用字符串来替换字符数组, 即以 String 对象作为参数

  • void write(String str):写入字符串。

  • void write(String str, int off, int len):写入字符串的某一部分。

FileOutputStream 从文件系统中的某个文件中获得输出字节。FileOutputStream 用于写出非文本数据之类的原始字节流。要写出字符流,需要使用 FileWriter

三、节点流

3.1 FileInputStream和FileReader

3.1.1 读取文件的步骤

1.建立一个流对象,将已存在的一个文件加载进流。

2.创建一个临时存放数据的数组。

3.调用流对象的读取方法将流中的数据读入到数组中。

4. 关闭资源。

3.1.2 FileInputStream

FileInputStream 是 InputStream (字节流输入流) 的典型实现。

        FileInputStream fis = null;
        try {
            // 1.建立一个流对象,将已存在的一个文件加载进流。
            fis = new FileInputStream(new File("info.txt"));
            // 2.创建一个临时存放数据的数组(大小4k对齐)。FileInputStream 是一个字节流
            byte[] bytes = new byte[1024];
            // 3.调用流对象的读取方法将流中的数据读入到数组中。
            int len = 0;
            while ((len = fis.read(bytes)) != -1) {
                // 方法1:
                // System.out.write(bytes, 0, len);
                // 方法2:
                System.out.println(new String(bytes, 0, len));
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 4. 关闭资源。
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

3.1.3 FileReader

FileReader 是 Reader (字符流输入流) 的典型实现。

        FileReader fr = null;
        try {
            // 1.建立一个流对象,将已存在的一个文件加载进流。
            fr = new FileReader(new File("info.txt"));
            // 2.创建一个临时存放数据的数组(大小4k对齐)。FileReader 是一个字符流
            char[] chars = new char[1024];
            // 3.调用流对象的读取方法将流中的数据读入到数组中。
            int len = 0;
            while ((len = fr.read(chars)) != -1) {
                System.out.println(new String(chars, 0, len));
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 4. 关闭资源。
            if (fr != null) {
                try {
                    fr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

3.2 FileOutputStream和FileWriter

3.2.1 写入文件的步骤

1.创建流对象,建立数据存放文件;

2.调用流对象的写入方法,将数据写入流;

3.关闭流资源,并将流中的数据清空到文件中。

3.2.2 FileOutputStream

FileOutputStream 是 OutputStream (字节流输出流) 的典型实现。

        FileOutputStream fos = null;
        try {
            // 1.创建流对象,建立数据存放文件;
            // fis = new FileOutputStream(new File("info.txt"));  // 则目录下有同名文件将被覆盖。
            fos = new FileOutputStream(new File("info.txt"), true); // 在文件内容末尾追加内容。
            // 2.调用流对象的写入方法,将数据写入流;FileOutputStream 是一个字节流
            fos.write("HelloWorld!".getBytes()); // 调用getBytes()方法获取一个字节数组,将字节数组写入流
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 3. 关闭资源。
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

3.2.3 FileWriter

FileWriter 是 Writer (字符流输出流) 的典型实现。

        FileWriter fw = null;
        try {
            // 1.创建流对象,建立数据存放文件;
            fw = new FileWriter(new File("info.txt")); // 覆盖目录下同名文件。
            // 2.调用流对象的写入方法,将数据写入流;FileOutputStream 是一个字节流
            String msg = "HelloWorld!";
            fw.write(msg.toCharArray()); // 调用toCharArray()方法获取一个字符数组,将字符数组写入流
            fw.write("HelloWorld!"); // 将字符串写入流
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 3. 关闭资源。
            if (fw != null) {
                try {
                    fw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

练习:读取一个文件写入到另一个文件中

        FileReader fr = null;
        FileWriter fw = null;
        try {
            fr = new FileReader(new File("test.iml"));
            fw = new FileWriter(new File("info.txt"));
            char[] chars = new char[1024];
            int len = 0;
            while ((len = fr.read(chars)) != -1) {
                fw.write(chars, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fr != null) {
                try {
                    fr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fw != null) {
                try {
                    fw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

注意:

1. 定义文件路径时,注意:可以用“/”或者“\\”。

2. 在读取文件时,必须保证该文件已存在,否则报异常。

3. FileInputStream、FileOutputStream字节流一定要通过字节数组进行读取和写入,目的是为了防止内存过大,导致出现OOMError。

4. 字节流操作字节,比如:.mp3,.avi,.rmvb,mp4,.jpg,.doc,.ppt

5. 字符流操作字符,只能操作普通文本文件。最常见的文本文 件:.txt,.java,.c,.cpp 等语言的源代码。尤其注意.doc,excel,ppt这些不是文本文件。

6. 在写入一个文件时,如果使用构造器FileOutputStream(file),则目录下有同名文 件将被覆盖。如果使用构造器FileOutputStream(file, true),则目录下的同名文件不会被覆盖, 在文件内容末尾追加内容。

四、缓冲流

为了提高数据读写的速度,Java API提供了带缓冲功能的流类,在使用这些流类时,会创建一个内部缓冲区数组,缺省使用8192个字节(8Kb)的缓冲区

public
class BufferedInputStream extends FilterInputStream {

    private static int DEFAULT_BUFFER_SIZE = 8192;

缓冲流要“套接”在相应的节点流之上,根据数据操作单位可以把缓冲流分为:

  • BufferedInputStream 和 BufferedOutputStream

  • BufferedReader 和 BufferedWriter

当读取数据时,数据按块读入缓冲区,其后的读操作则直接访问缓冲区

当使用BufferedInputStream读取字节文件时,BufferedInputStream会一次性从文件中读取8192个(8Kb),存在缓冲区中,直到缓冲区装满了,才重新从文件中读取下一个8192个字节数组。

向流中写入字节时,不会直接写到文件,先写到缓冲区中直到缓冲区写满,BufferedOutputStream才会把缓冲区中的数据一次性写到文件里。使用方法 flush() 可以强制将缓冲区的内容全部写入输出流

关闭流的顺序和打开流的顺序相反。只要关闭最外层流即可,关闭最外层流也会相应关闭内层节点流。

flush()方法的使用:手动将buffer中内容写入文件

如果是带缓冲区的流对象的close()方法,不但会关闭流,还会在关闭流之前刷新缓冲区,关闭后不能再写出。

  • 9
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Golang_HZ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值