资讯专栏INFORMATION COLUMN

Java NIO 基础(一)

hizengzeng / 1318人阅读

摘要:通过协议向网络读写数据通过协议向网络读写数据以一个服务器的形式,监听到来的连接,对每个连接建立一个。

Java NIO 教程 NIO是什么?

它是Java1.4之后出现的IO API,与传统IO和网络API不同,具有非阻塞的特点。

在BIO中我们使用字节流和字符流。NIO中我们使用channel和buffer。数据总是从一个channel中读取到buffer中,或者从buffer中写入到channel中。

NIO的意思是一个线程可以让一个channel将数据读取到buffer中,与此同时,这个线程还可以做其他的事情,线程可以等到数据全部进入buffer之后再处理数据,从buffer中写入线程也是一样的。

selector:选择器是一个NIO当中的概念,指的是一个对象,能监视多个channel发生的事件(如连接建立,数据到达等)。因此,一个单线程可以监视多个channel的数据。

Java NIO 总览

Java NIO的三个核心基础组件,

Channels

Buffers

Selectors

其余的诸如Pipe,FileLcok都是在使用以上三个核心组件时帮助更好使用的工具类。

Channels和Buffers的关系

所有的IO操作在NIO中都是以Channel开始的。一个Channel就像一个流。从Channel中,数据可以被读取到buffer里,也可以从buffer里写到Channel中。

基本的Channel实现有以下这些:

FileChannel

DatagramChannel

SocketChannel

ServerSocketChannel

涵盖了UDP,TCP以及文件的IO操作。

核心的buffer实现有这些

ByteBuffer

CharBuffer

DoubleBuffer

FloatBuffer

IntBuffer

LongBuffer

ShortBuffer

涵盖了所有的基本数据类型(4类8种,除了Boolean)。也有其他的buffer如MappedByteBuffer,此处不讲。

selectors

selector允许一个线程来监视多个Channel,这在当你的应用建立了多个连接,但是每个连接吞吐量都较小的时候是可行的。例如:一个聊天服务器。图为一个线程使用selector处理三个channel。

要使用一个Selector,你要先注册这个selector的Channels。然后你调用selector的select()方法。这个方法会阻塞,直到它注册的channels当中有一个准备好了的事件发生了。当select()方法返回的时候,线程可以处理这些事件,如新的连接的到来,数据收到了等。

NIO Channels

NIO channel和流很近似但是也有一些不同。

你既可以读取也可以写入到channel,流只能读取或者写入,inputStream和outputStream。

channel可以异步地读和写。

channel永远都是从一个buffer中读或者写入到一个buffer中去。

channel的实现

以下是NIO中最重要的几个channel的实现。

FileChannel 向文件当中读写数据。

DatagramChannel 通过UDP协议向网络读写数据

SocketChannel 通过TCP协议向网络读写数据

ServerSocketChannel 以一个web服务器的形式,监听到来的TCP连接,对每个连接建立一个SocketChannel。

一个简单的channel例子

使用一个FileChannel将数据读入一个buffer

     RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
    FileChannel inChannel = aFile.getChannel();

    ByteBuffer buf = ByteBuffer.allocate(48);

    int bytesRead = inChannel.read(buf);
    while (bytesRead != -1) {

      System.out.println("Read " + bytesRead);
      buf.flip();

      while(buf.hasRemaining()){
          System.out.print((char) buf.get());
      }

      buf.clear();
      bytesRead = inChannel.read(buf);
    }
    aFile.close();

buf.flip()的意思是读写转换,首先你读入一个buffer,然后你flip,转换读写,然后再从buffer中读出,buffer的操作接下来会讲。

NIO buffer

NIO buffer在与NIO Channel交互时使用,数据从channel中读取出来放入buffer,或者从buffer中读取出来写入channel。

buffer就是一块内存,你可以写入数据,并且在之后读取它。这块内存被包装成NIO buffer对象,它提供了一些方法来让你更简单地操作内存。

buffer的基本使用

使用buffer读写数据基本上分为以下4部操作:

将数据写入buffer

调用buffer.flip()

将数据从buffer中读取出来

调用buffer.clear()或者buffer.compact()

在写buffer的时候,buffer会跟踪写入了多少数据,需要读buffer的时候,需要调用flip()来将buffer从写模式切换成读模式,读模式中只能读取写入的数据,而非整个buffer。

