Java基础知识每日总结(19)---Java输入输出流、文件、递归

输入输出流、文件、递归

在变量、数组和对象中存储数据是暂时的,程序结束后它们则会丢失。为了能够永久地保存程序创建的数据,需要将其保存在磁盘文件中。这样以后就可以在其他程序中使用它们。Java的I/O技术可以将数据保存到文本文件、二进制文件甚至是ZIP压缩文件中。以达到永久性保存数据的要求。掌握I/O处理技术能够提高对数据的处理能力。

1.File类
java.io包中封装了用于输入输出的类。io包中唯一代表磁盘文件本身的File类定义了一些与操作系统无关的方法来操作磁盘文件。通过File实例可以获取文件本身的一些信息,例如文件名、文件长度、文件读写权限等。通过File类的一些方法,还可实现文件的创建、删除、重命名等操作。
可以通过以下3种方法创建一个File对象:

  • 指定文件的路径名 public File(String pathname)
  • 通过给定的父路径和子路径字符串,创建File对象 public File(String parent,String child) 参数说明:parent是父路径字符串,如一个文件夹的路径;child是子路径字符串,如该文件夹下的一个文件或目录
  • 使用一个File对象与字符串创建File对象 public File(File parent,String child) 参数说明:parent是一个File对象;child是该File对象对应的文件夹中的一个文件或目录
    File类提供了很多操作文件的方法,常用的:
    在这里插入图片描述

public boolean createNewFile() throws IOException
public boolean mkdirs()
public boolean delete()
public String getPath() //获取相对路径
public String[] list()
public File[] listFiles()

renameTo方法如果路径名相同就是改名,如果路径名不同就是改名并剪切;list方法获取该文件对象下的所有文件或目录的名字组成的一个数组;listFiles方法获取该文件对象下的所有文件或目录并将它们变成File对象组成一个File数组并返回。
注:Java中的删除(delete方法)是不去电脑回收站的。

2.FilenameFilter
①简介
FilenameFilter是一个接口,它是文件名称过滤器,可以与File类的list与listFiles方法搭配使用来筛选符合规则的文件名。

②方法
此接口中只有一个方法:boolean accept(File dir, String name),此方法的参数符合File类的第三种构造方法。File类中使用的构造方法和方法:
public File(File parent, String child)
public String[] list(FilenameFilter filter)
public File[] listFiles(FilenameFilter filter)

③实例
写程序,将D盘中的文件输出:

File f=new File("D://");
String[] s = f.list(new FilenameFilter() {   //匿名内部类
	
	@Override
	public boolean accept(File dir, String name) {
		// TODO Auto-generated method stub
		File f2=new File(dir,name);
		boolean b = f2.isFile();
		return b;
	}
});
for(String s2:s) {
	System.out.println(s2);
}

3.字节输入输出流
流是一组有序的数据序列,根据操作的类型,可将其分为输入流和输出流两种。I/O流提供了一条通道程序,可以使用这条通道把源中的字节序列送到目的地。虽然I/O流经常与磁盘文件存取有关,但是程序的源和目的地也可以是键盘、鼠标、内存或显示器窗口等。
程序通过输入流可以读取源中的数据,例
在这里插入图片描述
程序通过向输出流中写数据把信息传递到目的地。输出流的目标可以是文件、网络、压缩包和其他数据输出目标,输出模式:
在这里插入图片描述
字节流用于处理二进制数据的读取和写入,以字节为单位,InputStream类和OutputStream类是字节流的抽象基类,它们定义了数据流读取和写入的基本方法。
①标准输入流
InputStream类是字节输入流的抽象基类,是所有字节输入流的父类。定义了操作输入流的各种方法:
在这里插入图片描述
注:使用read方法当读到文件末尾时返回-1,这里可以与循环结合来读文件中的全部内容 。
public int read(byte[] b) throws IOException
此方法为一次读取一个字节数组的内容,返回实际读取到数组的长度,如果文件中有回车换行符,这也会在数组中存储。注意最后读入的长度小于数组的长度时,剩余数组部分的内容不变。当读到文件尾时返回-1。

byte[] b=new byte[1024];
int len;
while((len = fis.read(b))!=-1) {
	System.out.println(new String(b,0,len));
}

