码迷,mamicode.com
首页 > 编程语言 > 详细

Java NIO

时间:2021-06-23 16:46:55      阅读:0      评论:0      收藏:0      [点我收藏+]

标签:cal   java io   strong   获取   内存映射文件   out   应用   文件流   ima   

Java NIO 

 

 

一、概述

 

   NIO (JDK1.5  new  IO) 主要有三大核心部分:

  • Channel(通道)
  • Buffer(缓冲区)
  • Selector(选择器)

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

 

         NIO 和 传统IO(一下简称IO)之间第一个最大的区别是,IO是面向流的,NIO是面向缓冲区的。 Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区。NIO的缓冲导向方法略有不同。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。但是,还需要检查是否该缓冲区中包含所有您需要处理的数据。而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据。

 

IO的各种流是阻塞的。这意味着,当一个线程调用 read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。 NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。而不是保持线程阻塞,所以直至数据变得可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(Channel)。

Channel 和 Buffer简介

基本上,所有的 IO 在NIO 中都从一个Channel 开始。Channel 有点象流。 数据可以从Channel读到Buffer中,也可以从Buffer 写到Channel中。如图所示:

技术图片

 

 

 Channel 和 IO中的 Stream(流)是差不多一个等级的。只不过Stream是单向的,譬如:InputStream, OutputStream。 而Channel是双向的,既可以用来进行读操作,又可以用来进行写操作。
NIO中的Channel的主要实现有:

ChannelBuffer 有好几种类型。下面是JAVA NIO中的一些主要 Channel 的实现:

  • FileChannel  (文件IO)
  • DatagramChannel  (UDP)
  • SocketChannel   (Client  TCP)
  • ServerSocketChannel  (Server  TCP)

以下是Java NIO里关键的 Buffer 实现(Buffer的类型):

  • ByteBuffer
  • CharBuffer
  • DoubleBuffer
  • FloatBuffer
  • IntBuffer
  • LongBuffer
  • ShortBuffer

这些Buffer覆盖了你能通过IO发送的基本数据类型:byte、short、 int、 long、 float、 double 和 char。

Java NIO 还有个 MappedByteBuffer,用于表示内存映射文件。后面会讲到。

Selector 简介

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

技术图片

 

 

 

二、FileChannel (文件IO)

传统IO 与 FileChannel (NIO) 读取文件并输出代码比较:

传统:IO

 public static void Io(){
        InputStream in = null;
        try{
            in = new BufferedInputStream(new FileInputStream("src/nomal_io.txt"));
            byte [] buf = new byte[1024];
// 读取数据到缓冲区
int bytesRead = in.read(buf); while(bytesRead != -1) { for(int i=0;i<bytesRead;i++) System.out.print((char)buf[i]); bytesRead = in.read(buf); } }catch (IOException e) { e.printStackTrace(); }finally{ try{ if(in != null){ in.close(); } }catch (IOException e){ e.printStackTrace(); } } }

NIO : 

 public static void Nio(){
        RandomAccessFile aFile = null;
        try{
            aFile = new RandomAccessFile("src/nio.txt","rw");
            FileChannel fileChannel = aFile.getChannel();
           //  创建 1024 字节的缓冲区
            ByteBuffer buf = ByteBuffer.allocate(1024);
           // 从通道中读取数据到 缓冲区中
            int bytesRead = fileChannel.read(buf);
            System.out.println(bytesRead);
            while(bytesRead != -1)
            {
             // 切换缓冲区的模式为读模式
                buf.flip();
             // 当缓冲区 buf 中有数据则一直循环
                while(buf.hasRemaining())
                {
                    System.out.print((char)buf.get());
                }
              // 清空缓存区
                buf.compact();
              // 继续读取文件流到缓冲区中, 返回读取的字节数
                bytesRead = fileChannel.read(buf);
            }
        }catch (IOException e){
            e.printStackTrace();
        }finally{
            try{
                if(aFile != null){
                    aFile.close();
                }
            }catch (IOException e){
                e.printStackTrace();
            }
        }
}

