JAVA之输入输出(四)

NIO

传统的输入流、输出流都是通过字节的移动来实现的(即使不直接处理字节流,在底层的实现还是依赖于字节处理),也就是说,面向流的输入、输出系统一次只能处理一个字节,因此面向流的输入、输出系统通常效率不高。

JAVA新IO概述

新旧IO的目的都是用于输入输出,不高新IO采用了不同的方式来处理——新IO才哦用内存映射文件的方式来处理输入、输出,新IO将文件或文件的一段区域映射到内存中,这样就可以像访问内存一样访问文件了(模拟了操作系统虚拟内存的概念),通过这种方式来进行输入、输出比传统的输入、输出要快得多。

Channel(通道)和Buffer(缓冲)是新IO中的两个核心对象,Channel是对传统的输入、输出的模拟,在新IO系统中所有数据都要通过通道传输;Channel和传统的Stream最大的区别在于其提供了一个map()方法,通过该方法将一块数据映射到内存。

Buffer可以被理解成一个容器,其本质是一个数组,发送到Channel的所有对象首先要放到Buffer中,而从Channel读取的数据也必须先放到Buffer中。Buffer就像之前说的竹筒,不过Channel可以直接把文件的某一块区域映射成Buffer。

此外,新IO提供了用于将Unicode字符串映射出字节序列以及逆映射的Charset类,也提供了用于支持非阻塞式输入、输出的Selector类。

Buffer

Buffer可以保留多个相同类型的数据,所有基本类型数据,除了boolean,剩下都有对应的Buffer(比如ByteBuffer)

MappedByteBuffer用于表示Channel将磁盘文件的部分或全部内容映射到内存中得到的结果,通常MappedByteBuffer对象由Channel的map()方法返回。

Buffer有三个重要的概念:
容量:Buffer的最大数据量,缓冲区的容量不能为负值,创建后不能改变。
界限:第一个不能读写的位置。
位置:用于指明下一个可以被读取或写入的缓冲区的位置索引。
另外还有一个mark,运行将position直接定位到mark处。
0<=mark<=position<=limit<=capacity

Buffer的主要作用就是装入数据,然后输出数据。开始是position为0,limit为capacity,程序可通过put()方法向Buffer中放入以下数据(或者从Channel中获取以下数据),没放入一些数据,Buffer的position相应地向后移动一些。

Buffer的flip()方法会将position设置为0,limit设置为之前的position,让Buffer为输出数据做准备。
输出完成后Buffer调用clear()方法,clear()方法不是清空数据,它仅仅是将position设置为0,limit设置为capacity,这样为再次向Buffer装入数据做准备。

当使用get,set方法处理数据时,Buffer既支持对单个数据的访问,也支持对批量数据的访问(以数组为参数)

put,get访问数据有相对和绝对两种,相对访问会改变position的值,绝对则不会。

package NIO测试;

import java.nio.CharBuffer;

public class BufferTest {
	public static void main(String  []args)
	{
		CharBuffer cb=CharBuffer.allocate(8);
		System.out.println("capacity: "+cb.capacity());
		System.out.println("limit :"+cb.limit());
		System.out.println("position: "+cb.position());
		cb.put('a');
		cb.put('c');
		cb.put('e');
		System.out.println("加入元素后,position为 "+cb.position());
		cb.flip();
		System.out.println("flip之后的position为 "+cb.position());
		System.out.println("此时的limit为 "+cb.limit());
		System.out.println("取出第一个元素"+cb.get());
		cb.clear();
		System.out.println("执行clear后,position为"+cb.position());
		System.out.println("执行clear后,limit为"+cb.limit());
		System.out.println(cb.get(2));
		
	}
}<strong>
</strong>
输出结果为:
可以清晰地看出,flip之后limit发生改变,clear之后数据并没有被清空。


ByteBuffer提供了一个allocateDirect()方法来直接创建Buffer,其创建成本更高,但是读取效率同样很高。因此,对于长期使用的Buffer,应当使用直接Buffer,否则就使用普通Buffer。


Channel

Channel可以直接将指定文件的部分或全部映射成Buffer。
程序不能直接访问Channel,读或写都不可以,Channel只能和Buffer进行交互。