②标准输出流
OutputStream类是字节输出流的抽象类,该类定义了所有输出流的操作方法:
在这里插入图片描述
③文件输入流
FileInputStream文件字节输入流,可以从指定路径的文件中读取字节数据。该类继承自InputStream类。并实现了读取输入流的各种方法。可以通过以下两种方式,创建FileInputStream实例:

  • 使用File作为方法参数。public FileInputStream(File file) throws FileNotFoundException
  • 使用文件路径作为方法参数。 public FileInputStream(String name) throws FileNotFoundException

④文件输出流
与FileInputStream类对应,FileOutputStream类实现向文件中写数据。该类继承自OutputStream类,并实现了输出数据的各种方法。创建文件字节输出流常用的构造方法:

  • 使用File类型的文件对象,创建与文件关联的FileOutputStream类对象。public FileOutputStream(File file) throws FileNotFoundException
  • 根据指定的文件路径和文件名称,创建FileOutputStream实例。 public FileOutputStream(String name) throws FileNotFoundException name为文件的路径,如果文件不存在自动创建文件

⑤缓冲输入流
缓存流可以说是I/O的一种性能优化。缓存流为I/O流增加了内存缓存区。缓存区的大小,取决于其所在的操作系统、可用的内存空间以及机器配置。BufferedInputStream对象位于InputStream对象之前。BufferedInputStream类读取文件的过程:
在这里插入图片描述
BufferedInputStream类常用的构造方法:

  • 由InputStream类为对象创建BufferedInputStream实例。 方法声明:new BufferedInputStream(InputStream in) 参数说明:in是输入流对象
  • 创建指定缓冲区大小的BufferedInputStream。 方法声明:new bufferedInputStream(InputStream ins,int size) 参数说明:ins是底层输入流。size是指定缓冲区的大小

⑥缓冲输出流
BufferedOutputStream缓存字节输出流。OutputStream向文件中写数据时如果一个字节的写速度非常慢,但是如果以数组为单位写就会快很多,此时的数组就像一个缓冲区。使用BufferedOutputStream输出信息和OutputStream输出信息完全相同,只不过BufferedOutputStream类有一个flush()方法将缓存区中数据强制输出完。该类构造方法:

  • 创建一个新的缓存输出流,以将数据写入指定的底层输出流。 public BufferedOutputStream(OutputStream out) ,这里的参数为多态
  • 创建一个新的缓存输出流,以将具有指定缓存区大小的数据,写入指定的底层输出流。public BufferedOutputStream(OutputStream out, int size) 参数说明: size是缓冲区大小

⑦数据输入流
数据输入流允许应用程序以与机器无关方式从底层输入流中读取基本Java数据类型。即当读取数值时,不必再关心这个数值应当是多少个字节。构造方法:
public DataInputStream(InputStream in)
DataInputStream类常用的方法:
在这里插入图片描述
⑧数据输出流
数据输出流(DataOutputStream)允许应用程序以适当方式将基本Java数据类型写入输出流中,然后,应用程序可以使用数据输入流将数据读入。构造方法:
public DataOutputStream(OutputStream out)
DataOutputStream类的常用方法:
在这里插入图片描述
DataOutputStream类提供了3种写入字符串的方法,分别为:writeBytes()、writeChars()、writeUTF()。由于Java中的字符是uincode编码,是双字节的。writeBytes只是将字符串中的每一个字符的低字符内容写入目标设备中;而writeChars将字符串中的每一个字符的两个字节的内容都写到目标设备中;writeUTF对字符串按照UTF编码后的字节长度,然后才是每一个字节的UTF编码。
DataInputStream类只提供了一个readUTF()方法返回字符串。这是因为要在一个连续的字节流读取一个字符串,如果没有特殊的标记作为一个字符串的结尾,并且事先也不知道这个字符串的长度,这样就无法知道读取什么位置才是这个字符串的结束。DataOutputStream类中只有writeUTF()方法向目标设备中写入字符串的长度,所以,只有通过readUTF()方法能准确地读回写入字符串。

⑨数据的换行和追加写入
Windows下的换行符为’\r\n’,在需要的地方将它写入流即可实现换行。
追加写入是在原本文件的内容上继续写入一些东西,这与类的构造方法有关,如public FileOutputStream(File file, boolean append) throws FileNotFoundException,当append为true时,可以追加写入。

