Java NIO简介

来源:互联网 发布:淘宝超级运动会 编辑:程序博客网 时间:2024/05/21 10:53

在JDK1.4时Sun发布了java.nio这个包,顾名思义N代表NEW,即新一代IO通道,相比较于旧的IO流,新的IO通道是同步非阻塞的,因为旧的IO流越来满足不了现在需求,我们需要一种全新的非阻塞方式的IO通道。所以NIO诞生了

NIO中主要分为三大模块: Buffer(缓冲区)、Channel(通道)、Selector(选择器)

其中Channel是最重要的一个模块,我们可以使用的通道有(FileChannel、SocketChannel、ServerSocketChannel等),由于Channel是双向的,它允许我们在将资源或文件写出到缓冲区、或者从缓冲区写入。因此在文件操作中,或者是套接字通信中,我们都可以使用Channel来完成。

Buffer是通道中不可缺少的一个组成部分,缓冲区作为数据信息的载体,它通常用来处理一些数据操作上的问题。

Selector是SelectableChannel 对象的多路复用器。我们可以通过某个套接字通道的 register 方法注册该通道时,定义所需要进行的操作OP_ACCEPT ( 用于套接字接受操作的操作集位) OP_CONNECT ( 用于套接字连接操作的操作集位)  OP_READ(用于读取操作的操作集位) OP_WRITE (用于写入操作的操作集位),当它执行select会阻塞,直到有请求为止,当执行selectedKeys方法时会返回SelectionKey类型的Set集合,通过迭代器迭代我们可以对以上四种状态进行分别处理。

例子一:展示SocketChannel和Selector的使用

