Java NIO学习系列二:Channel

  上文总结了Java NIO中的Buffer相关知识点,本文中咱们来总结一下它的好兄弟:Channel。上文有说到,Java NIO中的Buffer通常和Channel配对使用,NIO中的全部IO都起始于一个Channel,一个Channel就至关于一个流,,能够从Channel中读取数据到Buffer,或者写数据到Channel中。缓存

  Channel简介服务器

  FileChannel网络

  SocketChanneldom

  ServerSocketChannel异步

  DatagramChannelsocket

  总结spa

 

1. Channel简介

  Java NIO中的Channel相似流,可是有一些不一样:操作系统

  • Channel既能够支持写也能够支持读,而流则是单向的,只能支持写或者读;
  • Channel支持异步读写;
  • Channel通常和Buffer配套使用,从Channel中读取数据到Buffer中,或从Buffer写入到Channel中;

  Channel的主要实现类有以下几种:code

  • FileChannel,能够对文件读/写数据;
  • DatagramChannel,经过UDP从网络读/写数据;
  • SocketChannel,经过TCP从网络读/写数据;
  • ServerSocketChannel,容许你监听TCP链接,对于每一个TCP链接都建立一个SocketChannel;

 

2. FileChannel

  Java NIO FileChannel是一类文件相连的channel,经过它能够从文件读取数据,或向文件写数据。FileChannel类是Java NIO类库提供的用于代替标准Java IO API来读写文件。FileChannel不能设置为非阻塞模式,只能工做在阻塞模式下。server

2.1 打开FileChannel

  在使用FileChannel以前先要打开它,就I/O类库中有三个类被修改了,用以产生FileChannel,这三个类是:InputStream、OutputStream、RandomAccessFile,以下是如何从RandomAccessFile获取FileChannel:

RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();

2.2 从FileChannel读取数据

  首先须要分配一个Buffer,从FileChannel读取的数据会读到Buffer中(是否是有点绕)。而后调用FileChannel的read()方法来读数据,这个方法会把数据读到Buffer中,返回的int表明读取了多少字节,返回-1表明文件末尾。

ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buf);

2.3 往FileChannel写数据

  经过调用FileChannel的write()方法能够往其中写数据,参数是一个Buffer:

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()) { channel.write(buf); }

  这里write()方法也没有保证一次写入多少数据,咱们只是不断重复直到写完。

2.4 关闭FileChannel

  用完FileChannel以后记得要将其关闭:

channel.close();

2.5 FileChannel位置

  调用FileChannel对象的position()方法能够获取其position,也能够调用position(long pos)设置其position:

// 获取position
long pos channel.position();
// 设置position
channel.position(pos +123);

  若是将position设置到文件末尾后面,而后尝试读取文件,会返回-1;

  若是将position设置到文件末尾后面,而后尝试向文件中写数据,则文件会自动扩展,而且从设置的position位置处开始写数据,这会致使“file hole”,即物理文件会有间隙。

2.6 FileChannel尺寸

  size()方法返回filechannel链接的文件的尺寸大小:

long fileSize = channel.size(); 

2.7 截短FileChannel

  truncate()方法能够截短文件:

channel.truncate(1024);

  如上,将文件截取为1024字节长度。

2.8 FileChannel Force

  FileChannel.force()方法会刷新全部未写入到磁盘的数据到磁盘上。由于操做系统会先将数据缓存到内存中,再一次性写入磁盘,因此不能保证写到channel中的数据是否写入到磁盘上,因此能够调用flush()方法强制将数据写入磁盘。

  force()方法有一个boolean参数,表明是否要写入文件的元数据(好比权限):

channel.force(true);

 

3. SocketChannel

  Java NIO SocketChannel用于和TCP网络socket相连,等同于Java网络包中的Socket。能够经过两种方式来建立一个SocketChannel:

  • 打开一个SocketChannel而且将其和网络上的某台服务器相连;
  • 当有一个链接到达一个ServerSocketChannel时会自动建立一个SocketChannel;

3.1 打开Socket通道

  以下示例说明了如何打开一个SocketChannel:

SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80));

3.2 关闭Socket通道

  对于这种资源类的使用,是要记得及时关闭的:

socketChannel.close();

3.3 从Socket通道读数据

  调用read()方法能够读取数据:

ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = socketChannel.read(buf);

3.4 往Socket通道写数据

  调用其写方法write()能够向其中写数据,使用一个Buffer做为其参数:

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()) { channel.write(buf); }

