概述

Java NIO提供了与标准IO不同的IO工作方式:

  • Channels and Buffers(通道和缓冲区):标准的IO基于字节流和字符流进行操作的,而NIO是基于通道(Channel)和缓冲区(Buffer)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。
  • Asynchronous IO(异步IO):Java NIO可以让你异步的使用IO,例如:当线程从通道读取数据到缓冲区时,线程还是可以进行其他事情。当数据被写入到缓冲区时,线程可以继续处理它。从缓冲区写入通道也类似。
  • Selectors(选择器):Java NIO引入了选择器的概念,选择器用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个的线程可以监听多个数据通道。
 

使用场景

NIO

  • 优势在于一个线程管理多个通道;但是数据的处理将会变得复杂;
  • 如果需要管理同时打开的成千上万个连接,这些连接每次只是发送少量的数据,采用这种;

传统的IO

  • 适用于一个线程管理一个通道的情况;因为其中的流数据的读取是阻塞的;
  • 如果需要管理同时打开不太多的连接,这些连接会发送大量的数据;
 

NIO vs IO区别

NIO vs IO之间的理念上面的区别(NIO将阻塞交给了后台线程执行)
  • IO是面向流的,NIO是面向缓冲区的

    • Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方;
    • NIO则能前后移动流中的数据,因为是面向缓冲区的
  • IO流是阻塞的,NIO流是不阻塞的
    • Java IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了
    • Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。NIO可让您只使用一个(或几个)单线程管理多个通道(网络连接或文件),但付出的代价是解析数据可能会比从一个阻塞流中读取数据更复杂。
    • 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。
  • 选择器
    • Java NIO的选择器允许一个单独的线程来监视多个输入通道,你可以注册多个通道使用一个选择器,然后使用一个单独的线程来“选择”通道:这些通道里已经有可以处理的输入,或者选择已准备写入的通道。这种选择机制,使得一个单独的线程很容易来管理多个通道。
 
 
 

Java NIO 由以下几个核心部分组成:

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

Channel

 

Channel的实现: (涵盖了UDP 和 TCP 网络IO,以及文件IO)

  • FileChannel
  • DatagramChannel
  • SocketChannel
  • ServerSocketChannel

读数据:

  • int bytesRead = inChannel.read(buf);

写数据:

  • int bytesWritten = inChannel.write(buf);
还有部分的使用,如配置Channel为阻塞或者非阻塞模式,以及如何注册到Selector上面去,参考Selector部分;
 

Buffer

Buffer实现: (byte,  char、short, int, long, float, double )

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

Buffer使用

读数据
  • flip()方法

    • 将Buffer从写模式切换到读模式
    • 调用flip()方法会将position设回0,并将limit设置成之前position的值。
    • buf.flip();
  • (char) buf.get()
    • 读取数据
  • Buffer.rewind()Buffer.mark()方法,可以标记Buffer中的一个特定position。之后可以通过调用
    • 将position设回0,所以你可以重读Buffer中的所有数据
    • limit保持不变,仍然表示能从Buffer中读取多少个元素(byte、char等)
  • Buffer.reset()方法,恢复到Buffer.mark()标记时的position
  • 一旦读完了所有的数据,就需要清空缓冲区,让它可以再次被写入。
  • clear()方法会:
    • 清空整个缓冲区。
    • position将被设回0,limit被设置成 capacity的值
  • compact()方法:
    • 只会清除已经读过的数据;任何未读的数据都被移到缓冲区的起始处,新写入的数据将放到缓冲区未读数据的后面。
    • 将position设到最后一个未读元素正后面,limit被设置成 capacity的值
写数据
  • buf.put(127);
 

Buffer的三个属性

  • capacity:含义与模式无关;Buffer的一个固定的大小值;Buffer满了需要将其清空才能再写;

    • ByteBuffer.allocate(48);该buffer的capacity为48byte
    • CharBuffer.allocate(1024);该buffer的capacity为1024个char
  • position:含义取决于Buffer处在读模式还是写模式(初始值为0,写或者读操作的当前位置)
    • 写数据时,初始的position值为0;其值最大可为capacity-1
    • 将Buffer从写模式切换到读模式,position会被重置为0
  • limit:含义取决于Buffer处在读模式还是写模式(写limit=capacity;读limit等于最多可以读取到的数据)
    • 写模式下,limit等于Buffer的capacity
    • 切换Buffer到读模式时, limit表示你最多能读到多少数据;
 

Selector

概述

Selector允许单线程处理多个 Channel。如果你的应用打开了多个连接(通道),但每个连接的流量都很低,使用Selector就会很方便。例如,在一个聊天服务器中。

    要使用Selector,得向Selector注册Channel,然后调用它的select()方法。这个方法会一直阻塞到某个注册的通道有事件就绪。一旦这个方法返回,线程就可以处理这些事件,事件的例子有如新连接进来,数据接收等。 