当数据都读完了,你需要清空buffer以供下次使用,可以有2种方法来操作:

调用clear()

调用compact()

区别:clear方法清空整个buffer,compact方法只清除你已经读取的数据,未读取的数据会被移到buffer的开头,此时写入数据会从当前数据的末尾开始。

一个简单的buffer使用例子:

RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();

//创建一个容量为48的ByteBuffer
ByteBuffer buf = ByteBuffer.allocate(48);

int bytesRead = inChannel.read(buf); //从channel中读(取数据然后写)入buffer
//下面是读取buffer
while (bytesRead != -1) {

      buf.flip();                      //转换buffer为读模式

     while(buf.hasRemaining()){
          System.out.print((char) buf.get()); // 一次读取一个byte
      }

  buf.clear();                         //清空buffer准备下一次写入
  bytesRead = inChannel.read(buf);    
}
aFile.close();
buffer的Capacity,Position和Limit

buffer有3个属性需要熟悉以理解buffer的工作原理:

容量(Capacity):缓冲区能够容纳的数据元素的最大数量。容量在缓冲区创建时被设定,并且永远不能被改变。

上界(Limit):写模式中等价于buffer的大小,即capacity;读模式中为当前缓冲区中一共有多少数据,即可读的最大位置。这意味着当调用filp()方法切换成读模式时,limit的值变成position的值,而position重新指向0.

位置(Position):下一个要被读或写的元素的位置。初始化为0,buffer满时,position最大值为capacity-1。切换成读模式的时候,position指向0。Position会自动由相应的 get( )和 put( )函数更新。

position和limit的值在读/写模式中是不一样的。
capacity的值永远表示buffer的大小。

下图解释了在读/写模式中Capacity,Position和Limit的意思。

buffer的种类

Java NIO中有以下这些buffer种类:

ByteBuffer

MappedByteBuffer //比较特殊,会在以后讲解

CharBuffer

DoubleBuffer

FloatBuffer

IntBuffer

LongBuffer

ShortBuffer

创建一个buffer

获得一个buffer 之前必须先分配一块内存,每个buffer类都有一个静态方法allocate() 来做这件事。

下例为创建一个容量为48byte的ByteBuffer:
ByteBuffer buf = ByteBuffer.allocate(48);

创建一个1024个字符的CharBuffer
CharBuffer buf = CharBuffer.allocate(1024);

将数据写入buffer

写入buffer的方法有2种:

1.从一个channel中写入buffer。

2.调用buffer的put()方法来自行写入数据。

例:

int bytesRead = inChannel.read(buf); //从channel读入buffer

buf.put(127); //自行写入buffer

put方法有很多的重载形式。以供你用各种不同的方法写入buffer中,比如从一个特定的position,或者写入一个array,详见JavaDoc。

flip()

flip方法将写模式切换成读模式,调用flip()方法会将limit设置为position,将position设置回0。

换句话说,position标志着写模式中写到哪里,切换成读模式之后,limit标志着之前写到哪里,也就是现在能读到哪里。

从buffer中读取数据

有2种方法可以从buffer中读取数据。

1.从buffer中读取数据到channel中。

2.使用buffer的get()方法自行从buffer中读出数据。

例子:

//从buffer中读取数据到channel中
int bytesWritten = inChannel.write(buf);

//使用buffer的get()方法自行从buffer中读出数据
byte aByte = buf.get();    

get方法有很多的重载形式。以供你用各种不同的方法读取buffer中的数据。例如从特定位置读取数据,或者读一个数组出来。详见JavaDoc。

rewind()

rewind()方法将position设置为0,但是不会动buffer里的数据,这样可以从头开始重新读取数据,limit的值不会变,这意味着limit依旧标志着能读多少数据。

clear()和compact()

当你读完所有的数据想要重新写入数据时,你可以调用clear或者compact方法。

当你调用clear()方法的时候,position被设置为0,limit被设置为capacity,换句话说,buffer的数据虽然都还在,但是buffer被初始化了,处于可以被重写的状态。

这也就意味着如果buffer中还有没被读取的数据,在执行clear之后,你无法知道数据读到哪儿了,剩下的数据还有多少。