4.字符输入输出流
由于汉字在文件中占用两个字节,如果使用字节流,读取不好可能会出现乱码现象。使用字符输入输出流可以避免这个现象。字节流与字符流的区别是:字节流以字节为单位传送数据,可以是任何类型的数据,例如文本、音频、视频、图片等;而字符流以字符为单位传送数据,只能传送文本类型的数据。

①Reader类
字符流用于处理字符数据的读取和写入。它以字符为单位。Reader类与Writer类是字符的抽象类,它们定义了字符流读取和写入的基本方法,各个子类会依其特点实现或覆盖这些方法。
注:计算机中每个汉字的显示是占用两个字节,而在Java语言中,字符类型的数据也是双字节,所以Java中的字符类型可以容纳汉字信息。
Reader类定义了操作字符输入流的各种方法,该类的常用方法:
在这里插入图片描述
②Writer类
Writer类用于解决字符数据的输出流,该类是所有字符输出流的抽象类,子类必须实现的方法仅有write(char[],int off,int len)、flush()和close()。Writer类的常用方法:
在这里插入图片描述
用字符流写数据时有可能代码正确但是没有结果,此时有一种可能:写的数据在字符流的缓冲中没有出来,此时就需要使用flush方法让缓冲中的数据写出来或者使用close方法,因为close方法的底层在使用前会先调用flush方法。

③FileReader类
FileReader类继承自类Reader,并实现了读取字符输入流的各种方法。可以从指定路径中读取字符数据。该类提供了实现读取字符输入流的各种方法。该类的构造方法:

  • 在给定从中读取数据的File对象,创建一个FileReader实例。 方法声明:new FileReader(File file) 参数说明:file是要读入数据的文件对象
  • 根据给定的文件名创建FileReader实例。 方法声明:new FileReader(String fileName) 参数说明:fileName表示与系统有关的文件名

InputStreamReader = FileInputStream + 编码表(默认GBK)
FileReader = FileInputStream + 编码表(默认GBK)

④FileWriter类
FileWriter类实现了文件字符输出流,该类继承自Writer类。实现了写入字符文件的便捷类,文件字符输出流关联指定路径的文件,数据通过文件字符输出流以字符为单位输出并保存到文件中。

  • 使用指定的File类型参数,创建与该文件关联的FileWriter实例。 方法声明:new FileWriter(File file) 参数说明:file是要写入数据的文件对象
  • 根据指定的文件名称和路径,创建关联该文件的FileWriter类实例。 方法声明:new FileWriter(String path) 参数说明:path表示与系统有关的文件名

OutputStreamWriter = FileOutputStream + 编码表(默认GBK)
FileWriter = FileOutputStream + 编码表(默认GBK)

⑤Scanner类
Scanner类实现了使用正则表达式来解析基本类型和字符串的简单文本扫描器。该类位于java.util包中。Scanner使用分隔符模式将其输入分解为标记,该类有多种构造方法:

  • 创建一个从指定文件扫描的Scanner对象。 方法声明:public Scanner(File source) 参数说明:source是要扫描的文件对象
  • 创建扫描指定字符串的Scanner对象。 方法声明:public Scanner(String source) 参数说明:source是要扫描的字符串
  • 创建对指定输入流扫描的Scanner对象。 方法声明:public Scanner(InputStream source) 参数说明:source是要扫描的输入流

Scanner提供了不同的方法可将得到的标记转换为不同类型的值:
在这里插入图片描述
⑥PrintWriter类
System.out是PrintStream类的一个实例对象,PrintStream类位于java.io包中,该类提供了一系列的print()和pritln()方法,用于信息的输出,可以输出任意类型的数据。io包中还提供了一个与PrintStream对应的PrintWriter类。PrintWriter即使遇到换行符(’\n’)也不会自动清空缓存区。只在设置了自动刷新(autoflush)模式下使用了println()方法后才会自动清空缓存区。
PrintWriter类的构造方法:

  • 使用指定文件创建不具有自动行刷新的PrintWriter实例。public PrintWriter(File file) throws FileNotFoundException
  • 创建具有指定文件名称且不带自动行刷新的PrintWriter实例。public PrintWriter(String fileName) throws FileNotFoundException
  • 根据OutputStream实例,创建不带自动行刷新的PrintWriter实例。public PrintWriter(OutputStream out)