import java.io.IOException;import java.net.InetSocketAddress;import java.nio.ByteBuffer;import java.nio.channels.SelectionKey;import java.nio.channels.Selector;import java.nio.channels.ServerSocketChannel;import java.nio.channels.SocketChannel;import java.util.Iterator;import java.util.Set;import java.util.concurrent.TimeUnit;/** * java.nio.channels.Selector * SelectableChannel 对象的多路复用器。  * 可通过调用此类的 open 方法创建选择器,该方法将使用系统的默认选择器提供者创建新的选择器。 * 也可通过调用自定义选择器提供者的 openSelector 方法来创建选择器。 * 通过选择器的 close 方法关闭选择器之前,它一直保持打开状态。 * 选择请求的类型(isAcceptable,isConnectable,isReadable,isWritable)分别执行相应的code */public class SelectorTest {public static void main(String[] args) {try {//新线程执行服务端,selector.select()是阻塞线程的new Thread(new Runnable() {public void run() {ChannelServer.open();}}).start();//延迟两秒主(main)线程执行客户端TimeUnit.SECONDS.sleep(2);ChannelClient.send();} catch (Exception e) {e.printStackTrace();}}//private static class ChannelServer {public static void open() {try {ServerSocketChannel ssc = ServerSocketChannel.open();// 调整此通道的阻塞模式。// 如果向一个或多个选择器注册了此通道,则尝试将此通道置于阻塞模式将导致抛出ssc.configureBlocking(false);ssc.socket().bind(new InetSocketAddress(11011));// 通过调用系统级默认 SelectorProvider 对象的 openSelector 方法来创建新的选择器。Selector selector = Selector.open();// 向给定的选择器注册此通道,返回一个选择键// SelectionKey selectionKey =ssc.register(selector, SelectionKey.OP_ACCEPT);// 轮询while (true) {// 选择一组键,其相应的通道已为 I/O 操作准备就绪。// 此方法是阻塞模式的选择操作。// 仅在至少选择一个通道、调用此选择器的 wakeup 方法,或者当前的线程已中断(以先到者为准)后此方法才返回。// 返回:// 已更新其准备就绪操作集的键的数目,该数目可能为零// 唤醒此方法的方法为wakeUp[selector.wakeup()];int readyChannels = selector.select();if (readyChannels == 0)continue;// 返回此选择器的已选择键集。// 可从已选择键集中移除键,但是无法直接添加键。// 试图向该键集中添加对象会导致抛出 UnsupportedOperationException。// 已选择键集是非线程安全的。Set<SelectionKey> selectedKey = selector.selectedKeys();for (Iterator<SelectionKey> iter = selectedKey.iterator(); iter.hasNext();) {SelectionKey key = iter.next();// 测试此键的通道是否已准备好接受新的套接字连接。if (key.isAcceptable()) {System.out.println("execute method::key.isAcceptable()");// 返回为之创建此键的通道。即使已取消该键,此方法仍继续返回通道。ServerSocketChannel server = (ServerSocketChannel) key.channel();// 接受到此通道套接字的连接。// 如果此通道处于非阻塞模式,那么在不存在挂起的连接时,此方法将直接返回 null。// 否则,在新的连接可用或者发生 I/O 错误之前会无限期地阻塞它。// 不管此通道的阻塞模式如何,此方法返回的套接字通道(如果有)将处于阻塞模式。SocketChannel client = server.accept();// 非阻塞client.configureBlocking(false);// 用于读取操作的操作集位。client.register(selector, SelectionKey.OP_READ);// 测试此键的通道是否已完成其套接字连接操作} else if (key.isConnectable()) {System.out.println("execute method::key.isConnectable()");// 测试此键的通道是否已准备好进行读取。} else if (key.isReadable()) {System.out.println("execute method::key.isReadable()");// 写出文件信息ByteBuffer bb = ByteBuffer.allocate(1024);SocketChannel sc = (SocketChannel) key.channel();sc.read(bb);bb.flip();while (bb.hasRemaining()) {System.out.print((char) bb.get());}System.out.println();// 将操作转移为写操作SocketChannel sch = (SocketChannel) key.channel();// 链式的配置注册写操作sch.configureBlocking(false).register(selector, SelectionKey.OP_WRITE);// 测试此键的通道是否已准备好进行写入。} else if (key.isWritable()) {System.out.println("execute method::key.isWritable()");SocketChannel sc = (SocketChannel) key.channel();sc.write(ByteBuffer.wrap("tomorrow is Saturday".getBytes()));selector.close();ssc.close();return;}// 从迭代器指向的 collection 中移除迭代器返回的最后一个元素(可选操作)iter.remove();}}} catch (Throwable t) {t.printStackTrace();}}}private static class ChannelClient {public static void send() {try {// 打开通道SocketChannel channel = SocketChannel.open();// 非阻塞channel.configureBlocking(false);// 打开选择器Selector selector = Selector.open();// 为选择器注册通道channel.register(selector, SelectionKey.OP_CONNECT);// 建立连接channel.connect(new InetSocketAddress("127.0.0.1", 11011));// 轮询while (true) {selector.select();Set<SelectionKey> key = selector.selectedKeys();for (Iterator<SelectionKey> iter = key.iterator(); iter.hasNext();) {SelectionKey selectionKey = iter.next();// 测试此键的通道是否已完成其套接字连接操作if (selectionKey.isConnectable()) {// 连接建立事件,已成功连接至服务器channel = (SocketChannel) selectionKey.channel();// 判断此通道上是否正在进行连接操作if (channel.isConnectionPending()) {// 完成套接字通道的连接过程。// 如果已连接了此通道,则不阻塞此方法并且立即返回 true。如果此通道处于非阻塞模式,那么当连接过程尚未完成时,此方法将返回 false。// 如果此通道处于阻塞模式,则在连接完成或失败之前将阻塞此方法,并且总是返回 true 或抛出描述该失败的、经过检查的异常。// 可在任意时间调用此方法。如果正在调用此方法时在此通道上调用读取或写入操作,则在此调用完成前将首先阻塞该操作。// 如果试图发起连接但失败了,也就是说如果调用此方法导致抛出经过检查的异常,则关闭此通道。channel.finishConnect();System.out.println("connect success !");// 写出信息channel.write(ByteBuffer.wrap("worinixianrenbanban".getBytes("UTF-8")));// 注册读事件channel.register(selector, SelectionKey.OP_READ);}} else if (selectionKey.isReadable()) {System.out.println("sad!  >_<|");// 读出操作SocketChannel sc = (SocketChannel) selectionKey.channel();ByteBuffer bb = ByteBuffer.allocate(1024);sc.read(bb);bb.flip();while (bb.hasRemaining()) {System.out.print((char) bb.get());}// 关闭操作selector.close();channel.close();// 退出轮询方法return;}}// key.clear();}} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}}


例子二:展示SocketChannel和FileChannel的使用

Server:

import java.io.BufferedOutputStream;import java.io.FileOutputStream;import java.io.IOException;import java.net.InetSocketAddress;import java.nio.ByteBuffer;import java.nio.channels.FileChannel;import java.nio.channels.ServerSocketChannel;import java.nio.channels.SocketChannel;// ~ File Information// ====================================================================================================================//套接字通道 -->本地文件通道public class UpLoadServer {public static void main(String[] args) {try {//开启服务ServerSocketChannel ssc = ServerSocketChannel.open();//bind端口号ssc.socket().bind(new InetSocketAddress(10234));//是否阻塞ssc.configureBlocking(true);while (true) {System.out.println("等待写入》》》》》》》》》》");//等待接收SocketChannel sc = ssc.accept();if (sc != null) {//写出文件(本地文件通道)FileChannel fc = new FileOutputStream("C:\\Users\\Administrator\\Desktop\\BM_HOLIDAY_APPLY1.jpg").getChannel();int len = 0;//设置缓冲区容量ByteBuffer buffer = ByteBuffer.allocate(1024);//将网络通道内容写入此缓冲区while ((len = sc.read(buffer)) > 0) {//反转此缓冲区。首先将限制设置为当前位置,然后将位置设置为 0。如果已定义了标记,则丢弃该标记。buffer.flip();//从缓冲区写到本地文件fc.write(buffer);//清除此缓冲区。将位置设置为 0,将限制设置为容量,并丢弃标记。 buffer.clear();}sc.close();fc.close();}}} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}


Client:

import java.io.BufferedInputStream;import java.io.FileInputStream;import java.io.IOException;import java.net.InetSocketAddress;import java.nio.ByteBuffer;import java.nio.channels.FileChannel;import java.nio.channels.SocketChannel;// ~ File Information// ====================================================================================================================//本地文件通道-->套接字通道public class UpLoadClient {public static void main(String[] args) {try {//获取连接SocketChannel sc = SocketChannel.open(new InetSocketAddress("localhost", 10234));//设置为阻塞sc.configureBlocking(true);//拿到文件通道FileChannel fc= new FileInputStream("E:\\文本+图片\\joker.jpg").getChannel();//设置缓冲区大小ByteBuffer buffer = ByteBuffer.allocate(1024);int ind;//public abstract int read(ByteBuffer dst)  throws IOException//将字节序列从此通道中读入给定的缓冲区。while((ind=fc.read(buffer))>0){//反转此缓冲区。首先将限制设置为当前位置,然后将位置设置为 0。如果已定义了标记,则丢弃该标记。buffer.flip();//public abstract int write(ByteBuffer src) throws IOException 从接口 WritableByteChannel 复制的描述 //将字节序列从给定的缓冲区中写入此通道。 sc.write(buffer);//public final Buffer clear()//清除此缓冲区。将位置设置为 0,将限制设置为容量,并丢弃标记。 buffer.clear();}sc.close();fc.close();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}


以上两个例子简单介绍了NIO中代码的具体使用,如果想深入了解,请查看JDK帮助手册

原创粉丝点击