JDK7开始基于NIO提供了一套新的FileSystem逻辑,这套逻辑的核心代码包括:
Files类:一个不同FileSystem系统下的统一工具类,提供了一套各种文件系统通用的方法,底层通过判断所处的文件系统调用对应的实现
Path类:可以针对不同的文件系统定义不同的Path,例如在windows系统下游WindowsPath实现,在sftp系统下有SftpPath实现(apache.sshd的实现)
FileSystemProvider:根据不同的Path判断当前所处的文件系统类型,例如WindowsFileSystemProvider
Nio的文件系统
Files
Files类的设计理念
Files类是一个不同FileSystem系统下的统一工具类,提供了一套各种文件系统通用的方法,底层通过判断所处的文件系统调用对应的实现
以一个通用方法为例:
public static boolean exists(Path path, LinkOption... options) {
if (options.length == 0) {
FileSystemProvider provider = provider(path);
if (provider instanceof ExtendedFileSystemProvider)
return ((ExtendedFileSystemProvider)provider).exists(path);
}
try {
if (followLinks(options)) {
provider(path).checkAccess(path);
} else {
// attempt to read attributes without following links
readAttributes(path, BasicFileAttributes.class,
LinkOption.NOFOLLOW_LINKS);
}
// file exists
return true;
} catch (IOException x) {
// does not exist or unable to determine if file exists
return false;
}
}
Files#exits方法是一个各个场景通用的判断方法,在这个方法中首先根据Path类型获取对于的FileSystemProvider,然后调用provider提供的对应方法
if (followLinks(options)) {
provider(path).checkAccess(path);
} else {
// attempt to read attributes without following links
readAttributes(path, BasicFileAttributes.class,
LinkOption.NOFOLLOW_LINKS);
}
public static <A extends BasicFileAttributes> A readAttributes(Path path, Class<A> type, LinkOption... options) throws IOException {
return provider(path).readAttributes(path, type, options);
}
这里可以看到两个流程都是首先基于provider方法获取FileSystemProvider,然后调用具体Provider的实现方法
因此我们可以得到一个结论,想要自行实现一套FileSystem只需要实现对应的Path和FileSystemProvider,然后基于Files类调用公共能力就可以实现
这一套逻辑典型的实现就是apache.sshd
provider
private static FileSystemProvider provider(Path path) {
return path.getFileSystem().provider();
}
根据这一方法可以看出来,FileSystemProvider被封装在FileSystem中,而FileSystem又被封装在Path中
newInputStream
根据其注释,我们可以得知两点:
Opens a file, returning an input stream to read from the file. The stream will not be buffered,即这个流是一个非缓冲流
The stream will be safe for access by multiple concurrent threads,即这个流是一个线程安全的流
public static InputStream newInputStream(Path path, OpenOption... options) throws IOException {
return provider(path).newInputStream(path, options);
}
这里调用FileSystemProvider#newInputStream方法,以windows系统为例可以看下默认实现补链接
众所周知,在BIO里面也是有对应的FileInputStream存在的,为什么NIO里面还要再搞一套流出来呢?结合下游方法和注释,可以大概推测JDK开发者的用意:
将老的BIO中文件流转换成非阻塞的NIO模式,既兼容了老的流处理逻辑,又提高了吞吐量和大文件处理效率
线程安全
newByteChannel
public static SeekableByteChannel newByteChannel(Path path, OpenOption... options)
throws IOException
{
Set<OpenOption> set;
if (options.length == 0) {
set = Collections.emptySet();
} else {
set = new HashSet<>();
Collections.addAll(set, options);
}
return newByteChannel(path, set);
}
返回的是一个SeekableByteChannel,它实现自ReadableByteChannel,可以把ReadableByteChannel想象成前面nio中SocketChannel和ServerSocketChannel的兄弟
根据注释,ReadableByteChannel的作用是提供一个传输字节流的,且可读的Channel,它的核心能力是把BIO模式转换成非阻塞的NIO模式
而SeekableByteChannle在ReadableByteChannel的基础上实现位置的维护和更改能力,我们的老朋友FileChannel及其实现类FileChannelImpl就是它的实现类
FileSystemProvider
newInputStream
默认实现如下:
public InputStream newInputStream(Path path, OpenOption... options)
throws IOException
{
……
// 构造ReadableByteChannel
ReadableByteChannel rbc = Files.newByteChannel(path, options);
……
// 构造流
return Channels.newInputStream(rbc);
}
该方法可以大概分为两步:
构造ReadableByteChannel,这里可以参考
Files#newByteChannel
方法补链接基于ReadableByteChannel构造InputStream
向下找到Channels#newInputStream
方法
public static InputStream newInputStream(ReadableByteChannel ch) {
Objects.requireNonNull(ch, "ch");
return new ChannelInputStream(ch);
}
这里是基于ReadableByteChannel构造的ChannelInputStream
这个ChannelInputStream实现自InputStream,显然是BIO的产物,但是由于其中封装了ReadableByteChannel属性,让它能够桥接BIO和NIO,并且线程安全
protected final ReadableByteChannel ch;
评论区