该类的常用方法:
在这里插入图片描述
Windows的文本换行是“\r\n”,而在Linux操作系统下的文本换行是“\n”。如果希望程序生成平台相关的文本换行,而不是在各种平台下都用“\n”作为文本换行,则应该使用PrintWriter的println()方法。该方法可以根据不同的操作系统而生成相应的换行符。

关于自动刷新:一般使用Writer的子类写数据时要使用flush方法或close方法来将流中数据挤出去,但是PrintWriter(打印流)可以设置自动刷新,不需要手动调用方法。设置自动刷新:
public PrintWriter(OutputStream out, boolean autoFlush)
public PrintWriter(Writer out, boolean autoFlush)

当第二个参数为true时表示启动了自动刷新,但只有println, printf, format三个方法可以自动刷新。此时,println=write+newLine+flush

⑦OutputStreamWriter类
OutputStreamWriter是一个字符流,它的构造方法有:

  • public OutputStreamWriter(OutputStream out)
  • public OutputStreamWriter(OutputStream out, String charsetName) throws UnsupportedEncodingException

该类的构造方法的参数是字节输出流的子类(多态),所以它是一个将字节流的数据以默认编码方式转换为字符流的数据的转换流。第二个构造方法指定了编码方式。

⑧BufferedWriter特有方法
public void newLine() throws IOException
该方法为写入的数据换行,使用与系统有关的换行符,即为数据加上与系统有关的换行符。

⑨BufferedReader特有方法
public String readLine() throws IOException
该方法读文件的一行数据,如果读到了文件尾返回null。

5.内存操作流
①简介
内存操作流一般用于临时处理信息,因为临时信息不需要保存,使用后就会从内存中消失。

②ByteArrayInputStream和ByteArrayOutputStream
ByteArrayOutputStream中的数据被写入一个byte数组,此数组会随着数据的不断写入而自动增长。关闭此类无效,类中的方法在流关闭后仍可被调用且不会出现异常。ByteArrayInputStream有一个内部缓冲区,它中存储了从流中读取的字节,关闭此类也无效。构造方法:
public ByteArrayInputStream(byte[] buf)
public ByteArrayOutputStream()

③CharArrayReader和CharArrayWriter
④StringReader和StringWriter
上面的两种流与②类似。

6.StringBuffer类和StringBuilder类
字符串是常量,对于创建成功的字符串对象,它的长度是固定的,内容不能被改变和编译。虽然使用拼接符“+”可以达到附加新字符或字符串的目的,但“+”会产生一个新的String实例,会在内存中创建新的字符串对象。如果重复的对字符串进行修改,将极大地增加系统开销。而JDK5.0新增了可变的字符序列StringBuilder类,会大大提高频繁做字符串增加的效率。
新创建的StringBuilder对象初始容量是16个字符,可以自行指定初始长度。如果附加的字符超过可容纳的长度,则StringBuilder对象将自动增加长度以容纳被附加的字符。若要使用StringBuilder最后输出字符串结果,可使用toString()方法。

①StringBuilder与StringBuffer
java.lang包中定义了StringBuffer类,该类是线程安全的可变字符序列。它是一个类似于String的字符串缓冲区,但不能修改。StringBuffer类是线程安全的。可以在必要时对字符串更新方法进行同步。

StringBuffer上主要操作是append()和insert()方法,该类重载了这些方法,以接受任意类型的数据。每个方法都能有效地将给定的数据转换成字符串,然后将字符串的字符追加或插入到字符串缓冲区中。

StringBuilder是JDK1.5新增的类,是StringBuffer的一个简易替换,如果字符串缓冲区被单个线程使用时,建议优先采用StringBuilder类,StringBuilder并不是线程安全的。因此在大多数情况下,StringBuilder类要比StringBuffer类运行速度快。StringBuilder与StringBuffer类提供的外部方法是一样的。如果需要同步,则建议使用StringBuffer。

②构造方法
public StringBuffer()
public StringBuffer(int capacity)
public StringBuffer(String str)
public StringBuffer(CharSequence seq)

