文章目录
1 概述
channel,意为通道,表示IO流和目标打开的连接。channel本身不能直接访问数据,只能与Buffer进行交互。所有的数据都是通过Buffer进行交互的。
- 读:将数据从channel通道读入缓冲区,再从缓冲区获取这个字节。
- 写:将数据写入Buffer,将buffer中数据写入channel。
NIO包中有channel的几种实现
- FileChannel:用于读取、写入、映射和操作文件的通道。
- DatagramChannel:能通过UDP读写网络中的数据。
- SocketChannel:能通过TCP读写网络中的数据。
- ServerSocketChannel:可以监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel。
2 FileChannel
I/O(asynchronous I/O),允许一个进程可以从操作系统请求一个或多个I/O操作而不必等待这些操作的完成。发起请求的进程之后会收到它请求的I/O操作已完成的通知。
2.1 getChannel
需要通过使用inputStream,outputStream或者RandomAccessFile来获取实例
FileChannel fileChannel = new RandomAccessFile(file, "rw").getChannel();
2.2 Scatter和Gatter
分散读取(Scattering Reads)是指从 Channel 中读取的数据“分散” 到多个 Buffer 中。按照缓冲区的顺序,从 Channel 中读取的数据依次将 Buffer 填满。
聚集写入(Gathering Writes)是指将多个 Buffer 中的数据“聚集”
到 Channel。
scatter / gather经常用于需要将传输的数据分开处理的场合
Scattering Reads在移动下一个buffer前,必须填满当前的buffer,这也意味着它不适用于动态消息(译者注:消息大小不固定)。换句话说,如果存在消息头和消息体,消息头必须完成填充(例如 128byte),Scattering Reads才能正常工。
Gatter writer会按照buffer在数组中的顺序,将数据写入到channel,注意只有position和limit之间的数据才会被写入。因此,如果一个buffer的容量为128byte,但是仅仅包含58byte的数据,那么这58byte的数据将被写入到channel中。因此与Scattering Reads相反,Gathering Writes能较好的处理动态消息。
//Gatter
ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body = ByteBuffer.allocate(1024);
//write data into buffers
ByteBuffer[] bufferArray = { header, body };
channel.write(bufferArray);
2.3 常用方法
int read(ByteBuffer dst)//从channel中读取数据到ByteBuffer
long read(ByterBuffer dsts)//从channel中“分散”数据到Bytebuffer[]中
int write(ByteBuffer src)将ByteBuffer中数据写入Channel
long write(ByteBuffer[] srcs)将ByteBuffer中数据“聚集”到Channel
transferFrom(Channel from,long count,long pos)
transferTo(Channel to,long count,long pos)
2.4 demo
@Test
public void testCostTime() throws IOException {
String src = "D:\\mygit\\downloads\\2016年初评成果附件.zip";
String des = "D:\\mygit\\downloads\\copy.zip";
Path from = Paths.get(src);
Path to = Paths.get(des);
long start1 = System.currentTimeMillis();
Files.copy(from,to,NOFOLLOW_LINKS);
long cost1 = System.currentTimeMillis();
System.out.println(cost1-start1);
long start2 = System.currentTimeMillis();
copyByChannel(src,des);
long cost2 = System.currentTimeMillis();
System.out.println(cost2-start2);
long start3 = System.currentTimeMillis();
copyByNioBuffer2(src,des);
long cost3 = System.currentTimeMillis();
System.out.println(cost3-start3);
long start4 = System.currentTimeMillis();
copyByNioDirectBuffer(src,des);
long cost4 = System.currentTimeMillis();
System.out.println(cost4-start4);
}
public void copyByChannel(String src,String des) throws IOException {
RandomAccessFile sr = new RandomAccessFile(src,"r");
RandomAccessFile dr = new RandomAccessFile(des,"rw");
FileChannel schannel = sr.getChannel();
FileChannel dchannel = dr.getChannel();
schannel.transferTo(0L,schannel.size(), dchannel);
}
public void copyByNioBuffer(String src,String des) throws IOException {
RandomAccessFile sr = new RandomAccessFile(src,"r");
RandomAccessFile dr = new RandomAccessFile(des,"rw");
FileChannel schannel = sr.getChannel();
FileChannel dchannel = dr.getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
while ((schannel.read(byteBuffer)) !=-1){
byteBuffer.flip();
dchannel.write(byteBuffer);
byteBuffer.clear();
}
schannel.close();
dchannel.close();
}
public void copyByNioDirectBuffer(String src,String des) throws IOException {
RandomAccessFile sr = new RandomAccessFile(src,"r");
RandomAccessFile dr = new RandomAccessFile(des,"rw");
FileChannel schannel = sr.getChannel();
FileChannel dchannel = dr.getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024);
while ((schannel.read(byteBuffer)) !=-1){
byteBuffer.flip();
dchannel.write(byteBuffer);
byteBuffer.clear();
}
schannel.close();
dchannel.close();
}
// 文件大小为1.4G
// 当增大缓存区大小时,DirectBuffer可能比Buffer快
5127 //Files.copy
10261 // transferTo
7330 // copyByNioBuffer
15681 //copyByNioDirectBuffer
3 DatagramChannel
// 打开dataGramChannel
DatagramChannel channel = DatagramChannel.open();
channel.socket().bind(new InetSocketAddress(9999));
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
// 接收数据
channel.receive(buf);
buf.clear();
buf.put(newData.getBytes());
buf.flip();
// 发送数据
channel.send(buf);
channel.close();
channel.connect(new InetSocketAddress("jenkov.com", 80));
4 SocketChannel
SocketChannel是一个连接到TCP网络套接字的通道。
操作步骤:
- 打开 SocketChannel
- 读写数据
- 关闭 SocketChannel
//打开SocketChannel
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80));
// 写入数据
String newData = "New String to write to file..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());
buf.flip();
while(buf.hasRemaining()) {
socketChannel.write(buf);
}
// 读取数据
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = socketChannel.read(buf)
// 关闭socketChannel
socketChannel.close();
非阻塞模式
将scoketChannel设为非阻塞模式,read和write方法会立即返回,可以异步调用。
serverSocketChannel.configureBlocking(false);
5 ServerSocketChannel
ServerSocketChannel 是一个可以监听新进来的TCP连接的通道, 就像标准IO中的ServerSocket一样。
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(9999));
while(true){
SocketChannel socketChannel =
serverSocketChannel.accept();
//do something with socketChannel...
}