Java NIO 学习教程(一)

这个系列的NIO主要是翻译过来的,原文地址:http://tutorials.jenkov.com/java-nio/index.html

Java NIO 教程

Java NIO:Channels and Buffers(管道和缓冲区)

在标准的IO API中,使用的是字节流和字符流。在NIO中,使用的是管道和缓冲区,数据从管道读取到缓冲区,或者从缓冲区写入到管道。

Java NIO:Non-blocking IO(非阻塞IO)

Java NIO允许能够执行非阻塞IO。例如,线程可以请求管道将数据读入缓冲区。当管道将数据读入缓冲区时,线程可以执行其他操作。一旦数据被读入缓冲区,线程就可以继续处理它。将数据写入管道也是如此。

Java NIO:Selectors(选择器)

Java NIO包含“选择器”的概念。选择器是一个对象,它可以监视事件的多个通道(例如:连接打开,数据到达等)。因此,一个线程可以监视多个数据通道。

Java NIO概述

Java NIO由以下的核心组件组成
  • 管道(Channels)
  • 缓冲(Buffers)
  • 选择器(Selectors)

Java NIO有更多的类和组件,但在我看来,管道、缓冲区和选择器构成了API的核心。其余的组件,如Pipe和FileLock只是与三个核心组件一起使用的实用程序类。

管道和缓冲(Channels and Buffers)

通常来说,NIO中的所有IO都以管道开始。管道有点类似于流。从管道中可以将数据读入缓冲区。数据也可以从缓冲区写入管道。这里有一个例子:
Java NIO:通道将数据读入缓冲区,缓冲区将数据写入通道

有几种通道和缓冲区类型。下面是Java NIO的主要通道实现列表:

  • FileChannel
  • DatagramChannel
  • SocketChannel
  • ServerSocketChannel

这些通道包括UDP + TCP、网络IO、和文件IO。

Java NIO的核心缓冲实现列表:

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

这些缓冲区涵盖了通过IO发送的基本数据类型:字节、短整型、int、长整型、浮点、双精度和字符。

Java NIO还有一个MappedByteBuffer,它与内存映射文件一起使用。

选择器(Selectors)

选择器允许单个线程处理多个管道。如果应用程序打开了许多连接(管道),但每个连接上的通信量都很低,那么这将非常方便。例如,在聊天服务器中。

下面是一个用选择器处理3个通道的线程的例子:
 Java NIO:线程使用选择器来处理3个通道

要使用选择器,需要给它注册管道。然后调用它的select()方法。此方法将阻塞,直到为已注册的管道之一准备好事件为止。一旦方法返回,线程就可以处理这些事件。事件的例子有传入连接、接收数据等。

Java NIO Channel(管道)

Java NIO管道类似于流,但还是有一些区别:
  • 可以对一个管道读和写,而流通常是单向的(读或写)。
  • 管道可以异步读写。
  • 管道总是从缓冲区读取或写入数据。

如上所述,你从管道读物数据进入缓冲区,并将数据从缓冲区写入通道。这里有一个例子:
Java NIO:通道将数据读入缓冲区,缓冲区将数据写入通道

管道的实现

下面是Java NIO中最重要的管道实现类:

  • FileChannel:从文件和文件中读取数据。
  • DatagramChannel:可以通过UDP在网络上读写数据。
  • SocketChannel:可以通过TCP在网络上读写数据。
  • ServerSocketChannel:允许像web服务器一样监听传入的TCP连接。为每个传入的连接创建一个SocketChannel。
管道基本的使用例子

下面是一个使用FileChannel将一些数据读入缓冲区的基本示例:

 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()的调用。首先读入缓冲区。翻转,然后读出。

Java NIO Buffer

Java NIO缓冲区经常用在与NIO管道交互中。数据从管道读取到缓冲区,然后从缓冲区写入到管道。

缓冲区本质上是一块内存,可以将写入数据,然后再读取数据。这个内存块被包装在NIO缓冲区对象中,该对象提供了一组方法,使使用内存块变得更容易。

缓冲区的基本使用

使用缓冲区读取和写入数据通常遵循以下4个步骤:

  • 将数据写入缓冲区
  • 调用buffer.flip()
  • 从缓冲区读取数据
  • 调用buffer.clear()或buffer.compact()

当数据写入缓冲区时,缓冲区会跟踪写入了多少数据。一旦需要读取数据,就需要调用flip()方法调用将缓冲区从写入模式切换到读取模式。在读取模式下,缓冲区允许读取写入缓冲区的所有数据。

一旦读取了所有数据,就需要把缓冲区清空,为再次写入做准备。可以通过两种方式实现清除缓冲区:调用clear()或调用compact()。clear()方法清除整个缓冲区。compact()方法只清除已经读过的数据。任何未读数据会移动到缓冲区的开头,数据将在未读数据之后写入缓冲区。

下面是一个简单的缓冲区使用示例,包括写入、翻转、读取和清除操作:

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

//create buffer with capacity of 48 bytes
ByteBuffer buf = ByteBuffer.allocate(48);

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

  buf.flip();  //make buffer ready for read

  while(buf.hasRemaining()){
      System.out.print((char) buf.get()); // read 1 byte at a time
  }

  buf.clear(); //make buffer ready for writing
  bytesRead = inChannel.read(buf);
}
aFile.close();
缓冲容量,位置和极限

缓冲区本质上是一块内存,可以将数据写入其中,然后再读取数据。这个内存块被包装在NIO缓冲区对象中,该对象提供了一组方法,使使用内存块变得更容易。

为了理解缓冲区的工作方式,缓冲区的三个属性需要熟悉。这些都是:

  • capacity(容量)
  • position(位置)
  • limit(极限)