注:StringBuffer和StringBuilder构造方法类似
CharSequence是一个接口,String类实现了此接口;capacity为容量,第二个方法创建了指定容量的字符串缓冲区;字符串容量和字符串长度是不同的概念,前者是理论上存储字符串的长度,后者是实际存储字符串的长度,初始容量为16字符;第三个方法创建了指定字符串内容的字符串缓冲区对象,此时容量增加了内容的长度。

③常用方法
注:StringBuffer和StringBuilder方法类似
append()方法:
public StringBuffer append(String str)
public StringBuilder append(Object obj)

StringBuffer类重载了append方法,该方法用于向字符串缓冲区中添加内容,通过该方法的多个重载形式,可实现接收任何类型的数据。例如int、boolean、char、String、double或者另一个字符串缓冲区等。方法返回值为方法调用者本身。

StringBuffer sb=new StringBuffer();
sb.append("nihao");
sb.append(3).append(false).append(3.4).append('f');
System.out.println(sb);  //nihao3false3.4f

intsert(int offset,arg)方法:
该方法用于向字符串缓冲区中的指定位置插入任意类型数据。StringBuffer类重载了该方法,通过该方法的不同重载形式,可实现向字符串缓冲区中插入int、float、char、boolean等基本数据类型或其他对象。
public StringBuffer insert(int offset,Object obj)
参数说明:offset是字符串缓冲区的位置。该参数必须大于等于0,且小于等于此序列的长度。obj是即将插入到此缓冲区的任意类型的数据或对象。此方法返回方法调用者本身。

StringBuffer sb=new StringBuffer();
sb.append("nihao");
sb.insert(3, "haha").insert(5, 3);
System.out.println(sb);   //nihha3haao

delete(int start, int end)方法:
移除此缓冲区字符串中的字符。该字符串从指定的start处开始,一直到索引end - 1处的字符(包括左边,不包括右边);如果不存在这种字符,则一直到序列尾部;如果start等于end,则不发生任何更改。
public StringBuffer delete(int start,int end)
public StringBuffer deleteCharAt(int index)

参数说明:start是将要删除的字符串的起点位置;end是将删除字符串的终点位置。index为删除指定位置上的一个元素。

StringBuffer sb=new StringBuffer();
sb.append("nihao");
System.out.println(sb);   //nihao
sb.deleteCharAt(3);
System.out.println(sb);   //niho
sb.delete(0, sb.length());
System.out.println("sb:"+sb);   //sb:

字符串缓冲区在底层仍会将字符串化为字符数组,所以下标还是从0开始。

replace()方法:
public StringBuffer replace(int start,int end,String str)

StringBuffer sb=new StringBuffer();
sb.append("HelloWorldJava");
System.out.println(sb);   //HelloWorldJava
sb.replace(5, 10, "haha");
System.out.println(sb);   //HellohahaJava

将字符串缓冲区start位置开始,end-1位置的字符串替换为字符串str

reverse()方法:
public StringBuffer reverse()
将字符串缓冲区内容反转

StringBuffer sb=new StringBuffer();
sb.append("HelloWorldJava");
System.out.println(sb);   //HelloWorldJava
sb.reverse();
System.out.println(sb);   //avaJdlroWolleH

substring()方法:
public String substring(int start)
public String substring(int start,int end)

StringBuffer sb=new StringBuffer();
sb.append("HelloWorldJava");
String s = sb.substring(5);
System.out.println(s);   //WorldJava
String s2 = sb.substring(5, 10);
System.out.println(s2);   //World
System.out.println(sb);   //HelloWorldJava

注:此方法返回值为String

④StringBuffer和String的相互转换
注:不能将一个字符串直接赋值给StringBuffer对象
String --> StringBuffer : 通过StringBuffer的构造方法或append方法
StringBuffer --> String : 通过String的构造方法或StringBuffer的toString方法

⑤要点
注:StringBuffer和StringBuilder类似
使用StringBuffer做字符串拼接
在程序中,为了节省内存资源,推荐使用StringBuffer的append方法做字符串拼接,拼接完成后用toString返回字符串。

使用StringBuffer的reverse方法反转字符串
注:可以通过此方法来看字符串是否对称