所有的Channel都不应该通过构造器来直接创建,而是通过传统的节点的getChannel()方法来返回对应的Channel,不同节点流获得的Channel不同,

Channel中最常用的三类方法——map()、read()、write(),其中map()方法可以将Channel的部分或全部映射成ByteBuffer,而read()和write()用于从Buffer中读取或写入数据。

map的方法签名为MappedByteBuffer map(FileChannel.MapMode mode,long position,long size),第一个参数是执行映射时的模式,第二个和第三个用于指定映射范围。

虽然FileChannel既可以读也可以写,但是FileInputStream获取的FileChanel只能读,而FileOutputStream获取的FileChanel只能写。

RandomAccessFile中也包含了getChanel方法,RandomAccessFile返回的FileChannel()是只读的还是只写的,则取决于RandomAccessFile打开文件的模式。

package NIO测试;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class RandomAccessFileTest {
	public static void main(String []args)throws IOException
	{
		File f=new File("test.txt");
		try(
		RandomAccessFile raf=new RandomAccessFile(f,"rw");
		FileChannel fc=raf.getChannel();
				)
		{
			ByteBuffer buffer=fc.map(FileChannel.MapMode.READ_ONLY, 0, f.length());
			fc.position(f.length());
			fc.write(buffer);
		}
	}

}
test.txt在运行前是这样的:

运行后结果如下:

可以看到,运行成功。

由于RandomAccessFile在打开时是以rw模式打开的,所以对于的Channel既是可读的又是可写的。


字符集和Charset

之前说过,计算机中所有的文件在底层都是二进制文件,也就是字节码,而字节码转换为字符,需要涉及编码(Encode)和解码(Decode),把明文的字符序列转化成计算机能理解的二进制序列称为编码,把二进制序列转化成普通人能看懂的明文字符序列称为解码。

JAVA默认使用Unicode字符集,但很多系统不支持Unicode字符集,那么从系统中读取数据到JAVA程序时,就有可能出现乱码的问题。

JDK1.4提供了Charset来处理字节序列和字符序列(字符串)之间的转化关系,该类包含了创建编码器和解码器的方法,还提供了获取Charset所支持的字符集的方法,Charset类是不可变的。

所谓字符集,就是将字符一一编号,使得字符和编号相对应。

下面的程序列举了所以JAVA支持的字符集。
package NIO测试;

import java.nio.charset.Charset;
import java.util.SortedMap;

