Java NIO用法

jdk1.4引入,提供分块IO操作

 

NIO即New IO,分为标准输入输出NIO,  网络编程NIO

 

标准输入输出NIO

buffer和channel是NIO的核心对象,其中数据放入buffer中,应用程序即channel必须通过buffer来读写数据。

Buffer使用步骤:

写入->调用flip()->读取->clear()/compact()方法清空

:clear清除整个缓冲区,compact清除已读数据

-- buffer类型:Byte,Char,Double,Float,Int,Long,ShortBuffer

 

Channel用于读写数据,可看作流。

特点:双向读写,可异步读写,读写必须通过buffer

--channel类型:File、Datagram(udp)、SocketChannel(tcp)、ServerSocket(tcp连接)Channel

 

文件中读取

//1 获取通道

FileInputStream fin = new FileInputStream("E:\\hello.txt");

FileChannel fc = fin.getChannel();

//2 创建缓冲区

ByteBuffer buffer = ByteBuffer.allocate(1024);

//3 将数据从通道读到缓冲区

int res = fc.read(buffer);



写入文件

Byte[] message = new Byte[]{1, 2, 3};

//1 获取一个通道

FileOutputStream out = new FileOutputStream("E:\\hello.txt");

FileChannel fc = out.getChannel();

//2 创建缓冲区,将数据写入缓冲区

ByteBuffer buffer = ByteBuffer.allocate(1024);

for (Byte i: message) {

buffer.put(i);

}

buffer.flip();

//3 缓冲区数据写入通道

fc.write(buffer);



文件拷贝(读写结合)

//1 声明源文件和目标文件

FileInputStream fi = new FileInputStream(new File("E:\\src.txt"));

FileOutputStream fo = new FileOutputStream(new File("E:\\dst.txt"));

//2 获得传输通道channel

FileChannel inChannel = fi.getChannel();

FileChannel outChannel = fo.getChannel();

//3 获得容器buffer

ByteBuffer buffer = ByteBuffer.allocate(1024);

while (true) {

//3 判断是否读完文件

int eof = inChannel.read(buffer);

if (eof == -1) {

break;

}

//4 重设一下buffer的position=0,limit=position

buffer.flip();

//5 开始写

outChannel.write(buffer);

//6 写完要重置buffer,重设position=0,limit=capacity

buffer.clear();

}

inChannel.close();

outChannel.close();

fi.close();

fo.close();

 

注意点

无数据时,xx.read(buffer)  返回 -1。

 

网络编程NIO

NIO第三个对象Selector

Selector selector = Selector.open();

 

Selector可单线程处理多个channel,可监听IO事件的发生,可将Channel注册到selector上

但Channel必须有异步模式(FileChannel没所以不行,SocketChannel可以),注册方法如下:

channel.configureBlocking(false);

SelectionKey key =channel.register(selector,SelectionKey.OP_READ);

 

selector四个常量(感兴趣监听的事件类型) interest set:

1. SelectionKey.OP_CONNECT

2. SelectionKey.OP_ACCEPT

3. SelectionKey.OP_READ

4. SelectionKey.OP_WRITE

注:可用  xxx | xxxx   接收多种事件。

 

判断是否interest set

int interestSet = selectionKey.interestOps();

boolean isInterestedInAccept = interestSet & SelectionKey.OP_ACCEPT;

boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT;

boolean isInterestedInRead = interestSet & SelectionKey.OP_READ;

boolean isInterestedInWrite = interestSet & SelectionKey.OP_WRITE;

 

判断事件就绪Ready set

selectionKey.isAcceptable();

selectionKey.isConnectable();

selectionKey.isReadable();

selectionKey.isWritable();

 

 

获取channel和selector

Channel channel = selectionKey.channel();

Selector selector = selectionKey.selector();

 

给selector附加一个对象

selectionKey.attach(theObject);

Object attachedObj = selectionKey.attachment();

SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);

 

获取就绪channel

Set selectedKeys = selector.selectedKeys();

Iterator keyIterator = selectedKeys.iterator();

while(keyIterator.hasNext()) {

SelectionKey key = keyIterator.next();

if(key.isAcceptable()) {

    // a connection was accepted by a ServerSocketChannel.

} else if (key.isConnectable()) {

    // a connection was established with a remote server.

} else if (key.isReadable()) {

    // a channel is ready for reading

} else if (key.isWritable()) {

    // a channel is ready for writing

}

keyIterator.remove();

}

 

完整例子

