Channels
Introducing Channels
_channel_对象代表了一个开放连接到能够执行IO操作的硬件设备,如文件,网络流,应用组件。_channel_使用_buffer_作为一个传输数据载体。
Channel and Its Children
所有_channel_类都是java.nio.channels.Channel
接口的实现类。它声明了两个方法:
void close()
:关闭_channel_boolean isOpen()
:检查_channel_是否开启着
所有_channel_都共用这两个方法,为了支持IO操作,有两个扩展接口:
WritableByteChannle
ReadableByteChannel
以上两个接口都是单向接口(只读或只写),java提供了java.nio.channels.ByteChannel
接口,该接口是同时支持读写操作。
java还提供了支持异步关闭的java.nio.channels.InterruptibleChannel
,实现该接口的_channel_具有异步关闭的功能:当一个线程阻塞在io操作上时,其它线程可以关闭该线程,然后该线程会抛出AsynchronousCloseException
。该接口也是可中断的,即其它线程触发该线程的interrupt()
方法,会抛出ClosedByInterruptException
。
两种获得_channel_实例的方法:
- 使用
Channels
工具类 - 通过经典的IO类来获得。
一个例子:
public class UsingChannel {
@Test
public void i2o() throws Exception {
ReadableByteChannel rbc = Channels.newChannel(System.in);
WritableByteChannel wbc = Channels.newChannel(System.out);
copy(rbc, wbc);
}
/**
* 最少的IO操作次数
*
* @param rbc
* @param wbc
* @throws IOException
*/
void copy(ReadableByteChannel rbc, WritableByteChannel wbc) throws IOException {
ByteBuffer buffer = ByteBuffer.allocate(1024);
int read;
while ((read = rbc.read(buffer)) != -1) {
buffer.flip();
wbc.write(buffer);
buffer.compact();
}
buffer.flip();
while (buffer.hasRemaining()) {
wbc.write(buffer);
}
}
/**
* 消除数据的复制移动
*
* @param rbc
* @param wbc
* @throws IOException
*/
void copy2(ReadableByteChannel rbc, WritableByteChannel wbc) throws IOException {
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (rbc.read(buffer) != -1) {
buffer.flip();
while (buffer.hasRemaining()) {
wbc.write(buffer);
}
buffer.clear();
}
}
}
Channels in Depth
Scatter/Gather I/O
提供一个IO操作横跨多个_buffer_的能力。
一个例子:
public void scga() throws Exception {
final int M_size = 1024 * 1024;
FileInputStream fis = new FileInputStream("./pdf");
FileOutputStream fos = new FileOutputStream("./pdfc");
ScatteringByteChannel src = (ScatteringByteChannel) Channels.newChannel(fis);
// 这样工作
// ByteBuffer[] buffers = new ByteBuffer[] {
// ByteBuffer.allocate(M_size), ByteBuffer.allocate(M_size),
// ByteBuffer.allocate(M_size) };
// 这样不工作
ByteBuffer[] buffers = new ByteBuffer[3];
for (ByteBuffer byteBuffer : buffers) {
byteBuffer = ByteBuffer.allocate(M_size);
}
for (ByteBuffer byteBuffer : buffers) {
if (byteBuffer == null) {
System.out.println("foreach can't assign value");
return;
}
}
// 这样工作
// for (int i = 0; i < buffers.length; i++) {
// buffers[i] = ByteBuffer.allocate(M_size);
// }
src.read(buffers);
src.close();
for (ByteBuffer byteBuffer : buffers) {
byteBuffer.flip();
}
GatheringByteChannel dest = (GatheringByteChannel) Channels.newChannel(fos);
dest.write(buffers);
dest.close();
}
这里发现一个我从来都没考虑过的点:foreach的使用,使用foreach时最好是仅取值,而不更新。切记切记!!!
File Channels
抽象类java.nio.channels.FileChannel
用于描述一个文件通道。因为该类实现了InterruptibeChannel
,该类是可中断的,该类同时实现了ByteChannel
,GatheringByteChannel
,ScatteringByteChannel
接口。
文件通道是线程安全的!
FileChannel
提供了如下方法:
Method | Description |
---|---|
void force(boolean metadata) | 将文件的所有更新都提交到存储设备 |
long position() | 返回文件指针的位置 |
FileChannel position(long newPosition) | 指定文件指针的 |
int read(ByteBuffer buffer) | |
int read(ByteBuffer buffer,long position) | 指定开始读取的位置 |
long size() | |
FileChannel truncate(long size) | 截断文件,size是指文件指针的位置,即将文件指针后的文件截断抛弃掉。 |
int write(ByteBuffer buffer) | |
int write(ByteBuffer buffer, long position) |
Locking Files
将一个文件锁定或部分锁定是一个重要的特性,但是知道Java1.4,java才支持该功能。这个功能使得JVM进程独占该文件或部分文件,知道该jvm进程释放文件锁。
尽管我们可以锁定整个文件,但通常最好是锁住一小部分。例如,数据库系统可能会锁住单独的表的一行,而不是整个表,以使其它部分能供外部操作,从而提高吞吐量。
每一个文件锁开始于文件的特定的字节位置,然后占据一定长度的字节。文件锁协调进程访问不同的文件区域。
有两种类型的文件锁,排他锁和共享锁。排他锁使得单个写进程,写入到文件,它禁止额外的文件锁同时应用到该区域。共享锁允许多个读进程,同时读取同一个文件区域,它不会禁止其它的共享锁应用到该区域,但是不允许排他锁应用到该问题。
文件锁属于进程锁,而不是线程锁,因此,同一个jvm进程实例下的线程时共享该文件锁的,这要注意。
FileChannel
声明了四个方法用于获取排他锁或共享锁:
- FileLock lock():阻塞方法,可能抛出
ClosedChannelException
,NonWritableChannelException
,OverlappingFileLockException
,FileLockInterruptionException
,AsynchronousCloseException
异常。 - FileLock lock(long position, long size, boolean shared):
- FileLock tryLock():非阻塞方式的,不管成功与否,立即返回。
以上方法它们每一个都返回一个FileLock
对象实例,该对象封装了文件某个区域的锁。该对象具有如下方法:
- FileChannel channel():返回该文件锁对应的文件通道对象
- void close():会调用ralease()方法释放锁
- boolean isShared():判断是否是共享锁
- boolean isValid():是否有效
- boolean overlaps(long position, long size):判断文件锁锁定的区域与指定的区域重合
- long position():返回文件锁相对于文件的开始位置
- void release():释放文件锁
- long size():返回文件锁锁定的区域大小
- String toString():文件锁的信息
尽管文件锁与文件通道关联,当实际上文件锁是与文件通道相关的具体文件相关的。如果不当心,就会进入竞争状态,甚至是死锁。因此,要总是记得释放锁。