JAVA输入/输出(四)-----NIO(new IO)
一、新IO概述
新IO采用内存映射文件的方式来处理输入/输出,新IO将文件或文件的一段区域映射到内存中,这样就可以像访问内存一样来访问文件了。
二、使用Buffer
容量 (capacity) :表示 Buffer 最大数据容量,一旦声明后,不能更改。通过Buffer中的capacity()获取。缓冲区capacity不能为负。
限制 (limit):第一个不应该读取或写入的数据的索引,即位于 limit
后的数据不可读写。通过Buffer中的limit()获取。缓冲区的limit不能为负,并且不能大于其capacity。
位置 (position):当前要读取或写入数据的索引。通过Buffer中的position()获取。缓冲区的position不能为负,并且不能大于其limit。
标记 (mark):标记是一个索引,通过 Buffer 中的 mark() 方法将mark标记为当前position位置。
之后可以通过调用 reset() 方法将 position恢复到标记的mark处。
标记、位置、限制、容量遵守以下不变式:
0 <= mark <= position <= limit <= capacity
flip():封印Buffer中没有数据的空间。limit设置为position,position设置为0。
clear()方法:将position设为0,limit设为capacity。
import java.nio.CharBuffer; public class Buffer { public static void main(String[] args) { //创建Buffer CharBuffer buff = CharBuffer.allocate(8); System.out.println("capacity: " + buff.capacity()); System.out.println("limit: " + buff.limit()); System.out.println("position: " + buff.position()); //放入元素 buff.put("a"); buff.put("b"); buff.put("c"); System.out.println("加入三个元素后,position = " + buff.position()); //调用flip()方法 buff.flip(); System.out.println("执行flip()后,limit = " + buff.limit()); System.out.println("position = " + buff.position()); //取出第一个元素 System.out.println("第一个元素(position=0): " + buff.get()); System.out.println("取出第一个元素后,position = " + buff.position()); //调用clear()方法 buff.clear(); System.out.println("执行clear()后,limit = " + buff.limit()); System.out.println("position = " + buff.position()); System.out.println("执行clear()后,缓冲区内容并没有被清除: " + "第三个元素为: " + buff.get(2)); System.out.println("执行绝对读取后,position = " + buff.position()); } }
输出结果为:
capacity: 8 limit: 8 position: 0 加入三个元素后,position = 3 执行flip()后,limit = 3 position = 0 第一个元素(position=0): a 取出第一个元素后,position = 1 执行clear()后,limit = 8 position = 0 执行clear()后,缓冲区内容并没有被清除: 第三个元素为: c 执行绝对读取后,position = 0
三、使用Channel
Channel最常用的三类方法:map()、read()和write()。
map(): 用于将Channel对应的部分或全部数据映射成ByteBuffer;而Read()和write()拥有一系列重载的方法,用于从Buffer中读取数据或向Buffer中写入数据。
MappedByteBuffer map(FileChannel.MapMode mode, long position, long size):mode只能为只读,读写。
import java.io.*; import java.nio.*; import java.nio.channels.*; import java.nio.charset.*; public class FileChannelTest { public static void main(String[] args) { File f = new File("aaa.txt"); //虽然FileChannel既可以读取也可以写入,但FileInputStream获取的FileChannel只能读,而FileOutputStream获取的FileChannel只能写。 try(FileChannel inChannel = new FileInputStream(f).getChannel(); FileChannel outChannel = new FileOutputStream("bbb.txt").getChannel()) { //将FileChannel里的全部数据映射成ByteBuffer MappedByteBuffer buffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, f.length()); //使用GBK的字符集来创建解码器 Charset charset = Charset.forName("GBK"); //直接将Buffer里的数据全部输出到"bbb.txt"文件 outChannel.write(buffer); //再次调用buffer的clear()方法,复原limit,position的 位置 buffer.clear(); //创建解码器(CharsetDecoder)对象 CharsetDecoder decoder = charset.newDecoder(); //使用解码器将ByteBuffer转换成CharBuffer CharBuffer charBuffer = decoder.decode(buffer); //CharBuffer的toString方法可以获取对应的字符串 System.out.println(charBuffer); } catch (IOException e) { e.printStackTrace(); } } }
RandomAccessFile中也包含了一个getChannel()。下面程序将Channel的记录指针移动到该Channel的最后,从而可以让程序将指定的ByteBuffer的数据追加到该Channel后面。
import java.io.*; import java.nio.*; import java.nio.channels.FileChannel; public class RandomFileChannelTest { public static void main(String[] args) throws IOException { File f = new File("aaa.txt"); //创建一个Random try(RandomAccessFile raf = new RandomAccessFile(f,"rw"); FileChannel randomChannel = raf.getChannel()) { //将Channel中的所有数据映射成ByteBuffer ByteBuffer buffer = randomChannel.map(FileChannel.MapMode.READ_ONLY, 0, f.length()); //把Channel的记录指针移动到最后 randomChannel.position(f.length()); //将buffer中的所有数据输出 randomChannel.write(buffer); } } }
四、字符集和Charset
import java.nio.charset.Charset; import java.util.SortedMap; public class CharsetTest { public static void main(String[] args) { //获取Java支持的全部字符集 SortedMap<String,Charset> map = Charset.availableCharsets(); for(String alias : map.keySet()) { System.out.println(alias + "------" + map.get(alias)); } } }
CharSetEncoder将CharBuffer或String(字符序列)转换成ByteBuffer(字节序列)
CharSetDecoder将ByteBuffer(字节序列)转换成CharBuffer(字符序列)
import java.nio.*; import java.nio.charset.*; public class CharsetTransform { public static void main(String[] args) throws CharacterCodingException { //创建简体中文对应的GBK Charset cn = Charset.forName("GBK"); //创建对应的编码器和解码器 CharsetEncoder cnEncoder = cn.newEncoder(); CharsetDecoder cnDecoder = cn.newDecoder(); //创建一个CharBuffer对象 CharBuffer cbuff = CharBuffer.allocate(8); cbuff.put("孙"); cbuff.put("悟"); cbuff.put("空"); cbuff.flip(); //将CharBuffer中的字符序列转换成字节序列 ByteBuffer bbuff = cnEncoder.encode(cbuff); //循环访问bbuff中的每个字节 for(int i = 0; i < bbuff.capacity(); i++) { System.out.println(bbuff.get(i) + " "); } //将ByteBuff中的数据解码成字符序列 System.out.println("\n" + cnDecoder.decode(bbuff)); } }
五、文件锁
lock(long position, long size, boolean shared): 对文件从position开始,长度为size的内容加锁,阻塞式的。
tryLock(long position, long size, boolean shared): 非阻塞式的方法。
shared:true,共享锁,允许多个进程读取文件,阻止其他进程获得该文件的排他锁。
shared:false,排他锁,锁住对该文件的读写。
import java.io.*; import java.nio.*; import java.nio.channels.*; public class FileLockTest { public static void main(String[] args) throws Exception { try(FileChannel channel = new FileOutputStream("a.txt").getChannel()) { //使用肥阻塞式的方式对指定文件全部数据进行枷锁 FileLock lock = channel.tryLock(); //程序暂停10s,其他程序无法对a.txt文件进行修改 Thread.sleep(10000); //释放锁 lock.release(); } } }
六、Java7的NIO.2(Path,Paths,Files)
Path接口:代表一个平台关的平台路径
Files工具类:包含了大量静态工具方法来操作文件
Paths工具类:包含了两个返回Path的静态工厂方法
import java.nio.file.*; public class PathTest { public static void main(String[] args) { //以当前路径来创建Path对象 Path path = Paths.get("."); System.out.println("path里包含的路径数量:" + path.getNameCount()); System.out.println("path的根路径:" + path.getRoot()); //获取path对应的绝对路径 Path absolutePath = path.toAbsolutePath(); System.out.println(absolutePath); //获取绝对路径的根路径 System.out.println("absolutePath的根路径:" + absolutePath.getRoot()); //获取绝对路径包含的路径数量 System.out.println("absolutePath里包含的路径数量:" + absolutePath.getNameCount()); System.out.println(absolutePath.getName(1)); //以多个String来构建Path对象 Path path2 = Paths.get("g:", "publish", "codes"); System.out.println(path2); } }

熟练掌握以下Files工具类的用法,可以大大简化文件IO
import java.io.*; import java.nio.charset.Charset; import java.nio.file.*; import java.util.*; public class FilesTest { public static void main(String[] args) throws Exception { //复制文件 Files.copy(Paths.get("aaa.txt"), new FileOutputStream("bbb.txt")); //判断aaa.txt是否为隐藏文件 System.out.println(Files.isHidden(Paths.get("aaa.txt"))); //一次性读取aaa.txt文件的所有行 List<String> lines = Files.readAllLines(Paths.get("aaa.txt"), Charset.forName("gbk")); System.out.println(lines); //判断指定文件的大小 System.out.println(Files.size(Paths.get("aaa.txt"))); //将多个字符串内容写入文件 List<String> poem = new ArrayList<>(); poem.add("水晶潭底银鱼跃"); poem.add("清徐风中碧竿横"); Files.write(Paths.get("poem.txt"), poem, Charset.forName("gbk")); //使用Java8新增的Stream API列出当前目下所有文件和子目录 Files.list(Paths.get(".")).forEach(path -> System.out.println(path)); //使用Java8新增的Stream API读取文件内容 Files.lines(Paths.get("poem.txt"), Charset.forName("gbk")).forEach(line -> System.out.println(line)); //判断C盘的总空间,可用空间 FileStore cStore = Files.getFileStore(Paths.get("C:")); System.out.println("C:总空间" + cStore.getTotalSpace()); System.out.println("C:可用空间" + cStore.getUsableSpace()); } }
七、Java7的NIO.2(FileVisitor遍历文件和目录)
Files类提供了如下两个方法来遍历文件和子目录
walkFileTree(Path statr, FileVisitor<? super Path> visitor):遍历start路径下的所有文件和子目录
walkFileTree(Path statr, Set<FileVisitOption> options, int maxDepth, FileVisitor<? super Path> visitor):最多遍历maxDepth深度的文件
上面两个方法都需要FileVisitor参数,Filevisitor代表一个文件访问器,遍历文件和子目录都会触发Filevisitor中相应的方法:
FileVisitResult postVisitDirectory(T dir,IOException exc):访问子目录之后触发该方法
FileVisitResult preVisitDirectory(T dir,BasicFileAttributes attrs):访问子目录之前触发该方法
FileVisitResult visitFile(T dir,BasicFileAttributes attrs):访问file文件时触发该方法
FileVisitResult visitFileFailed(T dir,IOException exc):访问file文件失败时触发该方法
上面4个方法返回一个FileVisitResult对象,它是一个枚举类,代表了访问之后的后续行为:
CONTINUE:继续访问
SKIP_SIBLINGS:继续访问,不访问该文件或目录的兄弟文件或目录
SKIP_SUBTREE:继续访问,不访问该文件或目录的子目录树
TERMINATE:中止访问
实际编程没必要 为FileVisitor4个方法都提供实现,可以通过继承SimpleFileVisitor(FileVisitor的实现类)来实现自己的“文件访问器”
import java.io.IOException; import java.nio.file.*; import java.nio.file.attribute.BasicFileAttributes; public class FileVisitorTest { public static void main(String[] args) throws IOException { //遍历F:\Workspaces\CSMA_CD下的所有文件和子目录 Files.walkFileTree(Paths.get("f:","Workspaces","CSMA_CD"), new SimpleFileVisitor<Path>() { //访问文件时触发该方法 @Override public FileVisitResult visitFile(Path file,BasicFileAttributes attrs) { System.out.println("正在访问" + file + "文件"); //找到了aaa.txt文件 if(file.endsWith("aaa.txt")) { System.out.println("以找到目标文件"); return FileVisitResult.TERMINATE; } return FileVisitResult.CONTINUE; } //开始访问目录时触发该方法 @Override public FileVisitResult preVisitDirectory(Path dir,BasicFileAttributes attrs) { System.out.println("正在访问:" + dir + " 路径"); return FileVisitResult.CONTINUE; } }); } }
八、Java7的NIO.2(使用WatchService监控文件变化)
Path提供了一个方法来监听文件系统的变化:
register(WatchService watcher, WatchEvent.King<?>... events):用watcher监听该Path代表的目录下的文件变化。events参数指定要监听哪些类型的事件。
WatchService用三个方法来获取被监听的文件变化事件:
WatchKey poll():获取下一个WatchKey,如果没有Watchkey发生就立即返回null
WatchKey poll(long timeout, TimeUnit unit):尝试等待timeout事件去获取下一个WatchKey
WatchKey take():如果没有WatchKey发生就一直等待
如果需要一直监控,使用take()方法;如果程序只需要监控指定事件,使用poll()方法
import java.io.IOException; import java.nio.file.*; public class WatchServiceTest { public static void main(String[] args) throws Exception { //获取文件系统的WatchService对象 WatchService watchService = FileSystems.getDefault().newWatchService(); //为C:盘根路径注册监听 Paths.get("C:/").register(watchService, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_DELETE); while(true) { //获取下一个文件变化 WatchKey key = watchService.take(); for(WatchEvent<?> event : key.pollEvents()) { System.out.println(event.context() + " 文件发生了 " + event.kind() + " 事件! "); } //重设WatchKey boolean valid = key.reset(); //如果重设失败,退出监听 if(!valid) { break; } } } }

九、Java7的NIO.2(访问文件属性)
XxxAttributeView:代表某种文件属性的视图
XxxAttributes:代表某种文件属性的“集合”,程序一般通过XxxAttributeView来获取XxxAttributes
import java.io.IOException; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.file.*; import java.nio.file.attribute.*; import java.util.*; public class AttributeViewTest { public static void main(String[] args) throws IOException { //获取将要操作的文件 Path testPath = Paths.get("aaa.txt"); //获取访问基本属性的BasicFileAttributeView BasicFileAttributeView basicView = Files.getFileAttributeView(testPath, BasicFileAttributeView.class); //获取访问基本属性的BasicFileAttribute BasicFileAttributes basicAttribs = basicView.readAttributes(); //访问文件的基本属性 System.out.println("创建时间:" + new Date(basicAttribs.creationTime().toMillis())); System.out.println("最后访问时间:" + new Date(basicAttribs.lastAccessTime().toMillis())); System.out.println("最后修改时间:" + new Date(basicAttribs.lastModifiedTime().toMillis())); System.out.println("文件大小:" + basicAttribs.size()); //获取访问文件属主信息的的FileOwnerAttributeView FileOwnerAttributeView ownerView = Files.getFileAttributeView(testPath, FileOwnerAttributeView.class); //获取该文件所属的用户 System.out.println(ownerView.getOwner()); //获取系统中guest对应的用户 UserPrincipal user = FileSystems.getDefault().getUserPrincipalLookupService().lookupPrincipalByName("guest"); //修改用户 ownerView.setOwner(user); //获取访问自定义属性的UserDefinedFileAttributeView UserDefinedFileAttributeView userView = Files.getFileAttributeView(testPath, UserDefinedFileAttributeView.class); List<String> attrNames = userView.list(); for(String name : attrNames) { ByteBuffer buf = ByteBuffer.allocate(userView.size(name)); userView.read(name, buf); buf.flip(); String value = Charset.defaultCharset().decode(buf).toString(); System.out.println(name + "--->" + value); } //添加一个自定义属性 userView.write("发行者", Charset.defaultCharset().encode("疯狂Java")); //获取访问自定义属性的UserDefinedFileAttributeView DosFileAttributeView dosView = Files.getFileAttributeView(testPath, DosFileAttributeView.class); //将文件设置隐藏,只读 dosView.setHidden(true); dosView.setReadOnly(true); } }
浙公网安备 33010602011771号