位置和极限的含义取决于缓冲区是读模式还是写模式。无论缓冲模式如何,容量总是相同的。

这是一个在写和读模式的容量,位置和限制的插图。
读写模式中的缓冲区容量、位置和限制

  • Capacity

缓冲区作为内存块,具有一定的固定大小,也称为“容量”。你只能在缓冲区写入:容量字节、长字节、字符等。一旦缓冲区满了,需要清空它(读取数据或者清除数据),然后才能向缓冲区写入更多数据。

  • Position

当你往缓冲区写入数据时,会在一个确定的position写入。初始position是0。当一个字节、长等被写入缓冲区时,position将被提前指向缓冲区中的下一个单元格,以便插入数据。position可以最大变成容量- 1。

从缓冲区读取数据时,也要从给定的position读取数据。当缓冲区从写入模式翻转到读取模式时,position将重置为0。从缓冲区读取数据时,其实是从position读取数据,position将被提前到下一个要读取的position。

  • Limit

在写模式中,缓冲区的极限是从缓冲区中可以写入的数据的最大限制。在写模式中,极限等于缓冲区的容量。

将缓冲区翻转到读模式时,limit是指可以从数据读取的数据量的限制。因此,当将缓冲区翻转到读模式时,极限设置为写模式的写入position。换句话说,你可以读取任意多的字节(极限设置为写入的字节数,以position标记)。

缓冲类型

Java NIO附带了以下缓冲区类型:

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

这些缓冲区类型表示不同的数据类型。也就是说允许将缓冲区中的字节处理为char、short、int、long、float或double。

分配一个缓冲区

要获得缓冲区对象,必须首先分配它。每个缓冲区类都有一个allocate ()方法来完成此任务。下面的例子显示了ByteBuffer的分配,容量为48字节:

ByteBuffer buf = ByteBuffer. allocation (48);

下面是一个为1024个字符分配空间的CharBuffer的例子:

CharBuffer buf = CharBuffer. allocation (1024);
将数据写入缓冲区

有两种方式将数据写入缓冲区:

  • 将数据从管道写入缓冲区
  • 通过缓冲区的put()方法,自己将数据写入缓冲区。

下面是一个显示通道如何将数据写入缓冲区的例子:

int bytesRead = inChannel.read(buf); //read into buffer.

下面是一个通过put()方法将数据写入缓冲区的例子:

buf.put(127);

put()方法还有许多其他版本,允许以许多不同的方式将数据写入缓冲区。例如,在特定位置写入,或将字节数组写入缓冲区。有关具体缓冲区实现的详细信息,请参阅JavaDoc。

flip()

flip()方法的作用是:将缓冲区从写入模式切换到读取模式。调用flip()将position 设置回0,并将极限为刚才的位置。

换句话说,position现在标记了读取位置,limit标记了写入缓冲区的字节数、字符数等——可以读取的字节数、字符数等的限制。

从缓冲区读取数据

从缓冲区读取数据有两种方式。

  • 从缓冲区读取数据到管道。
  • 使用get()方法之一从缓冲区读取数据。

下面是如何将数据从缓冲区读取到通道的示例:

//read from buffer into channel.
int bytesWritten = inChannel.write(buf);

下面是一个使用get()方法从缓冲区读取数据的例子:

byte aByte = buf.get();

get()方法还有许多其他版本,允许以许多不同的方式从缓冲区读取数据。例如,在特定位置读取,或者从缓冲区读取字节数组。有关具体缓冲区实现的详细信息,请参阅JavaDoc。

rewind()

rewind()将位置设置为0,这样就可以重新读取缓冲区中的所有数据。极限保持不变,因此仍然标记有多少元素(字节、字符等)可以从缓冲区读取。

clear() and compact()
  • 从缓冲区读取数据之后,必须让缓冲区为再次写入做好准备。可以通过调用clear()或调用compact()来做到这一点。
  • 如果调用clear(),该位置将被设置为0,并限制容量。换句话说,缓冲区被清除。缓冲区中的数据没有清除。只有标记告诉你可以将数据写入缓冲区的位置。
  • 当你调用clear()时,如果缓冲区中有任何未读数据,则数据将被“遗忘”,意味着不再有任何标记说明哪些数据已被读取,哪些数据未被读取。
  • 如果缓冲区中仍然有未读数据,并且你希望稍后读取它,但是你需要先写一些东西,那么调用compact()而不是clear()。
  • compact()将所有未读的数据复制到缓冲区的开头。然后将position设置为最后一个未读元素之后的位置。与clear()一样,limit属性仍然设置为容量。现在缓冲区可以写入了,但是不会覆盖未读数据。

mark() and reset()

可以通过调用Buffer.mark()方法在缓冲区中标记给定的位置。然后,您可以通过调用Buffer.reset()方法将位置重置回标记位置。下面是一个例子:

buffer.mark();

//call buffer.get() a couple of times, e.g. during parsing.

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

可以使用equals()和compareTo()比较两个缓冲区。

equals()

如果两个缓冲区相等:

  • 它们的类型相同(字节、字符、int等)。
  • 它们在缓冲区中有相同数量的剩余字节、字符等。
  • 所有剩余的字节、字符等都是相等的。

equals()只比较缓冲区的一部分,而不是缓冲区中的每个元素。实际上,它只是比较缓冲区中的其余元素。

compareTo()

compareTo()方法比较了两个缓冲区的其余元素(字节、字符等),以用于排序例程。如果:

  • 第一个元素等于另一个缓冲区中的相应元素,比另一个缓冲区中的元素小。
  • 所有元素都是相等的,但是第一个缓冲区在第二个缓冲区之前耗尽了元素(它的元素更少)。

猜你喜欢

转载自blog.csdn.net/change_on/article/details/83016365