使用

  1. 创建:Selector selector = Selector.open();
  2. 注册通道:
    • channel.configureBlocking(false);

      • //与Selector一起使用时,Channel必须处于非阻塞模式
      • //这意味着不能将FileChannel与Selector一起使用,因为FileChannel不能切换到非阻塞模式(而套接字通道都可以)
    • SelectionKey key = channel.register(selector, Selectionkey.OP_READ); 
      • //第二个参数表明Selector监听Channel时对什么事件感兴趣
      • //SelectionKey.OP_CONNECT  SelectionKey.OP_ACCEPT  SelectionKey.OP_READ SelectionKey.OP_WRITE
      • //可以用或操作符将多个兴趣组合一起
    • SelectionKey
      • 包含了interest集合 、ready集合 、Channel 、Selector 、附加的对象(可选)
      • int interestSet = key.interestOps();可以进行类似interestSet & SelectionKey.OP_CONNECT的判断
  3. 使用:
    • select():阻塞到至少有一个通道在你注册的事件上就绪了
    • selectNow():不会阻塞,不管什么通道就绪都立刻返回
    • selectedKeys():访问“已选择键集(selected key set)”中的就绪通道
    • close():使用完selector需要用其close()方法会关闭该Selector,且使注册到该Selector上的所有SelectionKey实例无效
 
  1. Set selectedKeys = selector.selectedKeys();
  2. Iterator keyIterator = selectedKeys.iterator();
  3. while(keyIterator.hasNext()) {
  4. SelectionKey key = keyIterator.next();
  5. if(key.isAcceptable()) {
  6. // a connection was accepted by a ServerSocketChannel.
  7. } else if (key.isConnectable()) {
  8. // a connection was established with a remote server.
  9. } else if (key.isReadable()) {
  10. // a channel is ready for reading
  11. } else if (key.isWritable()) {
  12. // a channel is ready for writing
  13. }
  14. keyIterator.remove();//注意这里必须手动remove;表明该selectkey我已经处理过了;
  15. }
 
 

Java测试关键代码

  1. RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
  2. FileChannel inChannel = aFile.getChannel();  //从一个InputStream outputstream中获取channel
  3. //create buffer with capacity of 48 bytes
  4. ByteBuffer buf = ByteBuffer.allocate(48);
  5. int bytesRead = inChannel.read(buf); //read into buffer.
  6. while (bytesRead != -1) {
  7. buf.flip();  //make buffer ready for read
  8. while(buf.hasRemaining()){
  9. System.out.print((char) buf.get()); // read 1 byte at a time
  10. }
  11. buf.clear(); //make buffer ready for writing
  12. bytesRead = inChannel.read(buf);
  13. }
  14. aFile.close();

文件通道

  1. RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
  2. FileChannel inChannel = aFile.getChannel();
读数据
  1. ByteBuffer buf = ByteBuffer.allocate(48);
  2. int bytesRead = inChannel.read(buf);
写数据
  1. String newData = "New String to write to file..." + System.currentTimeMillis();
  2. ByteBuffer buf = ByteBuffer.allocate(48);
  3. buf.clear();
  4. buf.put(newData.getBytes());
  5. buf.flip();
  6. while(buf.hasRemaining()) {
  7. channel.write(buf);
  8. }

Socket 通道

  1. SocketChannel socketChannel = SocketChannel.open();
  2. socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80));
读数据
  1. ByteBuffer buf = ByteBuffer.allocate(48);
  2. int bytesRead = socketChannel.read(buf);
写数据
  1. String newData = "New String to write to file..." + System.currentTimeMillis();
  2. ByteBuffer buf = ByteBuffer.allocate(48);
  3. buf.clear();
  4. buf.put(newData.getBytes());
  5. buf.flip();
  6. while(buf.hasRemaining()) {
  7. socketChannel.write(buf);
  8. }

ServerSocket 通道

  1. ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
  2. serverSocketChannel.socket().bind(new InetSocketAddress(9999));
  3. while(true){
  4. SocketChannel socketChannel =
  5. serverSocketChannel.accept();
  6. //do something with socketChannel...
  7. }
 

Datagram 通道(channel的读写操作与前面的有差异)

  1. DatagramChannel channel = DatagramChannel.open();
  2. channel.socket().bind(new InetSocketAddress(9999));
读数据
  1. ByteBuffer buf = ByteBuffer.allocate(48);
  2. buf.clear();
  3. channel.receive(buf);
  4. //receive()方法会将接收到的数据包内容复制到指定的Buffer. 如果Buffer容不下收到的数据,多出的数据将被丢弃。
写数据
    1. String newData = "New String to write to file..." + System.currentTimeMillis();
    2. ByteBuffer buf = ByteBuffer.allocate(48);
    3. buf.clear();
    4. buf.put(newData.getBytes());
    5. buf.flip();
    6. int bytesSent = channel.send(buf, new InetSocketAddress("jenkov.com", 80));