public class CharsetTest {
public static void main(String []args)
{
	SortedMap<String,Charset> map=Charset.availableCharsets();
	for(String alias:map.keySet())
	{
		System.out.println(alias+"------>"+map.get(alias));
	}
	}
}
该程序运行结果如下:
Big5------>Big5
Big5-HKSCS------>Big5-HKSCS
CESU-8------>CESU-8
EUC-JP------>EUC-JP
EUC-KR------>EUC-KR
GB18030------>GB18030
GB2312------>GB2312
GBK------>GBK
IBM-Thai------>IBM-Thai
IBM00858------>IBM00858
IBM01140------>IBM01140
IBM01141------>IBM01141
IBM01142------>IBM01142
IBM01143------>IBM01143
IBM01144------>IBM01144
IBM01145------>IBM01145
IBM01146------>IBM01146
IBM01147------>IBM01147
IBM01148------>IBM01148
IBM01149------>IBM01149
IBM037------>IBM037
IBM1026------>IBM1026
IBM1047------>IBM1047
IBM273------>IBM273
IBM277------>IBM277
IBM278------>IBM278
IBM280------>IBM280
IBM284------>IBM284
IBM285------>IBM285
IBM290------>IBM290
IBM297------>IBM297
IBM420------>IBM420
IBM424------>IBM424
IBM437------>IBM437
IBM500------>IBM500
IBM775------>IBM775
IBM850------>IBM850
IBM852------>IBM852
IBM855------>IBM855
IBM857------>IBM857
IBM860------>IBM860
IBM861------>IBM861
IBM862------>IBM862
IBM863------>IBM863
IBM864------>IBM864
IBM865------>IBM865
IBM866------>IBM866
IBM868------>IBM868
IBM869------>IBM869
IBM870------>IBM870
IBM871------>IBM871
IBM918------>IBM918
ISO-2022-CN------>ISO-2022-CN
ISO-2022-JP------>ISO-2022-JP
ISO-2022-JP-2------>ISO-2022-JP-2
ISO-2022-KR------>ISO-2022-KR
ISO-8859-1------>ISO-8859-1
ISO-8859-13------>ISO-8859-13
ISO-8859-15------>ISO-8859-15
ISO-8859-2------>ISO-8859-2
ISO-8859-3------>ISO-8859-3
ISO-8859-4------>ISO-8859-4
ISO-8859-5------>ISO-8859-5
ISO-8859-6------>ISO-8859-6
ISO-8859-7------>ISO-8859-7
ISO-8859-8------>ISO-8859-8
ISO-8859-9------>ISO-8859-9
JIS_X0201------>JIS_X0201
JIS_X0212-1990------>JIS_X0212-1990
KOI8-R------>KOI8-R
KOI8-U------>KOI8-U
Shift_JIS------>Shift_JIS
TIS-620------>TIS-620
US-ASCII------>US-ASCII
UTF-16------>UTF-16
UTF-16BE------>UTF-16BE
UTF-16LE------>UTF-16LE
UTF-32------>UTF-32
UTF-32BE------>UTF-32BE
UTF-32LE------>UTF-32LE
UTF-8------>UTF-8
windows-1250------>windows-1250
windows-1251------>windows-1251
windows-1252------>windows-1252
windows-1253------>windows-1253
windows-1254------>windows-1254
windows-1255------>windows-1255
windows-1256------>windows-1256
windows-1257------>windows-1257
windows-1258------>windows-1258
windows-31j------>windows-31j
x-Big5-HKSCS-2001------>x-Big5-HKSCS-2001
x-Big5-Solaris------>x-Big5-Solaris
x-euc-jp-linux------>x-euc-jp-linux
x-EUC-TW------>x-EUC-TW
x-eucJP-Open------>x-eucJP-Open
x-IBM1006------>x-IBM1006
x-IBM1025------>x-IBM1025
x-IBM1046------>x-IBM1046
x-IBM1097------>x-IBM1097
x-IBM1098------>x-IBM1098
x-IBM1112------>x-IBM1112
x-IBM1122------>x-IBM1122
x-IBM1123------>x-IBM1123
x-IBM1124------>x-IBM1124
x-IBM1166------>x-IBM1166
x-IBM1364------>x-IBM1364
x-IBM1381------>x-IBM1381
x-IBM1383------>x-IBM1383
x-IBM300------>x-IBM300
x-IBM33722------>x-IBM33722
x-IBM737------>x-IBM737
x-IBM833------>x-IBM833
x-IBM834------>x-IBM834
x-IBM856------>x-IBM856
x-IBM874------>x-IBM874
x-IBM875------>x-IBM875
x-IBM921------>x-IBM921
x-IBM922------>x-IBM922
x-IBM930------>x-IBM930
x-IBM933------>x-IBM933
x-IBM935------>x-IBM935
x-IBM937------>x-IBM937
x-IBM939------>x-IBM939
x-IBM942------>x-IBM942
x-IBM942C------>x-IBM942C
x-IBM943------>x-IBM943
x-IBM943C------>x-IBM943C
x-IBM948------>x-IBM948
x-IBM949------>x-IBM949
x-IBM949C------>x-IBM949C
x-IBM950------>x-IBM950
x-IBM964------>x-IBM964
x-IBM970------>x-IBM970
x-ISCII91------>x-ISCII91
x-ISO-2022-CN-CNS------>x-ISO-2022-CN-CNS
x-ISO-2022-CN-GB------>x-ISO-2022-CN-GB
x-iso-8859-11------>x-iso-8859-11
x-JIS0208------>x-JIS0208
x-JISAutoDetect------>x-JISAutoDetect
x-Johab------>x-Johab
x-MacArabic------>x-MacArabic
x-MacCentralEurope------>x-MacCentralEurope
x-MacCroatian------>x-MacCroatian
x-MacCyrillic------>x-MacCyrillic
x-MacDingbat------>x-MacDingbat
x-MacGreek------>x-MacGreek
x-MacHebrew------>x-MacHebrew
x-MacIceland------>x-MacIceland
x-MacRoman------>x-MacRoman
x-MacRomania------>x-MacRomania
x-MacSymbol------>x-MacSymbol
x-MacThai------>x-MacThai
x-MacTurkish------>x-MacTurkish
x-MacUkraine------>x-MacUkraine
x-MS932_0213------>x-MS932_0213
x-MS950-HKSCS------>x-MS950-HKSCS
x-MS950-HKSCS-XP------>x-MS950-HKSCS-XP
x-mswin-936------>x-mswin-936
x-PCK------>x-PCK
x-SJIS_0213------>x-SJIS_0213
x-UTF-16LE-BOM------>x-UTF-16LE-BOM
X-UTF-32BE-BOM------>X-UTF-32BE-BOM
X-UTF-32LE-BOM------>X-UTF-32LE-BOM
x-windows-50220------>x-windows-50220
x-windows-50221------>x-windows-50221
x-windows-874------>x-windows-874
x-windows-949------>x-windows-949
x-windows-950------>x-windows-950
x-windows-iso2022jp------>x-windows-iso2022jp


