BtyeChannel
BtyeChannel接口提供对通道进行字节读、写的抽象方法。实际上什么都不做,只是继承了ReadableBtyeChannel接口和WriteableByteChannel。
SeekableByteChannel
SeekableByteChannel接口继承了ByteChannel,但它还提供了position()、size()等方法。
一个SeekableByteChannel可以说是保留了通道的当前position,并且该position被改变的字节通道。
SeekableByteChannel会被连接到某个实体,通常是一个文件,这个实体 包含了可变长度的可被读或者写的字节序列。
通道的当前position可以通过position()方法被查询,也可以通过position(long)方法被修改。SeekableByteChannel也提供了对所连接实体的当前size大小的操作方法。当字节写入超过了它的当前size大小时,size会增加;当调用truncate()方法截断时,size会减少。
SeekableByteChannel继承的read()方法和write()抽象方法,在未来使用上也和BtyeChannel的有所不同。
read():从通道读取字节序列到给定buffer中。字节从通道的当前position开始读取。然后用实际读取的字节数更新position。
否则,该方法的行为与ReadableBtyeChannel接口的read()相同。
write():从给定buffer写入字节序列到通道中。字节从通道的当前position开始写入,除非通道连接到一个文件,并且该文件时通过追加模式打开的,这样的话position首先移动到文件的末尾。 否则,此方法的行为完全按照WritableByteChannel接口。
FileChannel
FileChannel类还只是一个抽象类,它提供了读、写、映射和锁住一个文件的抽象方法。
FileChannel实现了SeekableBtyeChannel接口。所以它有当前position,可以通过position()方法被查询,也可以通过position(long)方法被修改。
除了常见的字节通道的read、write、close()方法,FileChannel还提供了以下的面向文件操作的方法:
调用read(buffer,long)或write(buffer,long)方法,在文件的绝对位置进行字节读或写;
调用mapped()方法,文件的一个region区域可以直接映射到内存中; 对于大型文件,这通常比调用通常的读取或写入方法更高效;
调用force()方法,对文件所做的更新可能会被强制写入到底层存储设备,从而确保在系统崩溃时数据不会丢失;
调用transferTo()方法,字节可以从该通道的文件传输到另一个通道,反之调用transferFrom()方法亦然,这种方式可以被许多操作系统优化成可以非常快速地直接传入或传出文件系统缓存的方式;
调用lock()方法,文件的某个区域可以是被锁定的;
FileChannel是线程安全的,可以被多个并发线程使用。按照channel接口的规范,close()方法可以在任何时候被调用。在一个进程中,任意时刻只有一个操作能参与通道的position或者改变文件的大小。当第一个操作仍在进程中时,第二个类似这样的操作会阻塞直到第一个操作完成。
一个FileChannel可以通过该类定义的open()方法被创建(内部调用FileSystemProvider,稍后讲解),也可以通过调用 FileInputStream、FileOutputStream、RandomAccessFile实例的getChannel()方法返回得到。在第二种方式下,文件通道的状态与FileInputStream、FileOutputStream、RandomAccessFile这些原始对象的状态紧密相关。 改变通道的位置,无论是明确地或通过读取或写入字节,都会改变原始对象的文件位置,反之亦然。 通过文件通道更改文件的长度将改变通过原始对象看到的长度,反之亦然。 通过写入字节来更改文件的内容将改变原始对象看到的内容,反之亦然。
通过FileInputStream实例的getChannel方法获得的FileChannel只用于读。 通过FileOutputStream实例的getChannel方法获取的通道将只用于写。如果RandomAccessFile实例是使用模式“r”创建的,则它的getChannel方法获得的通道将被打开以供读取。如果RandomAccessFile实例是使用模式“rw”创建的,则它的getChannel方法获得的通道将被打开以供读取和写入。
打开以进行写入的文件通道可以处于追加模式,例如,如果它是从通过调用FileOutputStream(File,boolean)构造函数创建的文件输出流中获取,并为第二个参数传递true。 在此模式下,每次调用相对写入操作时,都会先将位置前移到文件末尾,然后写入请求的数据。 位置的提升和数据的写入是在单个原子操作中完成的是系统相关的,因此是未指定的。
FileSystemProvider
FileChannelImpl
FileChannelImpl是FileChannel的实现类。它有几个重要的成员变量如下:
FileDispatcher nd:用于不同的平台调用native()方法来完成read和write操作。
FileDescriptor fd:文件描述符。
final boolean writable:文件通道是否可写。
final boolean readable:文件通道是否可读。
FileChannelImpl底层是调用FileDispatcher的实现类来完成native read 和native write操作。
FileDispatherImpl
JNIEXPORT jint JNICALL
Java_sun_nio_ch_FileDispatcherImpl_read0(JNIEnv *env, jclass clazz, jobject fdo,
jlong address, jint len)
{
DWORD read = 0;
BOOL result = 0;
HANDLE h = (HANDLE)(handleval(env, fdo));
if (h == INVALID_HANDLE_VALUE) {
JNU_ThrowIOExceptionWithLastError(env, "Invalid handle");
return IOS_THROWN;
}
result = ReadFile(h, /* File handle to read */
(LPVOID)address, /* address to put data */
len, /* number of bytes to read */
&read, /* number of bytes read */
NULL); /* no overlapped struct */
if (result == 0) {
int error = GetLastError();
if (error == ERROR_BROKEN_PIPE) {
return IOS_EOF;
}
if (error == ERROR_NO_DATA) {
return IOS_UNAVAILABLE;
}
JNU_ThrowIOExceptionWithLastError(env, "Read failed");
return IOS_THROWN;
}
return convertReturnVal(env, (jint)read, JNI_TRUE);
}
核心方法是ReadFile方法。
FileChannel和FileInputStream一样,最终均调用了native的ReadFile方法,系统调用函数上是一样的!不同的地方在于,FileInputStream或者RandomAccessFile在读取数据时存在一个数据拷贝的过程。java的对象一般都是在java的堆中的,而native的代码是在native的栈或者堆中的,如果java想用,那么必须有个从native的堆到java的堆中拷贝的过程。本质上,这是内核内存和用户内存之间的数据拷贝。DirectByteBuffer虽然是java堆中的对象,但是引用native的数据,DirectByteBuffer有点类似指针的意思。FileChannel使用了DirectByteBuffer就可以省去拷贝到java堆空间的操作,从而减少内核内存和用户内存之间的数据拷贝。看下文。
参考:简书 Java文件NIO读取的本质——FileInputStream与FileChannel对比