程序读取数据模型

传统 IO 和 NIO 的不同

传统 IO 特点

  • 面向流。
  • 单向(只能读或只能写)。
  • 程序读写间接和操作系统交互。
  • 读写线程阻塞。

NIO 特点

  • 面向缓冲区。
  • 双向(既能读,又能写)。
  • 程序读写通过 Channel,程序和 Channel 之间的交互通过缓冲区。
  • 读写线程不阻塞。(然而 selector 的 select() 会阻塞)。

NIO 读取数据模型

NIO 在读取数据时,是数据通过 Channel 写入缓冲区,程序从缓冲区读取数据。写数据也是同理。

NIO 四大外围组件

NIO 有 4 个外围组件,它们是:

  • Buffer:为了读或写,存储数据的容器。
  • Channel:与某些组件建设连贯,相似 Java IO 中的流,进行读或写操作,然而和 IO 流不同的是,Channel 是双向的。
  • Charsets:蕴含字符集(charsets)、解码器(decoders)、编码器(encoders),能够用来进行 byte 与 unicode 的转换。
  • Selector:通过 Selector,能够与多个 Channel 一起工作。

Buffer

Buffer 是一个存储固定数据大小、存储 Java 根本数据类型数据的容器。一个 Buffer 由上面这些组成:

• Capacity :容器容量• Limit: 不能读或写的索引• Position: 下一个读或写的元素索引• Flip: 切换 IO 操作(读写操作)• Rewind: 设置 position = 0,limit 放弃不变• Mark: 在 Buffer 标记一个地位• Reset: 将 position 重置为 mark 的地位

间接字节缓冲区

Java 能够应用 ByteBuffer 的 allocateDirect 办法创立间接缓冲区:

ByteBuffer directBuffer = ByteBuffer.allocateDirect(1024);

相比拟于非间接缓冲区。IO 效率会更高。因为咱们在应用非间接缓冲区时,操作系统会创立一个长期间接缓冲区,将非间接缓冲区中的内容读入创立的长期间接缓冲区再进行操作。如果单从这点来看,应用间接缓冲区是 IO 最好的抉择。

然而在 Java 中创立间接缓冲区,是间接调用操作系统的 API 办法来创立的,会绕过 JVM 的内存治理,而且 JVM 针对缓冲区也有优化,所以可能应用间接缓冲区不是最好的形式。

往 ByteBuffer 写入数据

public class ByteBufferReadDemo {    /**     * 读取文件门路     */    private static final String INPUT_FILE = "./resources/input.txt";    /**     * 缓冲区大小     */    private static final int BYTE_BUFFER_LENGTH = 1024;    public static void main(String[] args) {        // 通过 IO 流获取 Channel        try (FileChannel fileChannel = new FileInputStream(INPUT_FILE).getChannel()) {            // 创立 ByteBuffer            ByteBuffer byteBuffer = ByteBuffer.allocate(BYTE_BUFFER_LENGTH);            StringBuilder content = new StringBuilder();            int read = 0;            // 数据从通道读出(写入到 ByteBuffer)            while ((read = fileChannel.read(byteBuffer)) != -1) {                content.append(new String(byteBuffer.array(), 0, read));                // 让 ByteBuffer 复原如初:position = 0, limit = capacity, 丢掉 mark                byteBuffer.clear();            }            System.out.println(content);        } catch (IOException e) {            throw new RuntimeException("Unable to read file", e);        }    }}

Buffer 读写原理

ByteBuffer 继承 Buffer 抽象类,很重要的三个属性是:

    # 读或者写的开始地位    private int position = 0;    # 限度读或写的地位    private int limit;    # 容量    private int capacity;

当新创建一个容量为 10 的 ByteBuffer 对象时,它们三个的地位别离为:

此时 ByteBuffer 为写模式,能够从 position 开始写, limit 是最多能写到的地位,此时 limit = capacity。

随后咱们往 buffer 写入 1234,buffer 内部结构变为:

position 挪动到最小的一个可写地位 5(position = 5)。

此时咱们想要将 buffer 数据读出,就要转成读模式(调用 flip() 办法):

position 挪动到 buffer 中第一个可读的地位,limit 挪动到 buffer 中最初一个可读的地位。

咱们将数据全副读出,此时 buffer 外部变为:

此时 position = limit,证实曾经没有可读。

此时咱们应用 clear(),将 ByteBuffer 复原,此时 buffer 回到写模式(默认是写模式):

compact()和 clear()的区别就是,clear() 会间接让 buffer 回到刚创立的状态,而 compact() 会压缩还没被读的数据。比方咱们只读了 1 和 2:

此时调用 compact(),buffer 内部结构会变为:

Channel

能够通过 Channel 对 Buffer 读写。

创立一个 FileChannel:

