Java的NIO系统(4) - 概述
NIO,一般认为是New IO的缩写,是Java4开始出现的。NIO是基于通道、面向缓冲区的IO模型。而且在Java7中对其进行了重大改进,甚至被称为NIO2。这个简单介绍一下NIO的关键特性。
NIO 并非老IO的替代品,比如NIO读取文件时不能按行读取
NIO的两大重要概念
1 缓冲区
缓冲区就是抽象类java.nio.Buffer及其实现类。Buffer类定义了缓冲区的核心状态:容量capacity、界限limit和当前位置position。当前位置是缓冲区能够进行读写的位置,每次读写都会向前推进;界限是缓冲区中有效字符的末尾后面一个位置;容量就是缓冲区的大小。
有一些缓冲区类型定义了可以存放的数据类型,有下面几种:
ByteBuffer | CharBuffer |
DoubleBuffer | FloatBuffer |
IntBuffer | LongBuffer |
ShortBuffer | MappedByteBuffer |
最后一个MappedByteBuffer是存放文件的。所有缓冲区都提供了get()和put()方法用于读取和写入数据。以ByteBuffer为例,他的实现如下:
方法 | 描述 |
---|---|
abstract byte get() | 返回当前位置的字节 |
ByteBuffer get(byte vals[ ]) | 将缓冲区复制到vals引用的数组中,返回对缓冲区的引用。如果缓冲区中剩余元素的数量小于vals.length, 就会抛出BufferUnderflowException异常 |
ByteBuffer get(byte vals[],int start, int num) | 从缓冲区复制num个元素到vals引用的数组中,从start指定的索引位置开始保存。返回对缓冲区的引用。如果缓冲区中剩余元素的数量不足num,就会抛出BufferUnderflowException 异常 |
abstract byte get(int idx) | 返回缓冲区中由idx指定的索引位置的字节 |
abstract ByteBuffer put(byte b) | 将b复制到缓冲区的当前位置,返回对缓冲区的引用,如果缓冲区已满,就会抛出BufferOverflowException异常 |
final ByteBuffer put(byte vals[]) | 将vals中的所有元素复制到缓冲区中,从当前位置开始。返回对缓冲区的引用。如果缓冲区不能容纳所有元素,就会抛出BufferOverflowException异常 |
ByteBuffer put(byte vals[],int start, int mum) | 将vals中从start开始的num个元素复制到缓冲区中。返回对缓冲区的引用。如果缓冲区不能容纳全部元素,就会抛出BufferOverfowException异常 |
ByteBuffer put(ByteBuffer bb) | 将bb中的元素复制到缓冲区中,从当前位置开始。如果缓冲区不能容纳全部元素,就会抛出BufferOvrflowException 异常。返回对缓冲区的引用 |
abstract ByteBuffer put(int idx, byte b) | 将b复制到缓冲区中idx指定的位置,返回对缓冲区的引用 |
2 通道
通道是java.nio.channels.Channel的实现类,表示到IO目标的已打开的连接。通道都实现了Closeable接口。
有两种方法获取通道。第一种是对支持通道的对象调用getChannel()方法,比如
DatagramSocket | Socket | ServerSocket |
FileInputStream | FileOutputStream | RandomAccessFile |
当然不同对象可能返回不同类型的通道,后面三种都是文件相关的,所以返回FileChannel。而网络相关的Socket返回的是SocketChannel。
第二种方法是使用Files工具中的静态方法newByteChannel方法,返回的当然是文件相关的通道,是SeekableByteChannel的实现。
通道支持的IO方法是read()和write()。比如FileChannel的定义如下:
方法 | 描述 |
---|---|
abstract int read(ByteBuffer bb) | 从调用通道读取字节到bb中,直到缓冲区已满或者不再有输入内容为止。返回实际读取的字节数。如果读到流的末尾,就返回-1 |
abstract int read(ByteBuffer bb,long start) | |
从start指定的文件位置开始,从调用通道读取字节到bb中,直到缓冲区已满或者不再有输入内容为止。不改变当前位置。返回实际读取的字节数,如果start超出文件的末尾,就返回-1 | |
abstract int write(ByteBuffer bb) | 将bb的内容写入调用通道,从当前位置开始。返回写入的字节数 |
abstract int write(ByteBuffer bb,long start) | 从start指定的文件位置开始,将bb中的内容写入调用通道。不改变当前位置。返回写入的字节数 |
选择器
java.nio.channels.Selector 也是NIO的重要概念。关于它的解释可以参考《博客园 - Java NIO Selector 一文全解》。
Java7 对NIO的增强
说起Java7,大家最先想到的资源类增强就是try-with-resource语句。此外,Java7增加了很多扩展以前NIO的类,这里简单介绍一些。
Path 接口
Path 描述的是目录结果中文件的位置信息,继承了Comparable
Path 提供了大量操作目录的方法,主要的有下面这些:
boolean isAbsolute();
Path getFileName();
Path getParent();
int getNameCount();
Path getName(int index);
Path subpath(int beginIndex, int endIndex);
Path resolve(Path other);
Path resolveSibling(Path other);
Path relativize(Path other);
Path toAbsolutePath();
File toFile();
看到有的方法和索引相关,有的和数量有关。Path的设计和之前的目录有一点差别,它将路径中的每一级都当成一个元素,从根目录开始索引是0。比如getName方法,它获取路径中对应索引的元素,如果是0就是最靠近根目录的部分。下面的程序正常结束:
Path path = Paths.get("/Users/sheldon/IdeaProjects/center/pom.xml");
assert path.getName(0).toString().equals("Users");
assert path.getName(1).toString().equals("sheldon");
老File对象可以调用toPath()方法转换为Path对象,Path对象也可以调用toFile()方法转为File对象。
Files 类
从名字看就知道它是个工厂类,和Arrays/Collections类似,它提供了大量方便生成和处理目录文件的方法,这里就不列举了。可以去看源代码,基本上你能想到的使用场景他都覆盖了。Java8 还给他增加了几个返回流的方法,比如lines()/list()/walk()等。
有些方法的参数是OpenOption类型的,这个枚举接口有几个实现,最常用的StandardOpenOption中定义了一些文件打开选项:
枚举 | 描述 |
---|---|
APPEND | 输出被写入文件的末尾 |
CREATE | 如果文件不存在,就创建文件 |
CREATE_NEW | 只有当文件不存在时才创建文件 |
DELETE_ON_CLOSE | 当文件被关闭时删除文件 |
DSYNC | 对文件的修改被立即写入物理文件。正常情况下,出于效率考虑,对文件的修改由文件系统进行缓冲,只有当需要时才写入文件中 |
READ | 为输入操作打开文件 |
SPARSE | 指示文件是稀疏的,这意味着文件中可能没有完全填满数据。如果文件系统不支持稀疏文件,那么会忽略该选项 |
SYNC | 导致对文件或文件中元数据的修改被立即写入物理文件 |
TRUNCATE_EXISTING | 将为输出操作而打开的、之前就存在的文件的长度减少到0 |
WRITE | 为输出操作打开文件 |
Paths 类
Files是对Path的操作,而Paths是用于生成Path的。这个工厂比较简单,直接通过get方法创建Path,刚刚的代码也用到了。
记住Path被创建并不代表对应文件被打开了
其他接口或类
文件属性接口
这套工具位于java.nio.file.attribute包,顶级接口是BasicFileAttributes。它定义的主要方法有:
方法 | 描述 |
---|---|
FileTime creationTime() | 返回文件的创建时间。如果文件系统没有提供创建时间,就返回一个依赖于实现的时间值 |
Object fileKey() | 返回文件键。如果不支持,就返回null |
boolean isDirectory() | 如果文件表示目录,就返回true |
boolean isOther() | 如果文件不是文件、符号链接或目录,就返回true |
boolean isRegularFile() | 如果文件是常规文件,而不是目录或符号链接,就返回true |
boolean isSymbolicLink() | 如果文件是符号链接,就返回true |
FileTime lastAccessTime() | 返回文件的最后一次访问时间。如果文件系统没有提供最后访问时间,就返回一个依赖于实现的时间值 |
FileTime lastModifiedTime() | 返回文件的最后一次修改时间 。如果文件系统没有提供最后一次修改时间,就返回一个依赖于实现的时间值 |
long size() | 返回文件的大小 |
它还有几个派生接口,可能在不同场景下有用。
文件系统类
FileSystem、FileSystems和FileStore类,在特定场景下很有用,比如专门提供快速读写的文件系统。