String、StringBuffer和StringBuilder的区别
String是内容不可变的,而StringBuffer和StringBuilder是内容可变的;
StringBuffer是同步的,线程安全,效率低;StringBuilder不是同步的,线程不安全,效率高

StringBuffer和数组的区别
StringBuffer可以放任意类型的数据,但是最终都会变成字符串;一个数组只能放同一类型的数据。

StringBuffer做形参
StringBuffer类型的变量作为形参传递时,如果是两个变量的直接赋值那么就与基本数据类型做形参一样,如果是调用方法那么就与引用数据类型做形参一样,此处与String不同。例,

public static void change(StringBuffer sb, StringBuffer sb2) {
		sb = sb2;
		sb2 = sb2.append(sb);
	}
public static void main(String[] args) {
		StringBuffer sb = new StringBuffer("haha");
		StringBuffer sb2 = new StringBuffer("hahaha1");
		change(sb, sb2);
		System.out.println(sb + "  " + sb2);   //haha  hahaha1hahaha1
	}

7.递归
①简介
递归就是一种在方法中调用方法本身的一种现象。例,

public static int recursion(int a) {
	if (a == 1) {
		return 1;
	} else {
		return a * recursion(a - 1);
	}
}

public static void main(String[] args) {
	System.out.println(recursion(8));   //40320
}

获取指定目录下指定后缀的文件:

public static void recursion(File f) {
	File[] f2 = f.listFiles();
	for (File ff : f2) {
		if (ff.isDirectory()) {
			recursion(ff);
		} else {
			if (ff.getName().endsWith(".java")) {
				System.out.println(ff);
			}
		}
	}
}

public static void main(String[] args) {
	File f = new File("D:\\JAVA");
	recursion(f);
}

②要点
递归一定要有结束递归的出口,否则就是死递归
递归的次数不能太多,否则就会内存溢出
构造方法中不能出现递归

8.随机访问流
①简介
RandomAccessFile是一个随机访问文件流,它是Object类的子类,属于java.io包,不属于InputStream和OutputStream但有它们的功能,即该流既有读的功能又有写的功能。

②构造方法
public RandomAccessFile(File file, String mode) throws FileNotFoundException
public RandomAccessFile(String name, String mode) throws FileNotFoundException

mode为操作文件的模式,它的值有"r",“rw”,“rws”,“rwd” 。

9.合并流
①简介
SequenceInputStream是一个合并流,它将多个输入流连在一起,当一个输入流读完时第二个输入流继续输入。

②构造方法
public SequenceInputStream(InputStream s1, InputStream s2)
public SequenceInputStream(Enumeration<? extends InputStream> e)

第一个方法合并两个流,第二个方法合并多个流。Enumeration是Vector类的 public Enumeration< E> elements() 方法的返回值。

10.序列化流
ObjectOutputStream是一个序列化流,它可以将对象以流一样的方式存入文本文件或网络传输。
ObjectInputStream是一个反序列化流,它将流对象数据还原为对象。这两个类都要求要传输的类实现序列化接口Serializable。
在对象类中还要设置static final long serialVersionUID值,否则类的内容一旦修改再使用流时会出现InvalidClassException异常。
使用transient关键字可以声明不需要序列化的成员变量。

11.Properties类
①简介
Properties是java.util包下的一个属性集合类,它继承自Hashtable<K,V>类,它可以和IO流相结合使用。它可以保存在流中或从流中加载,属性列表中的每个键及对应值都是一个字符串。
注:Properties不是泛型类。

②特有方法
public Object setProperty(String key, String value)
public String getProperty(String key)
public Set stringPropertyNames()

第一个方法是添加键值对,其底层本质是调用了父类的put方法;第二个方法是根据键获取对应的值;第三个方法是获取键的集合。

③与IO有关的方法
public void load(Reader reader)
public void store(Writer writer, String comments) throws IOExceptionthrows IOException

第一个方法是用流读取键值对数据到集合中;第二个方法是将集合中的数据用流写出去,comments是对集合中属性列表的一个描述。

12.管道流

13.NIO
NIO代表了New IO,一个新的IO,它在JDK4之后出现,有一个专属的包java.nio包。这个包有一些特殊的接口或类,例:Class Buffer。在JDK7之后它又出现了新的改变,例:Interface Path,Class Files,Class Paths。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值