在知道字符集的别名之后,程序就可以调用Charset的forName()方法来创建相应的Charset对象,forName()方法的参数就是相应字符集的别名。

Charset cs=Charset.forName("ISO-8859-1");

通过获得Charset对象之后,就可以通过该对象的newDecoder(),newEncoder()方法来返回对应的CharsetDecoder和CharsetEncoder。
下面举个例子,我在里面玩了一个最简单的加密解密,正常使用可以跳过这一步:
package NIO测试;


import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.IntBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;


public class CharsetTansform {
public static void main(String []args)throws Exception
{
Charset cn=Charset.forName("GBK");
CharsetEncoder cnEncoder=cn.newEncoder();
CharsetDecoder cnDecoder=cn.newDecoder();
CharBuffer cbuff=CharBuffer.allocate(8);
cbuff.put('孙');
cbuff.put('悟');
cbuff.put('空');
cbuff.flip();
ByteBuffer bbuff=cnEncoder.encode(cbuff);

for(int i=0;i<bbuff.capacity();i++)
{
byte x=bbuff.get(i);
x+=1;
bbuff.put(i, x);
System.out.print(bbuff.get(i)+" ");
}
cbuff=cnDecoder.decode(bbuff);
System.out.println("\n"+cbuff);
bbuff=cnEncoder.encode(cbuff);
for(int i=0;i<bbuff.capacity();i++)
{
byte x=bbuff.get(i);
x-=1;
bbuff.put(i,x);
System.out.print(bbuff.get(i)+" ");
}

System.out.println("\n"+cn.decode(bbuff));
}


}
输出结果为:

文件锁

使用文件锁可以有效地组织多个进程并发的修改同一个文件,所以现在的大部分操作系统都提供了文件锁机制。

JDK1.4的NIO,JAVA开始提供文件锁的支持。

在NIO中,JAVA提供了FileLock来支持文件锁定功能,在FileChannel提供了lock()、tryClock()方法可以获得文件锁FileLock对象。

lock()和tryLock()有着很大的区别——当lock()试图锁定某个文件,如果无法得到文件锁,程序将一直阻塞;而tryLock()是试图锁定文件,它将直接返回而不是阻塞,如果获得了文件锁,该方法返回文件锁,否则返回null。

此外,lock(long position,long size,boolean shared)和tryLock(long position.long size,boolean shared)都可以只锁定文件的部分内容。
当shared为true时,表明该锁是一个共享锁,它允许多观察进程来读取该文件时,但组织其他进程获得对该文件的文件锁。当shared为false,表明该锁是一个排它锁,它将锁住对该文件的读写,程序可以通过调用FileLock 的isShared来判断其是否会共享锁。

直接使用lock()或tryLock()方法获取的文件锁是排它锁。
package NIO测试;