如果还有没有读完的数据,但是你想先写数据,可以用compact()方法,这样未读数据会放在buffer前端,可以在未读数据之后跟着写新的数据。compact()会复制未读数据到buffer前端,然后设置position为未读数据单位后面紧跟的位置。limit还是设置为capacity,这和clear是一样的。现在buffer处于可以写的状态,但是不会覆盖之前未读完的数据。

mark()和reset()

你可以通过调用buffer.mark()来mark一个buffer中给定的位置。然后你就可以用buffer.reset()方法来讲position设置回之前mark的位置。

例子:

buffer.mark();

//调用buffer.get()方法若干次,e.g. 比如在做parsing的时候

buffer.reset();  //set position back to mark.   
equals() 和 compareTo()

使用这2种方法能够比较2个buffer。

equals()

equals()方法用于判断2个buffer是否相等,2个buffer是equal的,当它们:

是同一种数据类型的buffer。

buffer中未读取的bytes,chars等数据个数是一样的,即(limit-position)相等,capacity不需要相等,剩余数据的索引也不需要相等。

未读取的bytes,chars等内容是一模一样的,即各自[position,limit-1]索引的数据要完全相等。

如你所见,equals()方法只比较buffer的部分内容,而不是buffer中所有的数据,事实上,它只比较buffer中剩余的元素是否一样。

compareTo()

compareTo()方法比较两个buffer的剩余元素(字节,字符等),用于例如: 排序。

在下列情况下,缓冲区被认为比另一个缓冲区“小”:

比较是针对每个缓冲区你剩余数据(从 position 到 limit)进行的,与它们在 equals() 中的方式相同,直到不相等的元素被发现或者到达缓冲区的上界。如果一个缓冲区在不相等元素发现前已经被耗尽,较短的缓冲区被认为是小于较长的缓冲区。

if (buffer1.compareTo(buffer2) < 0) { 
// do sth, it means buffer2 < buffer1,not buffer1 < buffer2
    doSth(); 
} 




文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/76360.html

相关文章

  • JDK10都发布了,nio你了解多少?

    摘要:而我们现在都已经发布了,的都不知道,这有点说不过去了。而对一个的读写也会有响应的描述符,称为文件描述符,描述符就是一个数字,指向内核中的一个结构体文件路径,数据区等一些属性。 前言 只有光头才能变强 回顾前面: 给女朋友讲解什么是代理模式 包装模式就是这么简单啦 本来我预想是先来回顾一下传统的IO模式的,将传统的IO模式的相关类理清楚(因为IO的类很多)。 但是,发现在整理的过程已...

    YFan 评论0 收藏0
  • NIO网络相关基础知识

    摘要:操作系统是能够获取到事件操作完成的事件,基于回调函数机制和操作系统的操作控制实现事件检测机制。 前面的文章NIO基础知识介绍了Java NIO的一些基本的类及功能说明,Java NIO是用来替换java 传统IO的,NIO的一些新的特性在网络交互方面会更加的明显。 Java 传统IO的弊端     基于JVM来实现每个通道的轮询检查通道状态的方法是可行的,但仍然是有问题的,检查每个通道...

    1fe1se 评论0 收藏0
  • Netty序章之BIO NIO AIO演变

    摘要:后改良为用线程池的方式代替新增线程,被称为伪异步。最大的问题是阻塞,同步。每次请求都由程序执行并返回,这是同步的缺陷。这些都会被注册在多路复用器上。多路复用器提供选择已经就绪状态任务的能力。并没有采用的多路复用器,而是使用异步通道的概念。 Netty是一个提供异步事件驱动的网络应用框架,用以快速开发高性能、高可靠的网络服务器和客户端程序。Netty简化了网络程序的开发,是很多框架和公司...

    VincentFF 评论0 收藏0
  • Netty序章之BIO NIO AIO演变

    摘要:后改良为用线程池的方式代替新增线程,被称为伪异步。最大的问题是阻塞,同步。每次请求都由程序执行并返回,这是同步的缺陷。这些都会被注册在多路复用器上。多路复用器提供选择已经就绪状态任务的能力。并没有采用的多路复用器,而是使用异步通道的概念。 Netty是一个提供异步事件驱动的网络应用框架,用以快速开发高性能、高可靠的网络服务器和客户端程序。Netty简化了网络程序的开发,是很多框架和公司...

    CntChen 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<