        // 创立一个 File 文件对象        final File file = new File(FileChannelReadExample.class.getClassLoader().getResource(path).getFile());          // 依据须要读或者须要写创立不同的 FileChannel                      return fileOperation == FileOperation.READ ? new FileInputStream(file).getChannel() : new FileOutputStream(file).getChannel();

Charsets

蕴含 charsets、decoders、encoders。通过 Charsets 能够进行 unicode 与 byte 的转换。

编码与解码

编码(Encoding):一序列字符 -> 字节。

解码(Decoding):字节 -> 字符。

Charset 应用

        // 拿到 UTF-8 字符集            Charset utf8Charset = Charset.forName("UTF-8");        // 编码        ByteBuffer byteBuffer = utf8Charset.encode("zhangsan");        // 解码        System.out.println(utf8Charset.decode(byteBuffer));

Selector

Selector 能够多路复用 SelectableChannel,当 SelectableChannel 有 IO 事件产生时,Selector 就会告诉咱们的程序。

这里的 IO 事件(SelectableChannel 连贯 Selectors 的事件)包含:

  1. Connect
  2. Accept
  3. Read
  4. Write

SelectableChannel:能够多路复用的连贯 Selecotr 的通道。

Server/Client Demo

应用 Java NIO 实现一个简略的 Server、Client 示例:

Server

public class Server {    protected static final String HOST = "127.0.0.1";    protected static final int PORT = 8899;    public static void main(String[] args) {        try {            ServerSocketChannel serverSocket = ServerSocketChannel.open();            InetSocketAddress hostAddress = new InetSocketAddress(HOST, PORT);            // 绑定端口            serverSocket.bind(hostAddress);            // 设置为非阻塞            serverSocket.configureBlocking(false);            // 创立 selector            Selector selector = Selector.open();            // Channel 注册到 selector(ACCEPT 事件)            serverSocket.register(selector, serverSocket.validOps(), null);            while (true) {                // select 是阻塞的                int selectKeyNums = selector.select();                if (selectKeyNums > 0) {                    Set<SelectionKey> selectionKeys = selector.selectedKeys();                    Iterator<SelectionKey> selectionKeyIterator = selectionKeys.iterator();                    while (selectionKeyIterator.hasNext()) {                        SelectionKey selectionKey = selectionKeyIterator.next();                        if (selectionKey.isAcceptable()) {                            // 注册读事件                            registerRead(serverSocket, selector);                        } else if (selectionKey.isReadable()) {                            // 解决写事件                            dealRead(selectionKey);                        } else {                            System.out.println("Invalid selection key");                        }                        // 每次事件操作做完要删除,否则会持续读,报错                        selectionKeyIterator.remove();                    }                }            }        } catch (Exception e) {            e.printStackTrace();        }    }    public static void registerRead(ServerSocketChannel channel, Selector selector) throws IOException {        SocketChannel client = channel.accept();        client.configureBlocking(false);        client.register(selector, SelectionKey.OP_READ, null);        System.out.println(client.getRemoteAddress() + " connected!");    }    public static void dealRead(SelectionKey selectionKey) {        try {            SocketChannel channel = (SocketChannel) selectionKey.channel();            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);            int read = channel.read(byteBuffer);            if (read == -1) {                selectionKey.channel();                channel.close();            } else {                byteBuffer.flip();                System.out.println("from " + channel.getRemoteAddress() + " reve a message: " + StandardCharsets.UTF_8.decode(byteBuffer));            }        } catch (Exception e) {            e.printStackTrace();        }    }}

Client

public class Client {    public static void main(String[] args) {        try {            InetSocketAddress hostAddress = new InetSocketAddress(Server.HOST, Server.PORT);            SocketChannel client = SocketChannel.open(hostAddress);            Scanner sc = new Scanner(System.in);            while (sc.hasNextLine()) {                client.write(StandardCharsets.UTF_8.encode(sc.next()));            }        } catch (Exception e) {            e.printStackTrace();        }    }}

总结

IO 与 NIO 比照:

NIO 四大外围组件:

  1. ByteBuffer (存储数据的容器)
  2. Charsets (不便字符和字节的转化)
  3. Channels (真正 IO 的中央)
  4. Selectors (多路复用 SelectableChannel)

它们四个互相配合,形成 NIO 的流程。