import java.io.FileOutputStream;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;

public class FileLockTest {
	public static void main(String []args)throws Exception
	{
		try(
			FileChannel fc=new FileOutputStream("a.txt").getChannel()	)
		{
			FileLock lock=fc.tryLock();
			Thread.sleep(500);
			lock.release();
		}
	}

}<strong>
</strong>
在5秒之内,其他程序无法对a.txt文件进行修改。

文件锁虽然可以用于控制并发访问,但对于高并发的情形,还是推荐使用数据库来保存信息。


关于文件锁,还有以下几点:
在某些平台上,文件锁仅仅是建议性的,而不是强制性的。
在某些平台,不能同步的锁定一个文件并把它映射到内存中。
文件锁是由JAVA虚拟机所持有的,如果两个JAVA程序还是用同一个JAVA虚拟机以下,则他们不能对同一个文件进行加锁。
在某些平台上关闭FileChannel,会释放JAVA虚拟机在该文件的所有所,因此要避免对同一个被锁定的文件打开多个FileChannel。

JAVA 7 的NIO2

JAVA7对原NIO进行了重大改进:
1.提供了全面的文件IO和文件系统访问支持
2.基于异步Channel的IO

第一个进步表现为JAVA7 新增的java.noi,file包以及各个子包;第二个改进表现为JAVA7 在java.nio.channels包下增加了多个Asynchronous开头的Channel接口和类。JAVA7将这种改进称为NIO2

Path、Paths和Files核心API

早起的Java只提供了一个File类来访问文件系统,但File的功能有限,其不能利用特定文件系统的特效,访问效率也不高,此外,大多数方法在出错时仅返回失败,而不会返回异常信息。

NIO2引入的Path接口,Path接口代表了一个平台无关的平台路径。此外,NIO2还提供了Files、Paths两个工具类,其中Files包含了大量静态的工具方法来操作文件;Paths则包含了两个返回Path的静态工厂方法。

下面是Path接口的一些基本用法
package NIO2测试;

import java.nio.file.Path;
import java.nio.file.Paths;

public class PathTest {
	public static void main(String []args)throws Exception
	{
		Path path=Paths.get(".");
		System.out.println("path中包含的路径数量: "+path.getNameCount());
		System.out.println("path的根路径  "+path.getRoot());
		Path absolutePath=path.toAbsolutePath();
		System.out.println("path的绝对路径"+absolutePath);
		System.out.println("absolutePath的根路径 "+absolutePath.getRoot());
		System.out.println("absolutePath所包含的路径数量"+absolutePath.getNameCount());
		//System.out.println(absolutePath.getName(3));
		Path path2=Paths.get("g:","publish","codes");
		System.out.println(path2);
	}

}


下面是Files类的用法
package NIO2测试;

import java.io.FileOutputStream;
import java.nio.charset.Charset;
import java.nio.file.FileStore;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;

public class FilesTest {
	public static void main(String []args)throws Exception
	{
		Path path=Paths.get("E:\\Eclipse\\输入输出\\src\\NIO2测试\\FilesTest.java");
		Files.copy(path, new FileOutputStream("a.txt"));
		System.out.println("FileTest.java是否为隐藏文件 "+Files.isHidden(path));
		List<String>lines=Files.readAllLines(path,Charset.forName("gbk"));
		System.out.println(lines);
		System.out.println("Java文件的大小为 "+Files.size(path));
		List<String>poem=new ArrayList<>();
		poem.add("横看成岭侧成峰");
		poem.add("远近高低各不同");
		Files.write(Paths.get("poem.txt"), poem, Charset.forName("gbk"));
		Files.list(Paths.get(".")).forEach(paths->System.out.println(paths));
		Files.lines(path,Charset.forName("gbk")).forEach(line->System.out.println(line));
		FileStore cStore=Files.getFileStore(Paths.get("C:"));
		System.out.println("C:共有空间: "+cStore.getTotalSpace());
		System.out.println("C:可用空间: "+cStore.getUsableSpace());
		
		
		
	}

}

