Java NIO之Channel
介绍
定义
Channel(管道):A channel represents an open connection to an entity such as a hardware device, a file, a network socket,
or a program component that is capable of performing one or more distinct I/O operations, for example reading or writing.
管道可以将其理解为一条连接,可以通过这条连接传输数据。
java nio数据传输介质是管道,就像java io中数据传输介质是流一样。不过io中的流是分方向的输入流和输出流,
但是nio的管道是双向的既可以输出数据也可以读取数据。当然管道都是基于Buffer对象读取和写入数据的。
java nio借助Channel和Selector实现单线程的多路复用IO.在聊天服务器中会使用到这种技术。
Channel实现
Channel接口的实现有:
FileChannel(操作文件相关)
SocketChannel(TCP通信模型客户端Channel)
ServerSocketChannel(TCP通信服务器端Channel)
DatagramChannel(UDP数据报Channel)
备注:以下的学习时基于FileChannel来实现和学习的,其它几个Channel实现在网络高级编程中会使用到。
Channel接口
Channel接口就只定义了两个方法isOpen()和close()方法。
/**
* 管道表示和磁盘、文件、网络套接字或程序组件的一条连接,它可以用来执行IO操作比如读取数据或写入数据。
*
* A channel represents an open connection to an entity such as a hardware
* device, a file, a network socket, or a program component that is capable of
* performing one or more distinct I/O operations, for example reading or
* writing.
*
* Channels are, in general, intended to be safe for multithreaded access
* as described in the specifications of the interfaces and classes that extend
* and implement this interface.
*/
public interface Channel extends Closeable {
/**
* Tells whether or not this channel is open. </p>
*/
public boolean isOpen();
/**
* Closes this channel.
*/
public void close() throws IOException;
}
jdk nio隐藏细节太多了,如果真要看源码最好还是看openjdk的源码
FileChannel文件管道应用
FileChannel是一个抽象类,可以通过以下方法获得其实例对象
FileInputStream.getChannel()
FileOutputStream.getChannel()
RandomAccessFile.getChannel()
FileChannel核心方法
/**
* A channel for reading, writing, mapping, and manipulating a file.
*/
public abstract class FileChannel
extends AbstractInterruptibleChannel
implements SeekableByteChannel, GatheringByteChannel, ScatteringByteChannel
{
// -- Channel operations --
/**
* Reads a sequence of bytes from this channel into the given buffer.
*
* Bytes are read starting at this channel's current file position, and
* then the file position is updated with the number of bytes actually
* read. Otherwise this method behaves exactly as specified in the
* ReadableByteChannel interface.
*/
public abstract int read(ByteBuffer dst) throws IOException;
/**
* Writes a sequence of bytes to this channel from the given buffer.
*
* Bytes are written starting at this channel's current file position
* unless the channel is in append mode, in which case the position is
* first advanced to the end of the file. The file is grown, if necessary,
* to accommodate the written bytes, and then the file position is updated
* with the number of bytes actually written. Otherwise this method
* behaves exactly as specified by the WritableByteChannel
* interface.
*/
public abstract int write(ByteBuffer src) throws IOException;
// -- Other operations --
/**
* Returns this channel's file position.
*/
public abstract long position() throws IOException;
/**
* Sets this channel's file position.
*/
public abstract FileChannel position(long newPosition) throws IOException;
/**
* Returns the current size of this channel's file.
*/
public abstract long size() throws IOException;
/**
* Truncates this channel's file to the given size.
*
* If the given size is less than the file's current size then the file
* is truncated, discarding any bytes beyond the new end of the file. If
* the given size is greater than or equal to the file's current size then
* the file is not modified. In either case, if this channel's file
* position is greater than the given size then it is set to that size.
*
*/
public abstract FileChannel truncate(long size) throws IOException;
/**
* Transfers bytes from this channel's file to the given writable byte
* channel.
*/
public abstract long transferTo(long position, long count,
WritableByteChannel target)
throws IOException;
/**
* Transfers bytes into this channel's file from the given readable byte
* channel.
*/
public abstract long transferFrom(ReadableByteChannel src,
long position, long count)
throws IOException;
/**
* Reads a sequence of bytes from this channel into the given buffer,
* starting at the given file position.
*/
public abstract int read(ByteBuffer dst, long position) throws IOException;
/**
* Writes a sequence of bytes to this channel from the given buffer,
* starting at the given file position.
*/
public abstract int write(ByteBuffer src, long position) throws IOException;
// -- Memory-mapped buffers --
/**
* Maps a region of this channel's file directly into memory.
* 具体参考源码
*/
public abstract MappedByteBuffer map(MapMode mode,
long position, long size)
throws IOException;
// -- Locks --
/**
* Acquires an exclusive lock on this channel's file.
*
* An invocation of this method of the form fc.lock() behaves
* in exactly the same way as the invocation
*/
public final FileLock lock() throws IOException {
return lock(0L, Long.MAX_VALUE, false);
}
/**
* Attempts to acquire an exclusive lock on this channel's file.
*
* An invocation of this method of the form fc.tryLock()
* behaves in exactly the same way as the invocation
*/
public final FileLock tryLock() throws IOException {
return tryLock(0L, Long.MAX_VALUE, false);
}
}
FileChannel的position和Buffer的position属性是不同的意思,FileChannel的position指的是文件指针位置,
Buffer的position指的是子节数组中下标位置。
FileChannel读写数据分析
ByteBuffer buffer = ByteBuffer.allocate(10);
buffer.put("hello".getBytes());
//从Buffer对象中读取数据时,要先调用flip方法
buffer.flip();
fileChannel.write(buffer);
备注:因为调用write(buffer)写入数据到管道中时,只能读取Buffer对象中position到limit之间的数据,
如果不调用flip方法的话,读取到的Buffer数据可能就是错误的或0。
//向Buffer对象中写入数据时,要先调用clear()方法。
buffer.clear();
fileChannel.read(buffer);
备注:因为调用read(buffer)从管道中读取数据到管道时,如果不先调用clear方法,
那么Buffer对象就不一定是从position为0开始写入数据了,就会导致写入数据到Buffer对象是错误的。
备注:重点还是在于Buffer对象的那几个变量值,即position、limit、capacity。最关键的是position和limit,因为容量初始化之后通常不会变的。
FileChannel的文件拷贝
FileChannel自带的拷贝方法
//nio中Channle 自带提供的拷贝方法
public void testCopyFile2() throws Exception {
// 源文件
FileInputStream inputStream = new FileInputStream(new File("e:/nio/test.jpg"));
// 目标文件
FileOutputStream outputStream = new FileOutputStream(new File("e:/nio/copy.jpg"));
// 获得源文件的通道
FileChannel inChannel = inputStream.getChannel();
// 获得目标文件的通道
FileChannel outChannel = outputStream.getChannel();
// nio自带的考本文件方法
//1、transferFrom(ReadableByteChannel src,long position, long count)
outChannel.transferFrom(inChannel, 0, inChannel.size());
//2、transferTo(long position, long count,WritableByteChannel target)
inChannel.transferTo(0, inChannel.size(), outChannel);
//备注这两个方法是等效的,只是要注意文件读写模式。
//关闭资源 ...
}
基于FileChannel和ByteBuffer实现拷贝
//基于ByteBuffer和FileChannel的文件拷贝
public void testCopyFile() throws Exception {
// 源文件
FileInputStream inputStream = new FileInputStream(new File("e:/nio/2.jpg"));
// 目标文件
FileOutputStream outputStream = new FileOutputStream(new File("e:/nio/2copy.jpg"));
// 获得源文件的通道
FileChannel inChannel = inputStream.getChannel();
// 获得目标文件的通道
FileChannel outChannel = outputStream.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(10);
boolean flag = true;
while (flag) {
buffer.clear();
// 从管道(和源文件建立的管道)读取数据到buffer对象中
int data = inChannel.read(buffer);
buffer.flip();
if (data == -1) {// 一直读写直到没有数据时退出循环
flag = false;
}
// 从buffer对象中读取数据到管道(通过目标文件建立的管道)
outChannel.write(buffer);
}
//关闭资源
}
我对ByteBuffer对象的理解,是根据结构化的的子节数组,对现实内存的抽象。
关于Buffer的flip()和clear()方法,我的理解是当从Buffer对象中读取数据时,那么要先调用flip()方法,
当要向Buffer对象中写入数据时,要先调用clear()方法。
总结
总结其实学习nio最重要的不是学会它们如何操作,因为步骤性的技术通常是冗长难以记忆的,只要长时间不用就可能会生疏,
但是原理性的东西是内容量比较少的,所以掌握其最核心原理很重要。
参考
1、http://www.ibm.com/developerworks/cn/education/java/j-nio/section5.html
2、http://ifeve.com/file-channel/
3、http://www.cnblogs.com/dolphin0520/p/3916526.html