3.5 工做在非阻塞模式下

  能够将SocketChannel设置为非阻塞模式,设置以后能够异步地调用其connect()、read()和write()方法。

connect()

  对于处于非阻塞模式下的SocketChannel,调用其connect()方法以后会当即返回,即便没有成功创建链接。能够调用finishConnect()方法来获知是否成功创建链接:

socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80)); while(! socketChannel.finishConnect() ){ //wait, or do something else... }

write()

  对于处于非阻塞模式的SocketChannel,调用其write()也会返回,可是可能尚未写入数据。所以你须要在一个循环中调用它,就像前面的例子中看到的那样。

read()

  对于处于非阻塞模式的SocketChannel,调用其read()有可能出现返回int值,可是尚未任何数据读入到buffer中。所以须要关注返回int值,这个能够告诉咱们有多少数据是已经读取的。

非阻塞模式下和Selectors一块儿工做

  非阻塞模式下的SocketChannel适合和Selector一块儿搭配使用。经过往Selector中注册一个或多个SocketChannel,能够经过Selector选择已经就绪的Channel。具体使用稍后会详述。

 

4. ServerSocketChannel

  Java NIO ServerSocketChannel能够监听TCP链接,就像标准Java网络库中的ServerSocket。

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(9999)); while(true){ SocketChannel socketChannel = serverSocketChannel.accept(); //do something with socketChannel... }

4.1 打开ServerSocketChannel

  很简单,调用其open()方法就能够开启一个ServerSocketChannel:

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

4.2 关闭ServerSocketChannel

serverSocketChannel.close();

4.3 监听链接

  调用其accept()方法以后能够监听链接。在一个新的链接到来以前,都处于阻塞状态,一旦新的链接到来,accept()方法将返回一个和这个链接对应的SocketChannel。

  将其放在一个while循环中就能够不断监听新的链接:

while(true){
    SocketChannel socketChannel = serverSocketChannel.accept(); //do something with socketChannel... }

  固然实际中while循环应该使用其余的判断条件而不是true。

4.4 工做在非阻塞模式下

  ServerSocketChannel一样可设置为非阻塞模式,此时调用其accept()会当即返回,若是没有新的链接可能返回null,须要对返回值是否为空进行校验:

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(9999)); serverSocketChannel.configureBlocking(false); while(true){ SocketChannel socketChannel = serverSocketChannel.accept(); if(socketChannel != null){ //do something with socketChannel...  } }

 

5. DatagramChannel

  DatagramChannel用于发送和接收UDP包。由于UDP是一个无链接协议,不是像其余channel同样进行读写操做,而是经过数据包来交换数据。

5.1 打开DatagramChannel

DatagramChannel channel = DatagramChannel.open();
channel.socket().bind(new InetSocketAddress(9999));

  这个例子中打开了一个DatagramChannel,能够经过端口9999接收UDP数据包。

5.2 接收数据

ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
channel.receive(buf);

  调用DatagramChannel的receive()方法能够将接收到的数据包中的数据复制到指定的Buffer中。若是收到的数据大于Buffer容量,默认将其抛弃。

5.3 发送数据

String newData = "New String to write to file..."
                    + System.currentTimeMillis();    
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());
buf.flip();
int bytesSent = channel.send(buf, new InetSocketAddress("jenkov.com", 80));

  在这个例子中是向“jenkov.com”服务器的80端口发送UDP数据包。由于UDP不保证数据传输,因此也不会知道发送的数据包是否被收到。

5.4 链接到指定地址

  能够将DatagramChannel“链接”到一个指定的网络地址。由于UDP协议是无链接的,因此经过这种方式建立链接并不会像基于TCP的channel那样建立一个真实的链接。可是,这样能够“锁定”这个DatagramChannel,使得它只能和一个指定的ip地址交换数据。

channel.connect(new InetSocketAddress("jenkov.com", 80)); 

  在这种状况下(锁定),也能够像其余Channel那样调用read()和write()方法,只不过不可以保证数据必定可以收到。

int bytesRead = channel.read(buf); 
int bytesWritten = channel.write(buf);

 

6. 总结

  本文主要总结了Channel的相关知识,Channel是通道,和Buffer进行交互数据,能够读数据到Buffer中,也能够从Buffer往Channel写数据。Channel主要有下面几种:

  • FileChannel
  • SocketChannel
  • ServerSocketChannel
  • DatagramChannel

  其中FileChannel是和文件交互、SocketChannel和ServerSocketChannel是基于TCP的网络Channel,DatagramChannel是基于UDP的网络Channel。

相关文章
相关标签/搜索