Java I/O系统
Peng's Blog 只记录和技术相关的东西

Java I/O系统

2017-08-22

内容部分整理自《Java编程思想》第四版、第18章 - - Java I/O 系统

主要内容来自《Java程序员修炼之道》第二章 新I/O (基于Java7)

Java7改变的最大的就是 I/O ,被称为 “再次更新的I/O” 或 NIO.2 (NIO是jdk1.4的内容)

File这个类是JDK1.0就有的,Files是Java7的新类;

I/O操作并不只有文本处理那些,I/O是个完整是输出输出体系。

新IO的优点

  • 完全取代了 java.io.File类与文件系统的交互;
  • 提供了新的异步处理类,让你无需手动配置线程池和其它底层并发控制,便可在后台线程中执行文件和网络IO操作;
  • 引入了新的 Network-Channel 构造方法,简化了Socket与通道的编码工作

新IO的两大趋势:

  1. 对其它数据存储方法的探索,特别是在非关系或大数据集领域;
  2. 多核CPU的发展,使得真正并发的IO成为可能;

Java I/O 简史

说实话,我以前一直觉得Java的IO又乱又不好用 .. 嗯,它主要经过了三代的发展,现在好多了。

Java 1.0到1.3

没有完整的IO支持。

没有数据缓冲区或者通道的概念,程序员需要处理很多底层的细节;

IO操作会被阻塞,扩展能力受限;

锁支持的字符集编码有限,需要进行很多手工编码来支持特定的硬件;

不支持正则表达式,数据处理苦难;

Java 1.4引入NIO

non-blocking I/O,非阻塞IO。在1.4中引入,在Java7中对其进行改进。(从此之后,Java才得到服务器端开发人员的青睐)

为IO操作抽象出缓冲区和通道层;

字符集的编码和解码能力;

提供了能够将文件映射为内存数据的接口;

实现了非阻塞IO的能力;

基于流行的Perl实现的正则表达式类库;(Perl语言处理正则表达式非常厉害)

NIO.2

主要是为了解决Java1.4的NIO里面一些不足:

1、在不同的平台对文件名的处理不一致;

2、没有同意的文件属性模型;

3、遍历目录困难;

4、不能使用平台/操作系统的特性;

5、不支持文件系统的非阻塞操作?????

在Java7的NIO里面:

  • 一个能批量获取文件属性的文件系统接口;
  • 提供一个Socket和文件都能够进行同步(与轮询、非阻塞相对)IO操作的API;
  • 完成了通道功能

详细内容往下看。

文件 I/O 的基石:Path

这个是NIO.2的关键。Path通常代表文件系统中的位置。

在NIO.2中,你所创建和处理的Path可以不马上绑定到对应的物理位置上。JVM只会把Path绑定到运行时的物理位置上;

位置(Path)的概念和物理文件系统的处理分得非常清楚

屏幕快照 2017-08-22 下午8.08.04

创建一个Path

一般用 Paths.get(xxx) 来创建;

Path对象本身带着非常多的属性,当你需要啥的时候去查一下API文档就好了。

NIO.2 Path 和 Java已有的File类

File类从 jdk1.0 就有了。

Java7提供了两个新方法:

  • java.io.File类中新增了 toPath()方法,它可以把已有的File转化为新的Path;
  • Path类中有个toFile()方法,他可以把Path转化为File。

处理目录和目录树

在目录中查找文件

Paths.get(String)返回Path,然后用DirectoryStream(Path directory, String patternMatch)方法,它返回一个经过过滤的DirectoryStream,这里的模式匹配称为 glob 模式匹配,类似Perl正则表达式,但也有点不同。

遍历目录树

遍历目录树是Java 7的新特性。

Files.walkFileTree(Path xx,FileVistor visitor); 这个有点复杂。一般用Simple-FileVisitor,继承它然后重写它的visitFile方法。

NIO.2 的文件系统 I/O

Java7中跟文件处理有关的两个新的基础类介绍:

  • Files : 让你轻松复制、移动、删除或处理文件的工具类,有你需要的所有方法。(666)
  • WatchService:用来监视文件或目录的核心类,不管它们有没有变化;

创建和删除文件

创建文件:

Path target = Paths.get("xxx");
Path file = Files.createFile(target);

删除文件:

Path target = Paths.get("xxx");
Files.delete(target);

创建的时候要考虑文件读写权限的问题。

文件的复制和移动

文件的复制:

Path source = Paths.get("xxx");
Path target = Paths.get("xxx");
Files.copy(source,target);

File.copy()这个方法还有第三个参数,比如覆盖已有文件(REPLACE——EXISTING)等等。

文件的移动:

Path source = Paths.get("xxx");
Path target = Paths.get("xxx");
Files.move(source,target);