Java NIO 与 IO之间的区别的更多相关文章

  1. Java NIO 和 IO 的区别详解

    Java NIO为jdk1.4提供了新的API,本文主要来比较一下Java中NIO和IO的区别,Java初学者可以了解一下. 下表总结了Java NIO和IO之间的主要差别,我会更详细地描述表中每部分 ...

  2. Java NIO和IO的区别(转)

    原文链接:Java NIO和IO的区别 下表总结了Java NIO和IO之间的主要差别,我会更详细地描述表中每部分的差异. 复制代码代码如下: IO                NIO面向流     ...

  3. java nio 与io区别

    转自:http://blog.csdn.net/keda8997110/article/details/19549493 当学习了Java NIO和IO的API后,一个问题马上涌入脑海: 我应该何时使 ...

  4. Java NIO和IO的区别

    下表总结了Java NIO和IO之间的主要差别,我会更详细地描述表中每部分的差异. 复制代码 代码如下: IO NIO面向流 面向缓冲阻塞IO 非阻塞IO无 选择器 面向流与面向缓冲 Java NIO ...

  5. Java NIO和IO的主要区别

    From :http://blog.csdn.net/keda8997110/article/details/19549493 下表总结了Java NIO和IO之间的主要差别,我会更详细地描述表中每部 ...

  6. Java NIO与IO

    当学习了Java NIO和IO的API后,一个问题立即涌入脑海: 我应该何时使用IO,何时使用NIO呢?在本文中,我会尽量清晰地解析Java NIO和IO的差异.它们的使用场景,以及它们怎样影响您的代 ...

  7. Java NIO 与 IO

    我应该何时使用 IO,何时使用 NIO 呢?在本文中,我会尽量清晰地解析 Java NIO 和 IO 的差异.它们的使用场景,以及它们如何影响您的代码设计. Java NIO 和 IO 的主要区别 下 ...

  8. [转载] Java NIO与IO

    原文地址:http://tutorials.jenkov.com/java-nio/nio-vs-io.html 作者:Jakob Jenkov   译者:郭蕾    校对:方腾飞 当学习了Java ...

  9. Java NIO系列教程(十二) Java NIO与IO

    当学习了Java NIO和IO的API后,一个问题马上涌入脑海: 我应该何时使用IO,何时使用NIO呢?在本文中,我会尽量清晰地解析Java NIO和IO的差异.它们的使用场景,以及它们如何影响您的代 ...

随机推荐

  1. android的activity被杀死后如何重启

    最近公司的大屏展示机器人上的程序运行时间长了,比如五天,十天会出现偶尔的崩溃,查日志可能是内存溢出或者是ndk层的错误,这种错误一时也不太好查找,但是产品那边有个要求就是程序退出了一定要能重启,能抓日 ...

  2. Spark 论文篇-RDD:一种为内存化集群计算设计的容错抽象(中英双语)

    论文内容: 待整理 参考文献: Resilient Distributed Datasets: A Fault-Tolerant Abstraction for In-Memory Cluster C ...

  3. iptabes一条指令开放多个端口

    开放某个范围段内的端口: -A INPUT -p tcp -m state --state NEW -m tcp --dport 9001:9009 -j ACCEPT 开放多个不连续的端口: -A ...

  4. Atitit 医学之道 attilax总结

    Atitit 医学之道 attilax总结 1. 相关的学科3 1.1. 口腔医学  ok3 1.2. 人体解剖学  ok3 1.3. 生理学  ok3 1.4. 病理学  ok3 1.5. 骨伤科学 ...

  5. docker内程序如何读取dockerfile和compose.yml中设置的环境变量

    docker内程序如何读取dockerfile和compose.yml中设置的环境变量 背景 compose文件中配置了服务A和服务B,其中B服务调用了A服务的接口,那么B的实现代码中该如何调用A的服 ...

  6. ffmpeg主体架构分析

    [时间:2016-07] [状态:Open] [关键词:ffmpeg,libavcodec,libavformat] FFmpeg接触几年了,用的比较多的是libavcodec和libavformat ...

  7. Gtk-WARNING**:无法在模块路径中找到主题引擎:“pixmap”的解决

    Gtk-WARNING**:无法在模块路径中找到主题引擎:“pixmap”的解决  解决以上问题, 只需要安装 gnome-themes-standard 即可 如果终端中提示:   (gvim:23 ...

  8. Android开发(十四)——SimpleAdapter与自定义控件

    ListView中可以使用SimpleAdapter进行数据与视图的绑定,但都是对已有的系统控件的绑定,如果自定义空间直接使用SimpleAdapter绑定,则会报错. 如,使用CircleImage ...

  9. 聊聊动态语言那些事(Python)

    动态编程语言是高级程序设计语言的一个类别,在计算机科学领域已被广泛应用.它是一类在运行时可以改变其结构的语言:例如新的函数.对象.甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化.动态语言 ...

  10. hbase源码系列(十五)终结篇&Scan续集-->如何查询出来下一个KeyValue

    这是这个系列的最后一篇了,实在没精力写了,本来还想写一下hbck的,这个东西很常用,当hbase的Meta表出现错误的时候,它能够帮助我们进行修复,无奈看到3000多行的代码时,退却了,原谅我这点自私 ...