输出结果为:
FileTest.java是否为隐藏文件 false
[package NIO2测试;, , import java.io.FileOutputStream;, import java.nio.charset.Charset;, import java.nio.file.FileStore;, import java.nio.file.Files;, import java.nio.file.Path;, import java.nio.file.Paths;, import java.util.ArrayList;, import java.util.List;, , public class FilesTest {, 	public static void main(String []args)throws Exception, 	{, 		Path path=Paths.get("E:\\Eclipse\\输入输出\\src\\NIO2测试\\FilesTest.java");, 		Files.copy(path, new FileOutputStream("a.txt"));, 		System.out.println("FileTest.java是否为隐藏文件 "+Files.isHidden(path));, 		List<String>lines=Files.readAllLines(path,Charset.forName("gbk"));, 		System.out.println(lines);, 		System.out.println("Java文件的大小为 "+Files.size(path));, 		List<String>poem=new ArrayList<>();, 		poem.add("横看成岭侧成峰");, 		poem.add("远近高低各不同");, 		Files.write(Paths.get("poem.txt"), poem, Charset.forName("gbk"));, 		Files.list(Paths.get(".")).forEach(paths->System.out.println(paths));, 		Files.lines(path,Charset.forName("gbk")).forEach(line->System.out.println(line));, 		FileStore cStore=Files.getFileStore(Paths.get("C:"));, 		System.out.println("C:共有空间: "+cStore.getTotalSpace());, 		System.out.println("C:可用空间: "+cStore.getUsableSpace());, 		, 		, 		, 	}, , }]
Java文件的大小为 1251
.\.classpath
.\.project
.\.settings
.\1475478369806
.\1475478412541
.\1475478513809
.\1475478680647
.\1475478760418
.\1475478952802
.\1475479027512
.\1475479027512.bak
.\1475479225359
.\1475479248332
.\1475479296778
.\1475479361466
.\1475479433051
.\1475479515910
.\1475479572409
.\1475479900129
.\1475480136202
.\1475480268846
.\1475480310178
.\a.txt
.\aaa5631713230666427946.txt
.\bin
.\InsertContent.java
.\newFile.txt
.\object.txt
.\out.txt
.\poem.txt
.\replace.txt
.\src
.\test.txt
.\transient.txt
package NIO2测试;

import java.io.FileOutputStream;
import java.nio.charset.Charset;
import java.nio.file.FileStore;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;

public class FilesTest {
	public static void main(String []args)throws Exception
	{
		Path path=Paths.get("E:\\Eclipse\\输入输出\\src\\NIO2测试\\FilesTest.java");
		Files.copy(path, new FileOutputStream("a.txt"));
		System.out.println("FileTest.java是否为隐藏文件 "+Files.isHidden(path));
		List<String>lines=Files.readAllLines(path,Charset.forName("gbk"));
		System.out.println(lines);
		System.out.println("Java文件的大小为 "+Files.size(path));
		List<String>poem=new ArrayList<>();
		poem.add("横看成岭侧成峰");
		poem.add("远近高低各不同");
		Files.write(Paths.get("poem.txt"), poem, Charset.forName("gbk"));
		Files.list(Paths.get(".")).forEach(paths->System.out.println(paths));
		Files.lines(path,Charset.forName("gbk")).forEach(line->System.out.println(line));
		FileStore cStore=Files.getFileStore(Paths.get("C:"));
		System.out.println("C:共有空间: "+cStore.getTotalSpace());
		System.out.println("C:可用空间: "+cStore.getUsableSpace());
		
		
		
	}

}
C:共有空间: 497194889216
C:可用空间: 328174043136

使用FileVector遍历文件和目录

Files类里的walkFileTree(Path start,FileVistor<? super Path>visitor)方法可以用来遍历文件.
FileVisitor代表一个文件访问器,walkFileTree()方法会自动遍历start路径下的所有文件和子目录,遍历文件和子目录都会触发"FileVisitor"中的相应方法(P711)

使用Watchervice监控文件变化

NIO2的Path类提供了如下方法监听文件系统的变化
register(WatchSerivce watcher,WatchEvent.Kind<?>...evebts):用watcher监听该path代表的目录下的文件变化,events指定要监听那些类型的事件
P712


访问文件属性



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值