前言:

Java NIO(new inputsteam outputstream)使用通道、缓冲来操作流。它和传统IO的区别在去:1.NIO是面向缓冲、通道的,传统IO是面向流的;2.NIO通道中既可以写又可以读,传统IO只能是单向的;3.NIO可以设置成异步的,传统IO只能是阻塞的。

NIO 的核心类是 Channel(通道)、Buffer(缓冲)、Selector(选择器)。

第一章:Channel

Channel的特点是:既可以从通道中读取数据,又可以写数据到通道;通道的读写可以是异步的;通道的数据不能直接调用,要先读取到一个Buffer中,写入的时候也要通过Buffer。

Channel的实现包括:FileChannel(文件数据读写通道);DatagramChannel(UDP连接数据读写通道);SocketChannel(TCP连接数据读写通道);ServerSocketChannel(监听TCP连接,创建SocketChannel)

测试代码:文件读写

private static void readFile(String name) {
    File file = new File(name);
    if (!file.exists()) {
        System.out.println("没有此文件: " + name);
    }
    try (FileInputStream inputStream = new FileInputStream(file)) {
        FileChannel channel = inputStream.getChannel();
        ByteBuffer buffer = ByteBuffer.allocate(11);
        while (channel.read(buffer) != -1) {
            buffer.flip();
            byte[] bytes = new byte[buffer.limit()];
            buffer.get(bytes);
            buffer.clear();
            System.out.print(new String(bytes, "utf-8"));
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}
private static void writeFile(String name) {
    try (FileOutputStream outputStream = new FileOutputStream(new File(name))) {
        FileChannel channel = outputStream.getChannel();
        ByteBuffer buf;
        for (int i = 0; i < 100; i++) {
            String str = "test num : " + i + "\r\n";
            buf = Charset.forName("utf-8").encode(str);
            channel.write(buf);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

测试代码:端口监听

private static void testServerSocket() throws IOException {
    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    serverSocketChannel.bind(new InetSocketAddress(9090));
    SocketChannel socketChannel = serverSocketChannel.accept();
    ByteBuffer buf = ByteBuffer.allocate(1024);
    int read = socketChannel.read(buf);
    System.out.println(read);
    buf.flip();
    byte[] bytes = new byte[buf.limit()];
    buf.get(bytes);
    buf.clear();
    System.out.println(new String(bytes, "utf-8"));
    socketChannel.close();
}

运行代码,使用 telnet 127.0.0.1 9090 建立连接

 

第二章:Buffer

       Buffer 和IO中的缓存类一样,内部维护了一个数组,Buffer有三个主要的属性:capacity,position,limit和两个状态,读状态和写状态

下图表示Buffer在写状态和读状态的模型

 

       Buffer的用法见代码:

public static void main(String[] args) {
    ByteBuffer buffer = ByteBuffer.allocate(12); //初始化buffer,使用charbuffer为例
   
print("write", buffer); //初始时是写模式,数据从pos写入
   
String str = "testBuffer";

    buffer.put(str.getBytes()); //使用10个字节填充buffer
   
print("write", buffer);

    buffer.flip();//切换成读模式
   
print("read", buffer);

    buffer.get(new byte[3]);//读取三个字节
   
print("read", buffer);

    buffer.compact();//切换成写模式,将没有被读取的数据放到起始位置
   
print("write", buffer);

    buffer.flip();//切换成读模式
   
print("read", buffer);

    buffer.clear();//切换成写模式,直接从最开始写入
   
print("write", buffer);

}
private static void print(String str, Buffer buffer) {
    String sb = str + " , pos: " + buffer.position() +
            " , lim: " + buffer.limit() +
            " , cap: " + buffer.capacity();
    System.out.println(sb);
}

需要注意的方法是 flip 、compact、clear

当然Buffer还有更多的方法调用比如经典的 mark和reset 可以从源码进行理解学习!

第三章:Scatter/Gather

       Java NIO 支持scatter/gater 操作,用于描述从Channel中读取数据和写入数据到Chann中。分散 (scatter) 可以从Channel中读取数据到多个Buffer中。聚集 (gatter)可以将多个Buffer中的数据写入到Channel中。

       代码展示:

String fileName = "test.txt";
try (FileOutputStream outputStream = new FileOutputStream(new File(fileName))) {
    FileChannel channel = outputStream.getChannel();
    ByteBuffer head = ByteBuffer.allocate(4);
    ByteBuffer body = ByteBuffer.allocate(12);
    head.put("head".getBytes());
    body.put("this is body".getBytes());
    head.flip();
    body.flip();
    ByteBuffer[] message = {head, body};
    channel.write(message);
} catch (IOException e) {
    e.printStackTrace();
}
try (FileInputStream inputStream = new FileInputStream(new File(fileName))) {
    FileChannel channel = inputStream.getChannel();
    ByteBuffer head = ByteBuffer.allocate(4);
    ByteBuffer body = ByteBuffer.allocate(12);
    ByteBuffer[] message = {head, body};
    //buffer中写入数据,上一个写完才会写下一个
   
channel.read(message);

    System.out.println(new String(head.array()));
    System.out.println(new String(body.array()));
} catch (IOException e) {
    e.printStackTrace();
}

第四章:通道之间的数据传输

       在Java NIO中如果两个通道中有一个是FileChannel 那么这两个Channel是可以相互通信的,因为FileChannel 有transferFrom()从另一个通道拷贝数据到自身和tansferTo()拷贝数据到另一个通道中的操作;

代码展示如下:

public static void main(String[] args) throws InterruptedException {
    /*测试数据冲socket中写入到文本中
    new Thread(ChannelCopy::copyToFile).start();
    Thread.sleep(2000);
    new Thread(() -> {
        try (Socket socket = new Socket("127.0.0.1", 9090);
             OutputStream outputStream = socket.getOutputStream();) {
            outputStream.write("test copy socket data to file".getBytes());
            outputStream.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }).start();*/
   
new Thread(ChannelCopy::copyToSocket).start();

    Thread.sleep(2000);
    new Thread(() -> {
        try (Socket socket = new Socket("127.0.0.1", 9090);
             InputStream inputStream = socket.getInputStream()) {
            byte[] bytes = new byte[100];
            inputStream.read(bytes);
            System.out.println(new String(bytes));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }).start();
}
//获取一个socket连接
private static SocketChannel getSocket() throws IOException {

    ServerSocketChannel channel = ServerSocketChannel.open();
    channel.bind(new InetSocketAddress(9090));
    return channel.accept();
}
//socket中获取数据写入到文本中
private static void copyToFile() {

    String fileName = "test.txt";
    try (FileOutputStream outputStream = new FileOutputStream(new File(fileName))) {
        SocketChannel socket = getSocket();
        FileChannel channel = outputStream.getChannel();
        channel.transferFrom(socket, 0, 100);
    } catch (IOException e) {
        e.printStackTrace();
    }
}
//将数据从文本中写入到socket
private static void copyToSocket() {

    String fileName = "test.txt";
    try (FileInputStream outputStream = new FileInputStream(new File(fileName))) {
        SocketChannel socket = getSocket();
        FileChannel channel = outputStream.getChannel();
        channel.transferTo(0, 100, socket);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

第五章:Selector的使用

       Selector 主要用于获取满足状态的Channel进行操作。主要步骤如下:

  1. 开启一个选择器;
  2. 开启一个Channel,这个Channel要继承自 SelectableChannel
  3. 设置Channel的为非阻塞状态
  4. Channel注册Selector 和指定的状态,状态包含 accept,read,write,connect
  5. (可选)为Channel绑定特定对象,用于操作
  6. Selector进行选择,注意超时和唤醒
  7. Selector 获取满足条件的对象 以SelectedKey的对象集合返回
  8. 遍历SelectedKey的集合,每个Selected可以获取Channel和Selector
  9. 通过判断SelectedKey的不同状态,进行对应操作
  10. Selector 不使用时可以关闭,但不会关闭Channel

代码展示

new Thread(() -> {
    try {
        //开启一个Selector
       
Selector sel_ser = Selector.open();

        //开启服务器server
       
ServerSocketChannel server = ServerSocketChannel.open();

        //设置Channel为非阻塞状态
       
server.configureBlocking(false);

        server.bind(new InetSocketAddress(9091));
        server.register(sel_ser, SelectionKey.OP_ACCEPT);
        while (true) {
            //监听满足的channel
           
sel_ser.select();

            Iterator<SelectionKey> iterator = sel_ser.selectedKeys().iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                if (key.isAcceptable()) {
                    ServerSocketChannel channel = (ServerSocketChannel) key.channel();
                    SocketChannel socket = channel.accept();
                    socket.configureBlocking(false);
                    socket.register(sel_ser, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
                    iterator.remove();//移除,不然会引起异常
               
}

                if (key.isWritable()) {
                    SocketChannel socket = (SocketChannel) key.channel();
                    ByteBuffer buffer = ByteBuffer.allocate(1000);
                    buffer.put(("message come from server " + new Date() + "\n").getBytes());
                    buffer.flip();
                    socket.write(buffer);
                }
                if (key.isReadable()) {
                    SocketChannel socket = (SocketChannel) key.channel();
                    ByteBuffer buffer = ByteBuffer.allocate(100);
                    socket.read(buffer);
                    buffer.flip();
                    byte[] bytes = new byte[buffer.remaining()];
                    buffer.get(bytes);
                    System.out.println(new String(bytes).trim());
                }
            }
            Thread.sleep(2000);
        }
    } catch (IOException | InterruptedException e) {
        e.printStackTrace();
    }
}).start();
try {
    //创建Selector 注册在两个SocketChannel
   
Selector cli_ser = Selector.open();

    SocketChannel socket_1 = SocketChannel.open(new InetSocketAddress(9091));
    SocketChannel socket_2 = SocketChannel.open(new InetSocketAddress(9091));
    socket_1.configureBlocking(false);
    socket_2.configureBlocking(false);
    socket_1.register(cli_ser, SelectionKey.OP_WRITE | SelectionKey.OP_READ);
    socket_2.register(cli_ser, SelectionKey.OP_WRITE | SelectionKey.OP_READ);
    //监听Select
   
while (true) {

        cli_ser.select();
        Iterator<SelectionKey> iterator = cli_ser.selectedKeys().iterator();
        while (iterator.hasNext()) {
            SelectionKey key = iterator.next();
            if (key.isWritable()) {
                SocketChannel socket = (SocketChannel) key.channel();
                ByteBuffer buffer = ByteBuffer.allocate(100);
                buffer.put(("message come from client " + new Date() + "\n").getBytes());
                buffer.flip();
                socket.write(buffer);
            }
            if (key.isReadable()) {
                SocketChannel socket = (SocketChannel) key.channel();
                ByteBuffer buffer = ByteBuffer.allocate(1000);
                socket.read(buffer);
                buffer.flip();
                byte[] bytes = new byte[buffer.remaining()];
                buffer.get(bytes);
                System.out.println(new String(bytes).trim());
            }
        }
        Thread.sleep(2000);
    }
} catch (IOException | InterruptedException e) {
    e.printStackTrace();
}

 

 

posted on 2018-01-08 20:20  Stark_Tan  阅读(159)  评论(0编辑  收藏  举报