可以看出,使用 NIO 的实现方式相对复杂一点。

 

三、Buffer的基本用法

使用Buffer读写数据一般遵循以下几个步骤:

1、分配空间(分配1024 字节capacity ByteBuffer buf = ByteBuffer.allocate(1024); )。

2、写入数据到 Buffer: 

 

    • 从Channel 写到 Buffer ( fileChannel.read(buf) )

 

    • 通过 Buffer的 put()方法 (buf.put(…)

3、调用 flip()方法,将Buffer从写模式切换到读模式。

4、从 Buffer 中读取数据:

    • 从Buffer读取到Channel ( fileChannel.write(buf) ;)

 

    • 使用get()方法从Buffer中读取数据 (buf.get();

 

5、调用 clear()方法或者 compact()方法。 一旦读完了所有的数据,就需要清空缓冲区,让它可以再次被写入。有两种方式能清空缓冲区:调用clear()或compact()方法。clear()方法会清空整个缓冲区。compact()方法只会清除已经读过的数据。任何未读的数据都被移到缓冲区的起始处,新写入的数据将放到缓冲区未读数据的后面。

理解 Buffer的 capacity、position和limit 属性

缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存。这块内存被包装成NIO Buffer对象,并提供了一组方法,用来方便的访问该块内存。

capacity:

  • 作为一个内存块,Buffer有一个固定的大小值,也叫“capacity”.你只能往里写capacity个byte、long,char等类型。一旦Buffer满了,需要将其清空(通过读数据或者清除数据)才能继续写数据往里写数据。

position:

  • 当你写数据到Buffer中时,position表示当前的位置。初始的position值为0.当一个byte、long等数据写到Buffer后, position会向前移动到下一个可插入数据的Buffer单元。position最大可为capacity – 1.
  • 当读取数据时,也是从某个特定位置读。当将Buffer从写模式切换到读模式,position会被重置为0. 当从Buffer的position处读取数据时,position向前移动到下一个可读的位置。

limit:

  • 在写模式下,Buffer的limit表示你最多能往Buffer里写多少数据。 写模式下,limit等于Buffer的capacity。
  • 当切换Buffer到读模式时, limit表示你最多能读到多少数据。因此,当切换Buffer到读模式时,limit会被设置成写模式下的position值。换句话说,你能读到之前写入的所有数据(limit被设置成已写数据的数量,这个值在写模式下就是position)

Buffer 的其他方法:

rewind()方法:

       Buffer.rewind() 将position设回0,所以你可以重读Buffer中的所有数据。limit保持不变,仍然表示能从Buffer中读取多少个元素(byte、char等)。

clear()与compact()方法:

      一旦读完Buffer中的数据,需要让Buffer准备好再次被写入。可以通过clear()或compact()方法来完成。

如果调用的是clear()方法,position将被设回0,limit被设置成 capacity的值。换句话说,Buffer 被清空了。Buffer中的数据并未清除,只是这些标记告诉我们可以从哪里开始往Buffer里写数据。

如果Buffer中有一些未读的数据,调用clear()方法,数据将“被遗忘”,意味着不再有任何标记会告诉你哪些数据被读过,哪些还没有。

如果Buffer中仍有未读的数据,且后续还需要这些数据,但是此时想要先先写些数据,那么使用compact()方法。

compact()方法将所有未读的数据拷贝到Buffer起始处。然后将position设到最后一个未读元素正后面。limit属性依然像clear()方法一样,设置成capacity。现在Buffer准备好写数据了,但是不会覆盖未读的数据。

mark()与reset()方法:

通过调用Buffer.mark()方法,可以标记Buffer中的一个特定position。之后可以通过调用Buffer.reset()方法恢复到这个position。例如:

buffer.mark();
//call buffer.get() a couple of times, e.g. during parsing.
buffer.reset();  //set position back to mark.

 

Java NIO

标签:cal   java io   strong   获取   内存映射文件   out   应用   文件流   ima   

原文地址:https://www.cnblogs.com/dw3306/p/14920930.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!