在Java的早期版本中,没有完整的I/O支持,在开发过程中需要解决以下问题:1)没有数据缓冲区或者NIO的通道概念,需要编程人员处理底层细节。2)I/O是受阻塞的。3)不支持正则表达式,数据处理困难。
Java 1.4阶段,开始支持非阻塞I/O。帮助开发人员提供更快/更可靠的I/O解决方案。主要引入有2次:1)Java 1.4中引入非阻塞I/O。2)在Java 7中对非阻塞I/O进行修改。但是还存在局限性:1)不同平台中对文件名的处理不一致。2)没有统一的文件属性模式。3)遍历目录困难。4)不能使用平台/操作系统的特征。5)不支持文件系统的非阻塞操作。
为了解决NIO的局限,同时为了支持现代硬件和软件的I/O新规范。引入了NIO.2 API。主要特性为:1)一个能批量获取文件属性的文件系统接口,取消和特定文件系统相关的API,还有一个用于引入标准文件系统实现的服务提供者接口。2)引入一个套接字和文件都能够进行异步I/O操作的API。3)完成JSR-51中定义的套接字——通道功能。
在NIO.2的文件I/O中,Path是必须掌握的关键类之一。Path是一个抽象的构造,在创建和处理Path的时候不需要马上绑定对应的物理位置。Path通常代表文件系统中的位置,例如:c://path….等路径地址。
Path并不仅限于传统的文件系统,它也能表示zip或jar这样的文件系统。
以下方法都可以简单的创建Path类。
@Test
public void test1() {
Path path = Paths.get("D:\\path");
Path path1 = Paths.get("D:", "path");
Path path2 = FileSystems.getDefault().getPath("D:\\path");
}
这只是简单的罗列下Path中的部分方法,详细的可以去看API。
@Test
public void test2() {
Path path = Paths.get("D:\\path");
System.out.println("文件名:" + path.getFileName());//文件或文件夹名称
System.out.println("路径中名称元素的数量:" + path.getNameCount());//文件路径级别
System.out.println("父目录路径:" + path.getParent());//父路径
System.out.println("ROOT:" + path.getRoot());//根路径
}
虽然Path可以替代File,但是系统中难免有遗留代码需要维护,所有API提供了Path和File的转换。
@Test
public void test2() {
Path path = Paths.get("D:\\path");
File file = path.toFile();
Path path1 = file.toPath();
}
遍历目录是Java7引入瞩目的新特性。新加入的java.nio.file.DirectoryStream
例如在文件夹中罗列出.properties后缀文件。在以前需要遍历然后比较文件的后缀名称。文件使用glob表达式过滤,看谷歌具体的教程,现在请看:
@Test
public void test2() {
Path path = Paths.get("D:\\path");
try (
DirectoryStream paths = Files.newDirectoryStream(path, "*.properties");
) {
for (Path p : paths ) {
System.out.println("file name = " + p.getFileName());
}
} catch (IOException e) {
}
}
Java7支持整个目录树(子目录也会遍历)的遍历操作。这样可以简单的对子目录查找并执行操作。Files.walkFileTree(Path startingDir, FileVisitor Super Path> vistor);是最关键的方法。FileVisitor需要实现4个方法(Java 8),但是API已经提供了默认实现类SimpleFileVisitor
@Test
public void test3() throws IOException {
Path path = Paths.get("D:\\path");
Files.walkFileTree(path, new FindXMLVisitor());
}
class FindXMLVisitor extends SimpleFileVisitor {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
if (file.toString().endsWith(".xml")) {
System.out.println(file.getFileName());
}
return FileVisitResult.CONTINUE;
}
}
在NIO.2对文件系统的移动文件/修改文件属性等都有很好的改善和支持。主要由Files类提供。
@Test
public void test4() throws IOException {
Path path = Paths.get("D:\\path\\create.xml");
//Files.createFile(path);
Files.delete(path);
}
@Test
public void test4() throws IOException {
Path source = Paths.get("D:\\path\\123.txt");
Path target = Paths.get("D:\\path\\456.txt");
//第三个参数表示复制时候的属性,这个是覆盖文件,如果文件已存在则覆盖
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
}
@Test
public void test4() throws IOException {
Path source = Paths.get("D:\\path\\123.txt");
Path target = Paths.get("D:\\path\\backup\\123.txt");
//第三个参数表示复制时候的属性,这个是覆盖文件,如果文件已存在则覆盖
Files.move(source, target, StandardCopyOption.REPLACE_EXISTING);
}
Java7中可以直接使用带有缓冲区的读取器和写入器或输入输出流操作文件。
@Test
public void test5() {
Path readers = Paths.get("D:\\path\\123.txt");
Path writes = Paths.get("D:\\path\\456.txt");
try (BufferedReader reader = Files.newBufferedReader(readers,StandardCharsets.UTF_8);
BufferedWriter writer = Files.newBufferedWriter(writes, StandardCharsets.UTF_8)) {
//读取
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
//写入
writer.write("write");
} catch (IOException e) {
e.printStackTrace();
}
}
一次读取全部,如果文件内容比较大,建议不要这样读取,因为内存容易泄露
@Test
public void test5() {
Path readers = Paths.get("D:\\path\\123.txt");
try {
List lines = Files.readAllLines(readers,StandardCharsets.UTF_8);
for (String line : lines) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
异步IO其实只是在进行读写操作的时候允许进行其它操作处理。
将来式只是使用java.util.concurrent.Future接口来,在使用该接口的get方法后,如果处理完毕立即返回数据,如果没有则阻塞。
@Test
public void test6() throws IOException, ExecutionException, InterruptedException {
Path readers = Paths.get("D:\\path\\123.txt");
//异步打开文件
AsynchronousFileChannel channel = AsynchronousFileChannel.open(readers);
ByteBuffer buffer = ByteBuffer.allocate(100_000);//读取100 000字节
Future result = channel.read(buffer, 0);//读取
//处理其它事情
System.out.println("处理其它事情");
Integer integer = result.get();//获取结果,如果已经执行完立即返回,否则阻塞到执行完毕
}
回调式通过处理完毕后执行回调函数的方法去通知。
@Test
public void test7() throws IOException {
Path readers = Paths.get("D:\\path\\123.txt");
//异步打开文件
AsynchronousFileChannel channel = AsynchronousFileChannel.open(readers);
ByteBuffer buffer = ByteBuffer.allocate(100_000);//读取100 000字节
channel.read(buffer, 0, buffer, new CompletionHandler() {
@Override
public void completed(Integer result, ByteBuffer attachment) {
System.out.println("执行完毕后执行这里的方法");
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
System.out.println("失败后执行这里的方法");
}
});
}