Java NIO

作者: 小明今晚加班 | 来源:发表于2019-05-21 18:42 被阅读0次
    NIO和传统IO区别
    • 传统IO是面向流的(字节流或字符流),NIO是面向缓冲区的。面向流意味着每次从流中读取n个字节,直到读取所有字节,中间是不存在缓存的,因此面向流的IO操作,是不能移动流中数据的,即如果想要前后移动从流中读取数据,传统IO不支持,除非将这些数据先缓存到一个地方;NIO对数据的读取方式就不太一样了,它是先将数据读取到一个它稍后处理的缓冲区,此时我们可以在缓冲区进行一些小动作,比如前后的移动,这提高了数据处理的灵活性。但是,NIO方式还需要考虑一些其它因素,比如检查缓冲区中是否包含所有您要处理的数据;还需确保当更多的数据读取缓冲区时,不要覆盖缓冲区里尚未处理数据(NIO中相应的方法来支撑该需求--compact()方法)等。
    • 传统IO的各种流是阻塞的,这意味着,当一个线程调用read()或write()方法时,该线程被阻塞,直到有一些数据被读取,或数据完全写入,该线程在阻塞期间不能再干其它任何事。NIO是非阻塞模式,当一个线程从某通道发送读取数据请求,但它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取(直接返回不能获取结果),而不会保持线程阻塞,所以直到数据变得可读取之前,该线程可以继续做其它事情。非阻塞的写操作也是如此,一个线程请求写入数据到某通道时,不需要等待它完全写入,这个线程同时可以去做别的事情。线程通常将非阻塞IO的空闲时间用于其它通道上执行IO操作,所以一个单独的线程现在可以管理过个输入、输出通道(channel)。
    Java NIO

    NIO主要有三大核心部分:Channel、Buffer、Selector。传统IO基于字节流和字符流进行操作,而NIO基于Channel和Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择区)用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个线程可以监听多个数据通道。

    Channel

    Channel与Stream差不多是一个等级的,只不过Stream是单向的,比如InputStream、OutputStream.而Channel是双向的,既可以用来读操作,还可以进行写操作。
    NIO中Channel的主要实现有:

    • FileChannel
    • DatagramChannel
    • SocketChannel
    • ServerSocketChannel
      这四个实现基本上包括了IO数据的要求,文件、UDP和TCP、客户端服务器。所以如果采用NIO进行IO操作的话,基本上就是Buffer+各种Channel来实现。
    Buffer(缓冲区)

    Buffer类型和Java基本数据类型基本一 一对应,ByteBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer、CharBuffer。除此之外,还有一些应对特种情况(大文件)的Buffer,MappedByteBuffer、HeapByteBuffer、DirectByteBuffer。

    Selector

    Selector运行单线程可以管理多个channel,如果你的应用打开了多个通道,但每个连接的流量都很低,使用Selector就会很方便。例如在一个聊天服务器中。要使用Selector, 得向Selector注册Channel,然后调用它的select()方法。这个方法会一直阻塞到某个注册的通道有事件就绪。一旦这个方法返回,线程就可以处理这些事件,事件的例子有如新的连接进来、数据接收等。


    用FileChannel来举个Buffer+Channel进行IO操作的例子。
    首先传统IO代码:

       /**
         * 传统IO举例
         */
        @Test
        public void test2() throws Exception {
            InputStream in = new BufferedInputStream(new FileInputStream("pkg/a.txt"));
            byte[] bytes = new byte[1024];
            int len = in.read(bytes);
            while (len != -1) {
                String str = new String(bytes);
                System.out.println(str);
                len = in.read(bytes);
            }
            in.close();
        }
    

    NIO代码时这样的:

       /**
         * FileChannel举例
         */
        @Test
        public void test1() {
            RandomAccessFile rFile = null;
            try {
                rFile = new RandomAccessFile("pkg/a.txt", "rw");
                FileChannel channel = rFile.getChannel();
                ByteBuffer buf = ByteBuffer.allocate(1024);
                int len = channel.read(buf);
                System.out.println(len);
                while (len != -1) {
                    buf.flip();
                    System.out.println(StandardCharsets.UTF_8.decode(buf));
                    //下面的这种方式容易产生乱码
                    // while (buf.hasRemaining()) {
                    // System.out.print((char)buf.get());
                    // }
                    buf.compact();
                    len = channel.read(buf);
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    if (rFile != null) {
                        rFile.close();
                    }
                } catch (Exception e2) {
                    e2.printStackTrace();
                }
            }
        }
    

    上面仅仅举例了FileChannel + ByteBuffer的文件读取方式,还有DatagramChannel、SocketChannel、ServerSocketChannel,可以自己根据文档进行熟悉。

    这里没有列举Selector的使用,准备后面再另开一篇来写,Selector的内容相对较多一点。

    相关文章

      网友评论

          本文标题:Java NIO

          本文链接:https://www.haomeiwen.com/subject/guujzqtx.html