和复制很像,如果需要在移动的时候复制,那么可以加第三个参数 - COPY_ATTRIBUTES。

文件的属性

文件属性控制着谁对文件做什么。一般情况下,做什么许可包括能否读取、写入或执行文件。

基本文件的属性支持:

  • 最后修改的时间是什么时候
  • 它多大
  • 它是符号连接吗
  • 它是目录吗

这些属性的调用都是通过Files.readAttributes(Path path, String attributes ,LinkOption … options)得到的。

屏幕快照 2017-08-22 下午8.45.45

Java 7还支持跨文件系统的文件属性查看和处理功能。

特定文件属性支持:

这里主要是一些权限控制相关的东西。

特定文件属性支持主要有:FileAttribute接口和PosixFilePermissions类。

屏幕快照 2017-08-22 下午8.48.48

符号链接

可以理解成指向另一个文件或目录的入口,并且在大多数情况它们都是透明的。比如切换到符号链接的目录下会把你带到符号链接指向的目录下。

屏幕快照 2017-08-22 下午8.51.17

快速读写数据

一、打开文件

Java 7而已直接用带缓冲区的读取器和写入器或数据输出流打开文件。

打开一个用于读取的文件:

Path logFile = Paths.get("/xxx.txt");
BufferedReader reader = new Files.newBufferedReader(logFile,StandardCharsets.UTF_8);
String line;
while((line = reader.readLine())!=null){
    xxxx;
}

打开一个用于写入的文件:

Path logFile = Paths.get("xxx.txt");
BufferedWritter writer = Files.newBufferedWrite(logFile,StandardCharsets.UTF_8, StandardOpenOption.WRITE);
writer.write("hello world!");

至于InputStream和OutputStream(JDK1.0的老古董),能不用就别用。但Files里面还是有创建这两种对象的方法,就是为了向老版本兼容。。佩服。

可以说,字符串编码也是Java 7的亮点。上面用到了,可以看代码。

看到最后,感觉被鄙视了。作者说上面两个是Java 6 及之前的写法。。仍然属于比较繁琐的底层代码,看来Java 7很吊。

简化读取和写入

辅助类Files中有两个辅助方法,用于读取文件中的全部行和全部字节,也就是说没有必要再用while循环吧数据从字节数组中读到缓冲区里去。

Path logFile = Paths.get("xxx.txt");
List<String> lines = Files.readAllLines(logFile, StandardCharsets.UTF_8);
byte[] bytes = Files.readAllBytes(logFile);

当然,很多真实应用场景并没有上面那么巧合,所以,很多时候需要文件修改通知这个东西。

文件修改通知

Java 7新类,java.nio.file.WatchService类,监测文件或目录的变化。该类用客户线程监视注册文件或目录的变化,并且在检测到变化时返回一个事件。

这种事件通知对于安全监测、属性文件中的数据刷新等很多用例都非常有用。

屏幕快照 2017-08-22 下午9.13.16

SeekableByteChannel

Java 7 新引入的接口,为了让开发人员能够改变字节通道的位置和大小

应用场景的话比如吗,应用服务器为了分析日志中的某个错误码,可以让多个线程去访问连接在一个大型日志文件上的字节通道。

这个接口有个实现类:java.nio.channels.FileChannel,这个类可以在文件读取或写入时保持当前位置。比如说,你可以想要写一段代码读取日志文件中的最后1000个字符,或者向一个文本文件中特定位置写入一些价格数据。

比如修改文件的最后1000个字符:

Path logFile = xxxxx; //自己打
ByteBuffer buffer = ByteBuffer.allocate(1024);
FileChannel channel = FileChannel.open(logFile,StandardOpenOption.READ);
channel.read(buffer, channel.size - 1000);

异步 I/O 操作

这是NIO.2的另一个亮点,而且对于Socket和I/O都使用。

使用新的异步I/O API时,主要有两种方式:将来式和回调式。

将来式(Future)

主要是 java.util.concurrent.Future接口。

屏幕快照 2017-08-23 下午7.49.53

大概就是说在Future.get()返回结果之前,可以做其他事。这就是异步的。

屏幕快照 2017-08-23 下午7.54.06

用 isDone() 方法来判断是否完成。

回调式

Callback,采用的事件处理技术类似于Swing。基本思想是:主线程会委派一个侦查员completionHandler到独立的线程中执行IO操作。然后它会带着IO操作的结果返回到主线程中。

屏幕快照 2017-08-23 下午7.59.16

代码:

屏幕快照 2017-08-23 下午8.00.03

Socket 和 Channel 的整合

新接口:NetworkChannel 和其子接口 MulticastChannel 对着两个很重要的领域做了整理。


Comments

评论功能暂停使用,如需跟作者讨论请联系底部的GitHub