Java的NIO系统(4) - 概述

发表于2019-12-26,长度5187, 480个单词, 14分钟读完
Flag Counter

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, Iterable, Watchable三个接口。Watchable 接口也是nio包中定义的,表示可以被监听变化的对象。

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类,在特定场景下很有用,比如专门提供快速读写的文件系统。

Written on December 26, 2019
分类: dev, 标签: java
如果你喜欢,请赞赏! davelet