也就是new IO,自Java1.4开始。是基于Block的IO,比基于流的IO快。
它是面向缓冲区的 (Buffered Oriented), 非阻塞IO。

面向流和面向缓冲区

面向流的操作是单向操作。比如InputStream, OutputStream,没有双向的。
面向缓冲区的IO,在文件和程序间有一个类似管道的Channel和一个类似火车的Buffer。火车是双向的,反映的NIO也是双向的。
同时,程序只处理缓冲区,而不处理Channel。

核心组件

Channel Buffer Selector.
基于Channel 和 Buffer就可以完成数据处理。

Buffer

相当于一个数组。对应primitive 类型,每种类型都有各自的Buffer (没有布尔) 。
最常用的是ByteBuffer

创建Buffer可以在堆上、直接内存里。也可以把普通的byte数组包装一下。

public class BufferTest {
    public static void main(String[] args) {
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(100);
        ByteBuffer byteBuffer1 = ByteBuffer.allocate(100);
        byte[] bytes = new byte[10];
        ByteBuffer wrap = ByteBuffer.wrap(bytes);
    }
}

常用方法

put get

public class BufferTest {
    public static void main(String[] args) {
        ByteBuffer byteBuffer = ByteBuffer.allocate(10);
        byteBuffer.put((byte) 10);
        byteBuffer.put((byte) 20);
        byteBuffer.put((byte) 30);
        System.out.println(byteBuffer.get(1));
        byte[] array = byteBuffer.array();
        System.out.println(Arrays.toString(array));
    }
}

核心变量属性

Capacity Limit Position Mark

Capacity -> 数组长。能容纳元素的最大数量。
Limit -> 缓冲区中可以操纵的数据大小
Position -> 下一个要被读/写的下标(位置)

例子

Capacity是设定后不变的,Limit和Position会变。
图片说明
这是我写的一个例子。在这个例子中,长度为10的buffer的前5个字节存储了hello这个字符串。现在Capacity Limit都还是10, Position变成了5, 因为下一个读写位置是5。

如果我现在调用一次flip() 则limit会变成旧的position值 (5) ,position会变成0。flip就是变成读模式。注意读模式不是读东西,而是读取了数据再变成读模式。

public class BufferTest {
    public static void main(String[] args) {
        ByteBuffer byteBuffer = ByteBuffer.allocate(10);
        byteBuffer.put("hello".getBytes(StandardCharsets.UTF_8));
        byteBuffer.flip();
        byte[] bytes = new byte[byteBuffer.limit()];
        byteBuffer.get(bytes);
        System.out.println(new String(bytes));
    }
}

clear()可以清空缓冲区。但是缓冲区里的数据还在,这些数据处于被遗忘的状态,只是不能再用get读取了。
mark()可以记录上一次读写操作后的position位置,如果想还原,可以调用reset()方法。

Channel

Channel是个接口,常用的实现类有FileChannel DatagramChannel SocketChannel ServerSocketChannel
分别对应 文件 UDP TCP

例子 FileChannel png复制

public class ChannelTest {

    private static final String FILEPATH = "E:\\java-projects\\module5\\original.png";
    private static final String COPIED = "E:\\java-projects\\module5\\copy.png";

    public static void main(String[] args) throws Exception{
        FileInputStream fileInputStream = new FileInputStream(FILEPATH);
        FileOutputStream fileOutputStream = new FileOutputStream(COPIED);
        FileChannel channel1 = fileInputStream.getChannel();
        FileChannel channel2 = fileOutputStream.getChannel();
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        while (channel1.read(byteBuffer) != -1) {
            //精华全在这。读取完之后position会走到末尾,需要flip变成读模式。
            byteBuffer.flip();
            channel2.write(byteBuffer);
            //写完数据,position又动了,clear把position还原到头部
            byteBuffer.clear();
        }
        fileOutputStream.close();
        fileInputStream.close();

    }
}

例子 网络编程

客户端

public class NetworkChannel {
    public static void main(String[] args) throws Exception{
        SocketChannel channel = SocketChannel.open();
        channel.connect(new InetSocketAddress(9999));
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        byteBuffer.put("hello world".getBytes(StandardCharsets.UTF_8));
        byteBuffer.flip();
        channel.write(byteBuffer);
        channel.close();
    }
}

服务端

public class NetworkServerChannel {
    public static void main(String[] args) throws Exception{
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.bind(new InetSocketAddress(9999));
        serverSocketChannel.configureBlocking(false);
        while (true) {
            SocketChannel clientChannel = serverSocketChannel.accept();
            if (clientChannel != null) {
                ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                int len = clientChannel.read(byteBuffer);
                System.out.println(new String(byteBuffer.array(), 0, len));
                break;
            } else {
                System.out.println("做点别的事");
                Thread.sleep(100);
            }
        }
    }
}

多路复用

假设三个客户端程序连接三个不同的端口,没多路复用的话你需要三个线程,监听三个端口。如果客户端多了,你需要海量的线程。线程切换很费资源。
有了多路复用器,你只需要一个selector线程去同时监听多个端口。具体到NIO,一个selector可以监听多个Channel。(FileChannel不行,它没继承SelectableChannel)

注册

使用Channel.register(Selector sel,int ops)方法,将一个通道注册到一个选择器时。

​ 第一个参数:指定通道要注册的选择器是谁

​ 第二个参数:指定选择器需要查询的通道操作

可以供选择器查询的通道操作,从类型来分,包括以下四种:

​ (1)可读 : SelectionKey.OP_READ

​ (2)可写 : SelectionKey.OP_WRITE

​ (3)连接 : SelectionKey.OP_CONNECT

​ (4)接收 : SelectionKey.OP_ACCEPT

如果Selector对通道的多操作类型感兴趣,可以用“位或”操作符来实现:int key = SelectionKey.OP_READ | SelectionKey.OP_WRITE ;

例子

public class SelectorTest {
    public static void main(String[] args) throws Exception {
        Selector selector = Selector.open();
        ServerSocketChannel serverSocketChannel1 = ServerSocketChannel.open();
        ServerSocketChannel serverSocketChannel2 = ServerSocketChannel.open();
        ServerSocketChannel serverSocketChannel3 = ServerSocketChannel.open();
        serverSocketChannel1.bind(new InetSocketAddress(7777));
        serverSocketChannel2.bind(new InetSocketAddress(8888));
        serverSocketChannel3.bind(new InetSocketAddress(9999));
        //与selector一起使用的时候必须设置非阻塞
        serverSocketChannel1.configureBlocking(false);
        serverSocketChannel1.register(selector, SelectionKey.OP_ACCEPT);
        serverSocketChannel2.configureBlocking(false);
        serverSocketChannel2.register(selector, SelectionKey.OP_ACCEPT);
        serverSocketChannel3.configureBlocking(false);
        serverSocketChannel3.register(selector, SelectionKey.OP_ACCEPT);
        //会阻塞。直到至少有一个channel就绪。
        while (selector.select() > 0) {
            //就绪的集合保存在SelectedKeys集合中
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                if (key.isAcceptable()) {
                    //服务器的channel对象
                    ServerSocketChannel channel = (ServerSocketChannel) key.channel();
                    SocketChannel clientChannel = channel.accept();
                    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                    int len = clientChannel.read(byteBuffer);
                    System.out.println(new String(byteBuffer.array(), 0, len));
                    clientChannel.close();
                }
                iterator.remove();
            }
            serverSocketChannel1.close();
            serverSocketChannel2.close();
            serverSocketChannel3.close();
        }
    }
}