NIO.2是一组新的类和方法,主要存在于java.nio包内。
主要优点:
- 它完全取代了java.io.File与文件系统的交互。
- 它提供了新的异步处理类,让你无需手动配置线程池和其他底层并发控制,便可在后台线程中执行文件和网络I/O操作。
- 它引入了新的Network-Channel构造方法,简化了套接字(Socket)与通道的编码工作。
文件I/O的基石:Path
Path通常代表文件系统中的位置,比如C:\Users\dell\Desktop\nio。NIO.2把位置(由Path表示)的概念和物理文件系统的处理(比如复制一个文件)分得很清楚,物理文件系统的处理通常是由Files辅助类实现。
- Path:Path类中的方法可以用来获取路径信息,访问该路径中的各元素,将路径转换为其他形式,或提取路径中的一部分。有的方法还可以匹配路径字串以及移除路径中的冗余项
- Paths:工具类,提供返回一个路径的辅助方法,比如get(String first, String... more)和get(URI uri)
- FileSystem:与文件系统交互的类,无论是默认的文件系统,还是通过其统一资源标识(URI)获取的可选文件系统
- FIleSystems:工具类,提供各种方法,比如其中用于返回默认文件系统的FIleSystems.getDefault()
Path不一定代表真实的文件或目录。
1、创建一个Path
Path path = Paths.get("C:\\Users\\dell\\Desktop\\nio", "a.txt");
其实,等同于Path path = FileSystems.getDefault().getPath("C:\\Users\\dell\\Desktop\\nio", "a.txt");
2、从Path中获取信息
Path path = FileSystems.getDefault().getPath("C:\\Users\\dell\\Desktop\\nio", "a.txt");
System.out.println("File Name:" + path.getFileName());
System.out.println("Number of Name Elements in the Path:" + path.getNameCount());
System.out.println("Root of Path:" + path.getRoot());
System.out.println("Subpath from Root,2 elements deep:" + path.subpath(0, 2));
3、移除冗余项
Path normalizePath = Paths.get("./PathTest.java").normalize();
此外,toRealPath()方法也很有效,它融合了toAbsolutePath()和normalize()两个方法的功能,还能检测并跟随符号连接。
4、转换Path
合并两个Path之间的路径:
Path path = Paths.get("C:\\Users\\dell");
Path completePath = path.resolve("Desktop\\nio");
System.out.println(completePath);
取得两个Path之间的路径:
Path path1 = Paths.get("C:\\Users\\dell");
Path path2 = Paths.get("C:\\Users\\dell\\Desktop\\nio");
System.out.println(path1.relativize(path2));
比较
startsWith(Path prefix)、equals(Path path)、endsWith(Path suffix)。
5、NIO.2 Path和Java已有的FIle类
处理目录和目录树
- java.io.File.toPath():把File转化为Path
- java.nio.file.Path.toFile():把Path转化为File
java.nio.file.DirectorySystem<T>接口和它的实现类提供了很多功能:
- 循环遍历目录中的子项,比如查找目录中的文件
- 用glob表达式(比如*Foobar*)进行目录子项匹配和基于MIME的内容检测(比如text/xml)文件
- 用walkFileTree方法实现递归移动、复制和删除操作
1、在目录中查找文件
Path dir = Paths.get("C:\\\\Users\\\\dell\\\\Desktop\\\\nio");
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, "*.txt")) {
for (Path entry : stream) {
System.out.println(entry.getFileName());
}
} catch (IOException e) {
System.out.println(e.getMessage());
}
2、遍历目录树
Java7可以很容易地搜寻目录树中的文件,在子目录中查找,并对它们执行操作。
关键方法是:Path walkFileTree(Path start, FileVisitor<? super Path> visitor) throws IOException,其中visitor提供了一个默认实现类SimpleFileVisitor<T>。
/**
* Java7的新I/O
* @author z_hh
* @time 2018年8月16日
*/
public class Nio {
public static void main(String[] args) throws IOException {
Path startingDir = Paths.get("D:\\eclipse_workspace\\zhh_src\\src");
Files.walkFileTree(startingDir, new FindJavaVisitor());
}
private static class FindJavaVisitor extends SimpleFileVisitor<Path> {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
if (file.toString().endsWith(".java")) {
System.out.println(file.getFileName());
}
return FileVisitResult.CONTINUE;
}
}
}
重写了SimpleFileVisitor的visitFile方法来判断文件名是否以.java结尾。
其它用例包括递归移动、复制、删除或者修改文件。
为了确保递归等操作的安全性,walkFileTree方法不会自动跟随符号链接。
NIO.2的文件系统I/O
文件处理的基础类
Files:让你轻松复制、移动、删除或处理文件的工具类,有你需要的所有方法
WatchService:用来监视文件或目录的核心类,不管它们有没有变化
1、创建和删除文件
// 创建文件
Path txt = Paths.get("C:\\Users\\dell\\Desktop\\nio\\z.txt");
Path path = Files.createFile(txt);
// 创建文件并分配权限
Path jpg = Paths.get("C:\\Users\\dell\\Desktop\\nio\\h.jpg");
Set<PosixFilePermission> perms = PosixFilePermissions.fromString("r--r--r--");
FileAttribute<Set<PosixFilePermission>> attr = PosixFilePermissions.asFileAttribute(perms);
Files.createFile(jpg, attr);
// 删除
Files.delete(jpg);
Files.deleteIfExists(jpg);
2、文件的复制和移动
Path source = Paths.get("C:\\Users\\dell\\Desktop\\nio\\z.txt");
Path target = Paths.get("C:\\Users\\dell\\Desktop\\nio\\dir");
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
Files.move(source, target, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
3、文件的属性
基本文件属性的支持
特定文件属性支持
try {
Path path = Paths.get("C:\\Users\\dell\\Desktop\\nio\\a.txt");
// 获取属性范围
PosixFileAttributes attrs = Files.readAttributes(path, PosixFileAttributes.class);
// 读取访问许可
Set<PosixFilePermission> posixPermissions = attrs.permissions();
// 消除访问许可
posixPermissions.clear();
// 日志信息
String owner = attrs.owner().getName();
String perms = PosixFilePermissions.toString(posixPermissions);
System.out.format("%s %s%n", owner, perms);
// 设置新的访问许可
posixPermissions.add(PosixFilePermission.OWNER_READ);
posixPermissions.add(PosixFilePermission.GROUP_READ);
posixPermissions.add(PosixFilePermission.OTHERS_READ);
posixPermissions.add(PosixFilePermission.OWNER_WRITE);
Files.setPosixFilePermissions(path, posixPermissions);
} catch (Exception e) {
e.printStackTrace();
}
符号连接
Path readSymbolicLink(Path link) throws IOException
4、快速读写数据
打开文件
- BufferedReader newBufferedReader(Path path, Charset cs) throws IOException
- BufferedReader newBufferedReader(Path path, Charset cs) throws IOException
简化读取和写入
- List<String> readAllLines(Path path, Charset cs) throws IOException
- byte[] readAllBytes(Path path) throws IOException
5、文件修改通知
java.nio.file.WatchService类用客户线程监视注册文件或目录的变化,并且在检测到变化时返回一个事件。这种事件通知对于安全监测、属性文件中的数据刷新等很多用例都很有用。是现在某些应用程序中常用的轮询机制(相对而言性能较差)的理想替代品。(好像还有一种回调的方式^_^)
try {
WatchService watcher = FileSystems.getDefault().newWatchService();
Path dir = FileSystems.getDefault().getPath("C:\\Users\\dell\\Desktop\\nio");
// 检测变化
WatchKey key = dir.register(watcher, StandardWatchEventKinds.ENTRY_MODIFY);
while (true) {
// 得到写一个key及其事件
key = watcher.take();
for (WatchEvent<?> event : key.pollEvents()) {
// 检查是否为创建事件
if (event.kind() == StandardWatchEventKinds.ENTRY_MODIFY) {
System.out.println("Home dir has entry create!");
}
}
// 重置检测key
key.reset();
}
} catch (Exception e) {
e.printStackTrace();
}
6、SeekableByteChannel
Java7引入SeekableByteChannel接口,是为了让开发人员能够改变字节通道的位置和大小。比如,应用服务器为了分析日志中的某个错误码,可以让多个线程去访问连接在一个大型日志文件上的字节通道。
jdk中有一个java.nio.channels.SeekableByteChannel接口的实现类——java.nio.channels.FileChannel。这个类可以在文件读取或写入时保持当前位置。
Path logFile = Paths.get("C:\\error.log");
ByteBuffer buffer = ByteBuffer.allocate(1024);
FileChannel channel = FileChannel.open(logFile, StandardOpenOption.READ);
channel.read(buffer, channel.size() - 1000);
异步I/O操作
异步I/O其实只是一种在读写操作结束前允许其他操作的I/O处理。
Java7中有三个新的异步通道:
- AsynchronousFileChannel——用于文件I/O
- AsynchronousSocketChannel——用于套接字I/O,支持超时
- AsynchronousServerSocketChannel——用于套接字接受异步连接
使用新的异步I/O API时,主要有两种形式,将来式和回调式。
1、将来式
Path file = Paths.get("C:\\Users\\dell\\Desktop\\nio\\a.txt");
// 异步打开文件
AsynchronousFileChannel channel = AsynchronousFileChannel.open(file);
// 读取100,000字节
ByteBuffer buffer = ByteBuffer.allocate(100_000);
Future<Integer> result = channel.read(buffer, 0);
while (!result.isDone()) {
// 干点别的事情
System.out.println("睡个觉!");
}
// 获取结果
Integer byteRead = result.get();
System.out.println("Bytes read:" + byteRead);
AsynchronousFileChannel会关联线程池,它的任务是接收I/O处理事件,并分发给负责处理通道中I/O操作结果的结果处理器。跟通道中发起的I/O操作关联的结果处理器确保是由线程池中的某个线程产生的。
2、回调式
Path file = Paths.get("C:\\Users\\dell\\Desktop\\nio\\a.txt");
// 以异步方式打开文件
AsynchronousFileChannel channel = AsynchronousFileChannel.open(file);
ByteBuffer buffer = ByteBuffer.allocate(100_000);
// 从通道中读取数据
channel.read(buffer, 0, buffer, new CompletionHandler<Integer, ByteBuffer>() {
// 获取完成时的回调方法
@Override
public void completed(Integer result, ByteBuffer attachment) {
System.out.println("Byte read:" + result);
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
System.out.println("fail:" + exc.getMessage());
}
});
以上两个离职都是基于文件的,但也同样适合AsynchronousSocketChannel和AsynchronousServerSocketChannel。
Socket和Channel的整合
NetworkChannel
java.nio.channels.NetworkChannel代表一个连接到网络套接字通道的映射。
SelectorProvider provider = SelectorProvider.provider();
try {
// 将NetworkChannel绑定到3080上
NetworkChannel socketChannel = provider.openSocketChannel();
InetSocketAddress address = new InetSocketAddress(3080);
socketChannel = socketChannel.bind(address);
// 检查套接字选项
Set<SocketOption<?>> socketOptions = socketChannel.supportedOptions();
System.out.println(socketOptions.toString());
// 设置套接字的Tos(服务条款)选项
socketChannel.setOption(StandardSocketOptions.IP_TOS, 3);
// 获取SO_KEEPALIVE选项
Boolean keepAlive = socketChannel.getOption(StandardSocketOptions.SO_KEEPALIVE);
// ...
} catch (Exception e) {
e.printStackTrace();
}
MulticastChannel
多播(或组播)表示一对多的网络通讯,通常用来指代IP多播。使用java.nio.channels.MulticastChannel及其默认实现类DatagramChannel可以轻松地对多播发送和接收数据。
// 选择网络接口
NetworkInterface networkInterface = NetworkInterface.getByName("net1");
// 打开DatagramChannel
DatagramChannel dc = DatagramChannel.open(StandardProtocolFamily.INET);
// 将通道设置为多播
dc.setOption(StandardSocketOptions.SO_REUSEADDR, true)// 链式调用
.bind(new InetSocketAddress(8080))
.setOption(StandardSocketOptions.IP_MULTICAST_IF, networkInterface);
// 加入多播组
InetAddress group = InetAddress.getByName("180.90.4.12");
MembershipKey key = dc.join(group, networkInterface);
到此为止,我们对NIO.2 API的初步研究已经结束了。希望你喜欢这次行色匆匆的旅程!