面向流与面向缓冲

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

阻塞与非阻塞IO

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

选择器(Selectors)

    Java NIO的选择器允许一个单独的线程来监视多个输入通道,你可以注册多个通道使用一个选择器,然后使用一个单独的线程来“选择”通道:这些通道里已经有可以处理的输入,或者选择已准备写入的通道。这种选择机制,使得一个单独的线程很容易来管理多个通道。

NIO和IO如何影响应用程序的设计

无论您选择IO或NIO工具箱,可能会影响您应用程序设计的以下几个方面:

对NIO或IO类的API调用。
数据处理。
用来处理数据的线程数。

ByteBuffer buffer = ByteBuffer.allocate(48);

int bytesRead = inChannel.read(buffer);

while(! bufferFull(bytesRead) ) {

	bytesRead = inChannel.read(buffer);

}

JAVA NIO的核心部分

Channels通道
Buffers缓冲区
Selectors选择器
缓冲区一个用于特定基本数据类型的容器

Java Nio 中的Buffer主要用于nio通道的交互,数据从通道读入缓冲区,从缓冲区写入通道的.

Buffer就像一个数组,可以保存多个相同类型的数据.
常用的Buffer子类
ByteBuffer
CharBuffer
ShortBuffer
IntBuffer
LongBuffer
FloatBuffer
DoubleBuffer

创建buffer对象
static XXXBuffer allocate(1024); 创建一个容量为1024的buffer对象

缓冲区的基本属性
容量(capacity):表示Buffer的最大数据容量,缓冲区的容量不能为负,并且创建后不能更改

ByteBuffer allocate = ByteBuffer.allocate(1024);

限制(limit):第一个不应该读取或写入的数据的索引.

位置(position):下一个要读入或写入数据的索引,缓冲区的位置不能为负,并且不能大于其限制

标记(mark)与重置(reset):标记是一个索引,通过Buffer中的mark方法制定Buffer中的一个特定的position,之后可以通过reset方法恢复到这个位置.

JAVA NIO-LMLPHP

JAVA NIO-LMLPHP

JAVA NIO-LMLPHP


非直接缓冲区:通过allocate()方法分配缓冲区,将缓冲区建立在JVM的内存中。

直接缓冲区:通过allocateDirect()方法分配直接缓冲区,将缓冲区建立在物理内存中。可以提高效率。是由本地方法直接分配的.

非直接缓冲区的创建
ByteBuffer.allocate(1024);
直接缓冲区的创建
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024);
还可以通过FileChannel的map方法将文件区直接映射到内存中来创建,该方法返回MappedByteBuffer.

直接缓冲区和非直接缓冲区的判断,可以使用
byteBuffer.isDirect();来进行判断.
JAVA NIO-LMLPHP

JAVA NIO-LMLPHP
通道表示打开到IO设备(例如文件,套接字的连接)

Channel的实现

FileChannel
DatagramChannel
SocketChannel
ServerSocketChannel

FileChannel 从文件中读写数据。

DatagramChannel 能通过UDP读写网络中的数据。

SocketChannel 能通过TCP读写网络中的数据。

ServerSocketChannel可以监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel。

创建通道的3中方式

  • 获取通道的第一种方式是对支持通道的对象调用getChannel()方法,支持调用的类有
    FileInputStream
    FileOutputStream
    RandomAccessStream
    DataGramStream
    Socket
    ServerSocket

       RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
      	FileChannel inChannel = aFile.getChannel();
    
  • 第二种方式
    使用Files类的静态方法newByteChannel()获取字节通道

      ByteChannel inputStream = Files.newByteChannel(Paths.get(""),
      StandardOpenOption.CREATE_NEW);
    

Path是用来取代File的
Path用于来表示文件路径和文件。可以有多种方法来构造一个Path对象来表示一个文件路径,或者一个文件

支持以下StandardOpenOptions枚举:
WRITE - 打开文件以进行写访问。
APPEND - 将新数据附加到文件的末尾。该选项用于WRITE或CREATE选项。
TRUNCATE_EXISTING - 将文件截断为零字节。该选项与WRITE选项一起使用。
CREATE_NEW - 创建一个新文件,如果文件已经存在,则会引发异常。
CREATE - 如果文件存在,打开文件,如果没有,则创建一个新文件。
DELETE_ON_CLOSE - 流关闭时删除文件。此选项对临时文件很有用。
SPARSE - 提示新创建的文件将是稀疏的。这种高级选项在某些文件系统(例如NTFS)中得到尊重,其中具有数据“间隙”的大型文件可以以更有效的方式存储,这些空隙不占用磁盘空间。
SYNC - 保持与底层存储设备同步的文件(内容和元数据)。
DSYNC - 保持与底层存储设备同步的文件内容。

  • 第三种方式
    使用通道的静态方法open()打开并返回制定通道
    SocketChannel.open();

      /**
       * 演示打开通道的三种方式
       * 2017年6月22日 下午9:38:00
       */
      public void openSocket(){
          try {
              // 1、打开一个套接字通道
              SocketChannel sc = SocketChannel.open();
              // 根据主机名和端口号创建套接字地址
              InetSocketAddress socketAddress = new InetSocketAddress("192.168.1.102",8080);
              // 连接套接字
              sc.connect(socketAddress);
    
              // 2、打开一个server-socket通道
              ServerSocketChannel ssc = ServerSocketChannel.open();
              ssc.socket().bind(new InetSocketAddress(8080));
    
              // 3、打开一个datagram通道
              DatagramChannel dc = DatagramChannel.open();
              RandomAccessFile raf = new RandomAccessFile("/usr/local/swk/dump.txt", "r");
              FileChannel fc = raf.getChannel();
    
      	 //4丶打开一个Byte通道
      	 ByteChannel inputStream = Files.newByteChannel(Paths.get("/usr/local/swk/dump.txt",), StandardOpenOption.CREATE_NEW);
    
          } catch (IOException e) {
              e.printStackTrace();
          }
    
      }
    

示例

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();

以前IO的方式
cpu直接对io流进行调度

JAVA NIO-LMLPHP

改进后通过DMA
JAVA NIO-LMLPHP

再次改进后使用通道
JAVA NIO-LMLPHP

JAVA NIO-LMLPHP

JAVA NIO-LMLPHP

JAVA NIO-LMLPHP

JAVA NIO-LMLPHP

JAVA NIO-LMLPHP
参考文献:
https://blog.csdn.net/hhx0626/article/details/78183928?utm_source=copy
https://blog.csdn.net/fuyuwei2015/article/details/73698417?utm_source=copy

10-06 17:58