package test_2018_05_08;

 

import java.io.IOException;

import java.net.InetSocketAddress;

import java.net.ServerSocket;

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;

 

public class MultiPortEcho {

    private int ports[];

    private ByteBuffer echoBuffer = ByteBuffer.allocate(1024);

 

    public MultiPortEcho(int ports[]) throws IOException {

        this.ports = ports;

        go();

    }

 

    private void go() throws IOException {

        // 1. 创建一个selector,select是NIO中的核心对象

        // 它用来监听各种感兴趣的IO事件

        Selector selector = Selector.open();

        // 为每个端口打开一个监听, 并把这些监听注册到selector中

        for (int i = 0; i < ports.length; ++i) {

            //2. 打开一个ServerSocketChannel

            //其实我们没监听一个端口就需要一个channel

            ServerSocketChannel ssc = ServerSocketChannel.open();

            ssc.configureBlocking(false);//设置为非阻塞

            ServerSocket ss = ssc.socket();

            InetSocketAddress address = new InetSocketAddress(ports[i]);

            ss.bind(address);//监听一个端口

            //3. 注册到selector

            //register的第一个参数永远都是selector

            //第二个参数是我们要监听的事件

            //OP_ACCEPT是新建立连接的事件

            //也是适用于ServerSocketChannel的唯一事件类型

            SelectionKey key = ssc.register(selector, SelectionKey.OP_ACCEPT);

            System.out.println("Going to listen on " + ports[i]);

        }

        //4. 开始循环,我们已经注册了一些IO兴趣事件

        while (true) {

            //这个方法会阻塞,直到至少有一个已注册的事件发生。当一个或者更多的事件发生时

            // select() 方法将返回所发生的事件的数量。

            int num = selector.select();

            //返回发生了事件的 SelectionKey 对象的一个 集合

            Set selectedKeys = selector.selectedKeys();

            //我们通过迭代 SelectionKeys 并依次处理每个 SelectionKey 来处理事件

            //对于每一个 SelectionKey,您必须确定发生的是什么 I/O 事件,以及这个事件影响哪些 I/O 对象。

            Iterator it = selectedKeys.iterator();

            while (it.hasNext()) {

                SelectionKey key = (SelectionKey) it.next();

                //5. 监听新连接。程序执行到这里,我们仅注册了 ServerSocketChannel

                //并且仅注册它们“接收”事件。为确认这一点

                //我们对 SelectionKey 调用 readyOps() 方法,并检查发生了什么类型的事件

                if ((key.readyOps() & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT) {

                    //6. 接收了一个新连接。因为我们知道这个服务器套接字上有一个传入连接在等待

                    //所以可以安全地接受它;也就是说,不用担心 accept() 操作会阻塞

                    ServerSocketChannel ssc = (ServerSocketChannel) key.channel();

                    SocketChannel sc = ssc.accept();

                    sc.configureBlocking(false);

                    // 7. 讲新连接注册到selector。将新连接的 SocketChannel 配置为非阻塞的

                    //而且由于接受这个连接的目的是为了读取来自套接字的数据,所以我们还必须将 SocketChannel 注册到 Selector上

                    SelectionKey newKey = sc.register(selector, SelectionKey.OP_READ);

                    it.remove();

                    System.out.println("Got connection from " + sc);

                } else if ((key.readyOps() & SelectionKey.OP_READ) == SelectionKey.OP_READ) {

                    // Read the data

                    SocketChannel sc = (SocketChannel) key.channel();

                    // Echo data

                    int bytesEchoed = 0;

                    while (true) {

                        echoBuffer.clear();

                        int r = sc.read(echoBuffer);

                        if (r <= 0) {

                            break;

                        }

                        echoBuffer.flip();

                        sc.write(echoBuffer);

                        bytesEchoed += r;

                    }

                    System.out.println("Echoed " + bytesEchoed + " from " + sc);

                    it.remove();

                }

            }

            // System.out.println( "going to clear" );

            // selectedKeys.clear();

            // System.out.println( "cleared" );

        }

    }

 

    static public void main(String args2[]) throws Exception {

        String args[] = {"9001", "9002", "9003"};

        if (args.length <= 0) {

            System.err.println("Usage: java MultiPortEcho port [port port ...]");

            System.exit(1);

        }

        int ports[] = new int[args.length];

        for (int i = 0; i < args.length; ++i) {

            ports[i] = Integer.parseInt(args[i]);

        }

        new MultiPortEcho(ports);

    }

}

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(JAVA)