NIO
NIO(New IO,新IO),是JDK1.4开始支持的,它与传统IO最大的区别是采用内存映射文件的方式处理输入、输出流。通过之前的学习可以发现,传统的IO处理方式是以字节为单位进行输入、输出,这种处理方式相对于新IO的处理方式是比较慢的。在新IO中,将一个文件或文件的部分映射到内存中,而后在内存操作数据,在性能上肯定是有明显的提升。
新IO相关的类都放在java.nio包下,要想通过新的IO来操作输入、输出则必须掌握两个核心对象Channel(通道)和Buffer(缓冲),其中Channel的主要作用就是借助传统的流对象将数据映射到缓冲区或者将缓冲区中的数据输出到对应的IO设备中。而Buffer更像是一个中转站,不管是输入通道还是输出通道,都要经过Buffer,而它的原型可以理解为就是一个数组,关于buffer有以下三个概念需要理解:
以上是Buffer类常用的一些方法,Buffer类还为每种基本数据类型提供了相应的子类,除布尔类型之外。这些子类都包含有三个重要的方法put、get和allocate方法,代表着存入、取出和获取本类对象。
Channel常用方法
Channel是一个接口,它的使用主要依赖于它的子类,而它的子类实例的创建又依赖于传统IO类,它为不同的传统IO节点流提供了不同的Channel子类,而这些子类中最常用的方法有如下三个。
栗子,使用ByteBuffer、FileChannel完成文件的拷贝
本栗通过使用新IO完成了文件的拷贝,通过代码量就可以看出新的IO完成文件的拷贝要更加的简洁。
Charset类
众所周知计算机只认识0、1两个数字,也就是说计算机上的所有数据都是以二进制码的形式存在的,而我们能通过显示器看到图片、文字、视频的原因是,我们使用了相应的软件帮我们进行了转换。拿文本编辑器来说,我们打开一片文档时,文本编辑器会将计算机存储的二进制码文件翻译成人能够读懂的文字,这个过程我们称之为解码(Decode);而当我们保存一个文档时,文本编辑器又将文字转换成计算机所能识别的二进制码,这个过程我们称之为编码(Encode)。而编码与解码肯定要依赖一个标准,不然就会产生乱码,而这个标准就是字符集,Java默认使用Unicode字符集,Windows默认使用GBK字符集,Linux默认使用UTF-8字符集,不同的字符集对应的二进制编码也是不同的。通过Charset类的availableCharsets方法可以查看Java支持的所有字符集,现将常用的字符集列举如下:
1、GBK:简体中文字符集。
2、ISO-8859-1:拉丁字母表。
3、UTF-8:8位UCS转换格式。
了解了字符集后,我们来看一下Java提供的编码器和解码器的使用。
栗子,编码器、解码器使用
6、已成功安装该软件,点击后续步骤即可访问教程以及更多内容
package com.icypt.charset;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
public class Test {
public static void main(String[] args){
//创建中文字符集对应的Charset类对象
Charset charset = Charset.forName("GBK-8");
//创建编码器对象
CharsetEncoder ce = charset.newEncoder();
//创建解码器对象
CharsetDecoder cd = charset.newDecoder();
//创建缓冲区对象
CharBuffer cb = CharBuffer.allocate(32);
cb.put('冰');
cb.put('点');
cb.flip();
//编码
ByteBuffer bb = null;
try {
bb = ce.encode(cb);
//打印编码之后的字节数组
for(int i=0; i < cb.limit(); i++) {
System.out.println(bb.get(i));
}
//打印解码之后的缓冲数组
System.out.println("打印解码之后的缓冲数组:" +cd.decode(bb));
} catch (CharacterCodingException e) {
e.printStackTrace();
}
}
}
通过Java提供的编码器和解码器我们可以非常方便的对指定字符集的数据进行编码和解码操作,其实在实际的开发中我们无序创建编码、解码器对象,只需调用Charset类提供的静态方法decode和encode即可完成编码、解码操作。
NIO.2
JDK1.7对原有的NIO进行了大的改进,主要体现在三个方面:
1、新增Path接口,它是一个与平台无关的文件路径的抽象,其功能相对于File类更加的全面。
2、新增Paths、Files两大工具类,使得我们对于文件系统的访问和IO的操作更加的方便简洁。
3、提供了基于异步的Channel的IO。
栗子,Path接口的使用
package com.icypt.path;
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
public class Test {
public static void main(String[] args){
//根据路径创建Path接口对象,"."表示当前路径
Path path = Paths.get("Test.java");
//获取绝对路径
Path absolutePath = path.toAbsolutePath();
System.out.println("获取绝对路径:" + absolutePath);
//获取父路径
System.out.println("父路径:" + absolutePath.getParent());
//获取根路径
System.out.println("根路径:" + absolutePath.getRoot());
//获取路径数量
System.out.println("absolutePath包含的路径数量:" + absolutePath.getNameCount());
//以路径拼接的方式构建Path对象
Path contactStr = Paths.get("D:\\", "Test.txt");
System.out.println("文件名称:" + contactStr.getFileName());
//将Path对象转换为File对象
File file = contactStr.toFile();
System.out.println("文件大小:" + file.length()/1024 + "kb");
}
}
从上面的栗子可以看出,Path对象是由Paths类的静态方法get创建的,而且还支持将Path对象转化为File对象,使得File类的功能也有所加强。
栗子,Files工具类的使用
package com.icypt.files;
import java.io.File;
import java.io.FileOutputStream;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
public class Test {
public static void main(String[] args) throws Exception{
Path path = Paths.get("D:\\Test.txt");
//文件拷贝,为了演示的方便,将异常全部抛出,交由JVM处理
Files.copy(path, new FileOutputStream(new File("D:\\Test_cp.txt")));
Path path1= Paths.get("D:\\", "Test\\", "msg.txt");
//判断文件是否存在
if(!Files.exists(path1)) {
//创建文件
Files.createFile(path1);
}
//读取文件的所有行数据
List<String> allLineData = Files.readAllLines(path);
System.out.println("所有行数据:" + allLineData);
//获取指定文件的大小
System.out.println("文件的大小:" + Files.size(path));
//jdk1.8,遍历当前路径下的所有文件和子目录
Path path2= Paths.get("D:\\");
Files.list(path2).forEach(pathObj -> System.out.println(pathObj.getFileName()));
//将字节数组中的内容写入文件
Files.write(path1, "但愿人长久,千里共婵娟!".getBytes());
//获取D盘的总空间和剩余空间
long totalSize = Files.getFileStore(Paths.get("D:")).getTotalSpace()/(1024*1024*1024);
System.out.println("d盘总空间:" + totalSize + "G");
long freeSize = Files.getFileStore(Paths.get("D:")).getUsableSpace()/(1024*1024*1024);
System.out.println("d盘可用空间:" + freeSize + "G");
}
}
以上只是将Files工具类中常用的几个方法做一演示,由于工具类就是对原子操作进行高度封装, 其使用是非常简单的,这里就不深究了。
在之前我们对一个文件夹下的所有文件及目录进行遍历时,通常采用的方式是递归遍历,这种遍历方式不仅复杂,而且极不方便。Files工具类为我们提供了更加方便简洁的遍历方式。
栗子,使用FileVisitor遍历文件及目录
package com.icypt.filevisitor;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
/**
*运行结果
*正在访问的目录:D:\test
*正在访问的目录:D:\test\abc
*正在访问的目录:D:\test\abc\h
*找到的文件名称:final.txt
*/
public class Test {
public static void main(String[] args) throws Exception{
Files.walkFileTree(Paths.get("D:\\test"), new SimpleFileVisitor<Path> (){
//访问目录之前触发
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
System.out.println("正在访问的目录:" + dir);
return FileVisitResult.CONTINUE;
}
//访问文件时触发
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
if(file.endsWith("final.txt")) {
System.out.println("找到的文件名称:" + file.getFileName());
//终止查找
return FileVisitResult.TERMINATE;
}
//继续查找
return FileVisitResult.CONTINUE;
}
});
}
}
以上栗子实现了在指定的Test文件夹下查找以final.txt结尾的路径,发现我们并不需要关注如何去遍历文件及目录,只需将查找条件在visitFile方法中定义即可实现,相比之前的硬编码实现的递归查找更加的简洁、方便。需要注意的FileVisitResult提供的四个静态常量的含义:
1、 CONTINUE:表示继续查找。
2、TERMINATE:表示终止查找。
3、SKIP_SUBTREE:表示继续查找,但跳过当前文件或者文件夹的子目录树。
4、SKIP_SIBLINGS:表示继续查找,但跳过当前文件或者文件夹的兄弟文件夹或文件。
栗子,监控文件的变化
package com.icypt.watchservice;
import java.nio.file.FileSystems;
import java.nio.file.Paths;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
public class Test {
public static void main(String[] args) throws Exception{
//创建WatchService实例
WatchService ws = FileSystems.getDefault().newWatchService();
//注册监听,test目录下只要新建、修改、删除文件及文件夹都会被监听
Paths.get("D:\\test").register(ws,
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_MODIFY,
StandardWatchEventKinds.ENTRY_DELETE);
while(true) {
//获取下一个文件变化的事件
WatchKey wk = ws.take();
for(WatchEvent<?> event : wk.pollEvents()) {
System.out.println(event.context() + ",文件发生了" + event.kind() + "事件!");
}
//重置WatchKey
boolean flag = wk.reset();
//如果重置失败,退出监听
if(!flag) {
break;
}
}
}
}
上栗通过WatchService实现了监控文件变化的功能,对监控到的信息只是做了打印,各位学友可以根据自己的业务情况相应去实现,需要注意的是WatchService总共提供了三个方法让我们获取被监听文件的事件:
1、WatchKey poll():获取下一个WatchKey,如果没有WatchKey发生,则返回null;
2、WatchKey poll(long timeout, TimeUnit unit):尝试等待timeout时间去获取WatchKey;
3、WatchKey take():获取下一个WatchKey,如果没有WatchKey发生,则一直等待,本栗使用就是此方法。
至此关于NIO以及NIO.2的功能就讲解完了,下篇我们开始学习Java中多线程的使用。
java培训班:http://www.baizhiedu.com/java2019