Java NIO(三)NIO之Buffer

系列文章目录

第一篇 Java NIO(一)I/O简介
第一篇 Java NIO(二)BIO
第一篇 Java NIO(三)NIO之Buffer
第一篇 Java NIO(四)NIO之Channel
第一篇 Java NIO(五)NIO之Selector
第一篇 Java NIO(六)Netty


Java NIO(三)NIO之Buffer


前言

上一篇文章中我们简单的聊了下Java的IO体系中的BIO,接下来我们来聊一下Java中的NIO,NIO是对BIO的一次大的革新,在有了底层IO模型——IO多路复用模型的支持,NIO在网络IO方面的表现异常卓越。NIO中核心部分有以下三个,分别是Buffer、Channel、Selector。本文将简单介绍下NIO中的Buffer部分。

注:每篇文章中都会有NIO一个Server端和客户端的简单例子,帮助大家从整体上对NIO有个把握


一、示例代码

客户端代码:

public class TestClient {

    public void start(String host, int port) {
        try {

            for (int i = 0; i < 10; i++) {
                // 连接阶段
                Socket client = new Socket(host, port);

                // 写数据阶段  (对服务器而言是读)
                OutputStream os = client.getOutputStream();
                String name = UUID.randomUUID().toString();
                String msg = "hello server";
                System.out.println("client " + name + " say: " + msg);
                os.write(msg.getBytes());

                // 读服务器传过来的数据  (对服务器而言是写)
                byte[] byteArray = new byte[1024];
                InputStream is = client.getInputStream();
                int k = is.read(byteArray);
                while (k <= 0) {
                    k = is.read(byteArray);
                }
                String content = new String(byteArray);
                System.out.println("server response to client" + name + ": " + content);
                is.close();
                os.close();
                client.close();

            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        new TestClient().start("localhost", 8801);
    }
}

服务端代码:

public class NIOServer {

    private int port = 8002;

    // 处理器,负责管理所有的I/O事件
    private Selector selector;

    // 缓冲区 Buffer等待区
    private final ByteBuffer buffer = ByteBuffer.allocate(1024);

    public NIOServer(int port) {
        this.port = port;
        try {
            // 服务器管道
            ServerSocketChannel server = ServerSocketChannel.open();
            // 处理器启动
            selector = Selector.open();
            // 服务器管道的处理模式设置为非阻塞
            server.configureBlocking(false);
            // 服务器管道注册到处理器上,并告知说:"我关心服务器管道的'接入'活动"
            server.register(selector, SelectionKey.OP_ACCEPT);
            // 管道告知接入地址
            server.bind(new InetSocketAddress(this.port));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 开始监听
     */
    public void listen() {
        // 处理器上班,喇叭喊一声:"营业啦!!!"
        System.out.println("Server Listening on " + port);
        try {
            while (true) {
                // 四处观察,开始接待
                selector.select();
                // 拿到接待的所有客户(即所有的I/O事件)
                Set<SelectionKey> keySet = selector.selectedKeys();
                Iterator<SelectionKey> iter = keySet.iterator();
                while (iter.hasNext()) {
                    SelectionKey key = iter.next();
                    iter.remove();
                    // 开始处理客户(I/O事件)
                    process(key);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void process(SelectionKey key) throws Exception {
        if (key.isAcceptable()) { // 这个客户需要接入
            // 拿到这个客户要接入的服务器管道
            ServerSocketChannel server = (ServerSocketChannel) key.channel();
            // 服务器管道接入,底层调用native方法,获取到客人的客户端管道
            SocketChannel socketChannel = server.accept();
            // 客户端管道也设置为非阻塞
            socketChannel.configureBlocking(false);
            // (程序)将客户端管道也注册到处理器上,并说:"我关心该管道的'读'活动",相当于服务器准备好接受该客户端发送的数据
            //(这里完全是程序自我定制,非必须步骤,可以该客户端只接受数据不发送数据,模拟客户端发送数据过程)
            socketChannel.register(selector, SelectionKey.OP_READ);
        } else if (key.isReadable()) { // 客人向服务器发送"需求",即发送数据
            // 拿到客户端管道
            SocketChannel socketChannel = (SocketChannel) key.channel();
            // 将管道里面的数据写到缓存
            int len = socketChannel.read(buffer);
            if (len > 0) {
                // 操作缓存
                buffer.flip();
                String content = new String(buffer.array(), 0, len);
                // (程序)将客户端管道再次注册到处理器上,并说:"我关心这个通道的'写'活动"
                //(这里完全是程序自我定制,非必须步骤,可以该客户端只发送数据不接受数据,模拟客户端写数据过程)
                socketChannel.register(selector, SelectionKey.OP_WRITE);
                System.out.println("Read Content: " + content);
            }
        } else if (key.isWritable()) {
            // 拿到客户端管道
            SocketChannel channel = (SocketChannel) key.channel();
            // 服务器要发送的内容即response
            String content = "Hello,client";
            // 把数据写到管道里
            channel.write(ByteBuffer.wrap((":::Response::: " + content).getBytes()));
            // 整个场景模拟结束,关闭
            channel.close();
        }
    }

    public static void main(String[] args) {
        new NIOServer(8801).listen();
    }

}

二、Buffer

1. 介绍

Buffer,顾名思义就是缓冲区的意思,它是NIO中数据交换的载体,实质上是一种承载数据的容器。在上一篇BIO文章中我们提到BIO的工作模式是使用流来进行数据交换,并且根据操作的不同,分为输入流和输出流两种,而在NIO中则是以Buffer来进行数据交换的,例如后面将要提到的Channel中,都是通过Buffer来进行读写操作的,Buffer可以同时进行输入和输出操作。

2. Buffer分类

关于Buffer的类文件都封装在jdk的java.nio包中,Buffer类在jdk中是一个抽象类,它实现了Buffer的一些共性的设计,譬如属性部分的position、limit、capacity等,再譬如方法部分的clear()、flip()等,这些在后面讲解Buffer相关操作的时候还会详细说明。这里先做下简单的了解。特别说明,Buffer在多线程的场景下是不安全的,它的底层并未做同步方面的控制,因此,在有多线程并发的情景下,我们需要对其做好同步控制。

除此之外,jdk底层还为我们设计好了一些常用的buffer,这些实现类开箱即用,大大简化了我们的开发工作。主要有以下这些

  • ByteBuffer(字节类型的缓冲器)
  • IntBuffer(整型类型的缓冲器)
  • CharBuffer(字符类型的缓冲器)
  • FloatBuffer(浮点数型的缓冲器)
  • LongBuffer(长整型的缓冲器)
  • DirectByteBuffer(直接缓冲器)(后面讲解)

除了这些以外,jdk中还有许多其他的Buffer实现类,感兴趣的同学可以自己去jdk中跟一下源码,这里便不再赘述。

3. Buffer的基本原理

Buffer的基本原理在与Buffer类中定义的几个共性变量position、limit、capacity

private int position = 0;
private int limit;
private int capacity;

那么这几个属性代表什么含义呢?我们以ByteBuffer为例一起来学习下

    final byte[] hb;                  // Non-null only for heap buffers
    final int offset;
    boolean isReadOnly; 

ByteBuffer中定义了一个字节数组 hb,这个就是用于真正存储数据的容器。刚刚提到的三个属性都是基本该容器而存在的,通过和它们的协作,保证了数据的正确读写。

  • position:代表下一个要操作的数据的索引(读或者写),它的值由get/put操作自动做更新。在新创建一个buffer的时候,position的值自动初始化为0
  • limit:代表数据的终点的索引,它和position之间的数据代表了剩余在缓冲器中还需要被操作的数据
  • capacity:代表缓冲区承载数据的能力
    在这里插入图片描述
    通过上图我们可以更直观的了解到这三个属性的含义。

4. Buffer的操作

在了解了Buffer的一些特性之后,我们来了解下Buffer的一些操作

  • flip() 该操作会将limit设置到position的位置,然后将position设置为0,即读取数据前的准备操作
  • get() 该操作从缓冲器中获取数据,position往后移动,limit保持不变
  • clear() 该操作将状态变化的值全部设置为初始值

下面结合代码看一下

public class BufferUse {

    public static void main(String[] args) throws Exception {
        File file = new File("test1.txt");
        FileInputStream fin = new FileInputStream(file);
        FileChannel fc = fin.getChannel();
        ByteBuffer bb = ByteBuffer.allocate(20);
        output("初始化", bb);

        fc.read(bb);
        output("调用read()", bb);

        bb.flip();
        output("调用flip()", bb);

        while (bb.remaining() > 0) {
            byte b = bb.get();
            System.out.println((char) b);
            output("调用get()", bb);
        }

        bb.clear();
        output("调用clear()", bb);

        fin.close();
    }

    private static void output(String step, Buffer buffer) {
        System.out.println(step + " ");
        System.out.println("capacity: " + buffer.capacity());
        System.out.println("position: " + buffer.position());
        System.out.println("limit: " + buffer.limit());
        System.out.println("-------------------------------------------");
    }
}

文件中的内容为hello,输出结果如下:

初始化 
capacity: 20
position: 0
limit: 20
-------------------------------------------
调用read() 
capacity: 20
position: 6
limit: 20
-------------------------------------------
调用flip() 
capacity: 20
position: 0
limit: 6
-------------------------------------------
h
调用get() 
capacity: 20
position: 1
limit: 6
-------------------------------------------
e
调用get() 
capacity: 20
position: 2
limit: 6
-------------------------------------------
l
调用get() 
capacity: 20
position: 3
limit: 6
-------------------------------------------
l
调用get() 
capacity: 20
position: 4
limit: 6
-------------------------------------------
o
调用get() 
capacity: 20
position: 5
limit: 6
-------------------------------------------


调用get() 
capacity: 20
position: 6
limit: 6
-------------------------------------------
调用clear() 
capacity: 20
position: 0
limit: 20
-------------------------------------------

总结

本文主要对Buffer做了一个简单的介绍,至于一些细节性的实现,大家如果感兴趣的话可以自己去jdk的源码中去研究。甚至还有直接内存缓冲器(如DirectByteBuffer)、Buffer的切片等特性,感兴趣的话大家也可以去深入的了解一下。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值