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

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

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

传统IO vs NIO

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel; public class SimpleFileTransferTest { private long transferFile(File source, File des) throws IOException {
long startTime = System.currentTimeMillis();
if (!des.exists()) {
des.createNewFile();
}
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(source));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(des)); //将数据源读到的内容写入目的地--使用数组
byte[] bytes = new byte[bis.available()];
int len;
while ((len = bis.read(bytes)) != -1) {
bos.write(bytes, 0, len);
} long endTime = System.currentTimeMillis();
return endTime - startTime;
} private long transferFileWithNIO(File source, File des) throws IOException {
long startTime = System.currentTimeMillis();
int length = 0x8FFFFFF;
if (!des.exists()) {
des.createNewFile();
}
//FileChannel对象只能通过调用来获得getChannel()方法的开放上RandomAccessFile,FileInputStream或FileOutputStream对象。
// 无法FileChannel直接创建对象。
RandomAccessFile read = new RandomAccessFile(source, "rw");
RandomAccessFile write = new RandomAccessFile(des, "rw");
FileChannel readChannel = read.getChannel();
FileChannel writeChannel = write.getChannel(); //1M缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(1024*1024);
// MappedByteBuffer out = read.getChannel()
// .map(FileChannel.MapMode.READ_WRITE, 0, length);
while (readChannel.read(byteBuffer) > 0) {
//byteBuffer.limit( byteBuffer.position() ).position(0);
byteBuffer.flip();
writeChannel.write(byteBuffer);
byteBuffer.clear();
} writeChannel.close();
readChannel.close();
long endTime = System.currentTimeMillis();
return endTime - startTime;
} public static void main(String[] args) throws IOException {
SimpleFileTransferTest simpleFileTransferTest = new SimpleFileTransferTest();
File source = new File("C:\\Users\\Administrator\\Desktop\\事务.png");
File des = new File("C:\\Users\\Administrator\\Desktop\\io.txt");
File nio = new File("C:\\Users\\Administrator\\Desktop\\nio.txt"); long time = simpleFileTransferTest.transferFile(source, des);
System.out.println(time + ":普通字节流时间"); long timeNio = simpleFileTransferTest.transferFileWithNIO(source, nio);
System.out.println(timeNio + ":NIO时间");
}
}

一、为什么要使用NIO

可以看到使用过NIO重新实现过的传统IO根本不虚,在大文件下效果还比NIO要好(当然了,个人几次的测试,或许不是很准)

  • 而NIO要有一定的学习成本,也没有传统IO那么好理解。

IO操作往往在两个场景下会用到:

  • 文件IO

  • 网络IO

NIO的魅力:在网络中使用IO就可以体现出来了

二、NIO快速入门

io和nio的区别

可简单认为:IO是面向流的处理,NIO是面向块(缓冲区)的处理

    • 面向流的I/O 系统一次一个字节地处理数据

    • 一个面向块(缓冲区)的I/O系统以块的形式处理数据

NIO主要有三个核心部分组成

  • buffer缓冲区

  • Channel管道

  • Selector选择器

2.1buffer缓冲区和Channel管道

在NIO中并不是以流的方式来处理数据的,而是以buffer缓冲区和Channel管道配合使用来处理数据。

简单理解一下:

  • Channel管道比作成铁路,buffer缓冲区比作成火车(运载着货物)

而我们的NIO就是通过Channel管道运输着存储数据的Buffer缓冲区的来实现数据的处理

  • 要时刻记住:Channel不与数据打交道,它只负责运输数据。与数据打交道的是Buffer缓冲区

    • Channel-->运输

    • Buffer-->数据

相对于传统IO而言,流是单向的。对于NIO而言,有了Channel管道这个概念,读写都是双向的。

2.1.3FileChannel通道核心要点

Channel通道只负责传输数据、不直接操作数据的。操作数据都是通过Buffer缓冲区来进行操作!

三、网络IO(没有Selector选择器)

客户端

public class IOBlockClient {

    public static void main(String[] args) throws IOException {

        // 1. 获取通道
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 6666)); // 2. 发送一张图片给服务端吧
FileChannel fileChannel = FileChannel.open(Paths.get("C:\\Users\\Administrator\\Desktop\\nio.png"), StandardOpenOption.READ); // 3.要使用NIO,有了Channel,就必然要有Buffer,Buffer是与数据打交道的呢
ByteBuffer buffer = ByteBuffer.allocate(1024); // 4.读取本地文件(图片),发送到服务器
while (fileChannel.read(buffer) != -1) { // 在读之前都要切换成读模式
buffer.flip(); socketChannel.write(buffer); // 读完切换成写模式,能让管道继续读取文件的数据
buffer.clear();
} // 5. 关闭流
fileChannel.close();
socketChannel.close();
}
}

服务端

public class IOBlockServer {

    public static void main(String[] args) throws IOException {

        // 1.获取通道
ServerSocketChannel server = ServerSocketChannel.open(); // 2.得到文件通道,将客户端传递过来的图片写到本地项目下(写模式、没有则创建)
FileChannel outChannel = FileChannel.open(Paths.get("io.png"), StandardOpenOption.WRITE, StandardOpenOption.CREATE); // 3. 绑定链接
server.bind(new InetSocketAddress(6666)); // 4. 获取客户端的连接(阻塞的)
SocketChannel client = server.accept(); // 5. 要使用NIO,有了Channel,就必然要有Buffer,Buffer是与数据打交道的呢
ByteBuffer buffer = ByteBuffer.allocate(1024); // 6.将客户端传递过来的图片保存在本地中
while (client.read(buffer) != -1) { // 在读之前都要切换成读模式
buffer.flip(); outChannel.write(buffer); // 读完切换成写模式,能让管道继续读取文件的数据
buffer.clear(); } // 7.关闭通道
outChannel.close();
client.close();
server.close();
}
}

如果仅仅是上面的代码是不行的!这个程序会阻塞起来!

  • 因为服务端不知道客户端还有没有数据要发过来(与刚开始不一样,客户端发完数据就将流关闭了,服务端可以知道客户端没数据发过来了),导致服务端一直在读取客户端发过来的数据。

  • 进而导致了阻塞!

网络NIO

客户端

public class NoBlockClient {

    public static void main(String[] args) throws IOException {

        // 1. 获取通道
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 6666)); // 1.1切换成非阻塞模式
socketChannel.configureBlocking(false); // 2. 发送一张图片给服务端吧
FileChannel fileChannel = FileChannel.open(Paths.get("C:\Users\Administrator\Desktop\nio.png"), StandardOpenOption.READ); // 3.要使用NIO,有了Channel,就必然要有Buffer,Buffer是与数据打交道的呢
ByteBuffer buffer = ByteBuffer.allocate(1024); // 4.读取本地文件(图片),发送到服务器
while (fileChannel.read(buffer) != -1) { // 在读之前都要切换成读模式
buffer.flip(); socketChannel.write(buffer); // 读完切换成写模式,能让管道继续读取文件的数据
buffer.clear();
} // 5. 关闭流
fileChannel.close();
socketChannel.close();
}
}

服务端

public class NoBlockServer {

    public static void main(String[] args) throws IOException {

        // 1.获取通道
ServerSocketChannel server = ServerSocketChannel.open(); // 2.切换成非阻塞模式
server.configureBlocking(false); // 3. 绑定连接
server.bind(new InetSocketAddress(6666)); // 4. 获取选择器
Selector selector = Selector.open(); // 4.1将通道注册到选择器上,指定接收“监听通道”事件
//方法的第二个参数,表示一个“interest 集合”,意思是通过 Selector 监听 Channel 时,对哪些( 可以是多个 )事件感兴趣。可以监听四种不同类型的事件:
//Connect :连接完成事件( TCP 连接 ),仅适用于客户端,对应 SelectionKey.OP_CONNECT 。
//Accept :接受新连接事件,仅适用于服务端,对应 SelectionKey.OP_ACCEPT 。
//Read :读事件,适用于两端,对应 SelectionKey.OP_READ ,表示 Buffer 可读。
//Write :写时间,适用于两端,对应 SelectionKey.OP_WRITE ,表示 Buffer 可写。
//Selector 可以对 Channel 的多个事件感兴趣,所以在我们想要注册 Channel 的多个事件到 Selector 中时,可以使用或运算 | 来组合多个事件
server.register(selector, SelectionKey.OP_ACCEPT); // 5. 轮训地获取选择器上已“就绪”的事件--->只要select()>0,说明已就绪
while (selector.select() > 0) {
// 6. 获取当前选择器所有注册的“选择键”(已就绪的监听事件)
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator(); // 7. 获取已“就绪”的事件,(不同的事件做不同的事)
while (iterator.hasNext()) { SelectionKey selectionKey = iterator.next(); // 接收事件就绪
if (selectionKey.isAcceptable()) { // 8. 获取客户端的链接
SocketChannel client = server.accept(); // 8.1 切换成非阻塞状态
client.configureBlocking(false); // 8.2 注册到选择器上-->拿到客户端的连接为了读取通道的数据(监听读就绪事件)
client.register(selector, SelectionKey.OP_READ);
// 读事件就绪
} else if (selectionKey.isReadable()) { // 9. 获取当前选择器读就绪状态的通道
SocketChannel client = (SocketChannel) selectionKey.channel(); // 9.1读取数据
ByteBuffer buffer = ByteBuffer.allocate(1024); // 9.2得到文件通道,将客户端传递过来的图片写到本地项目下(写模式、没有则创建)
FileChannel outChannel = FileChannel.open(Paths.get("2.png"), StandardOpenOption.WRITE, StandardOpenOption.CREATE); while (client.read(buffer) > 0) {
// 在读之前都要切换成读模式
buffer.flip(); outChannel.write(buffer); // 读完切换成写模式,能让管道继续读取文件的数据
buffer.clear();
}
}
// 10. 取消选择键(已经处理过的事件,就应该取消掉了)
//从 selected keys 集合中,获取到一个 key ,但是没有将它删除,那么下一次 #select 时, 这个 SelectionKey 还在 selectedKeys 中
iterator.remove();
}
}
}
}

  

在客户端上要想获取得到服务端的数据,也需要注册在register上(监听读事件)!

public class NoBlockClient2 {

    public static void main(String[] args) throws IOException {

        // 1. 获取通道
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 6666)); // 1.1切换成非阻塞模式
socketChannel.configureBlocking(false); // 1.2获取选择器
Selector selector = Selector.open(); // 1.3将通道注册到选择器中,获取服务端返回的数据
socketChannel.register(selector, SelectionKey.OP_READ); // 2. 发送一张图片给服务端吧
FileChannel fileChannel = FileChannel.open(Paths.get("C\\Users\\\administrator\\Desktop\\nio.png"), StandardOpenOption.READ); // 3.要使用NIO,有了Channel,就必然要有Buffer,Buffer是与数据打交道的呢
ByteBuffer buffer = ByteBuffer.allocate(1024); // 4.读取本地文件(图片),发送到服务器
while (fileChannel.read(buffer) != -1) { // 在读之前都要切换成读模式
buffer.flip(); socketChannel.write(buffer); // 读完切换成写模式,能让管道继续读取文件的数据
buffer.clear();
} // 5. 轮训地获取选择器上已“就绪”的事件--->只要select()>0,说明已就绪
while (selector.select() > 0) {
// 6. 获取当前选择器所有注册的“选择键”(已就绪的监听事件)
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator(); // 7. 获取已“就绪”的事件,(不同的事件做不同的事)
while (iterator.hasNext()) { SelectionKey selectionKey = iterator.next(); // 8. 读事件就绪
if (selectionKey.isReadable()) { // 8.1得到对应的通道
SocketChannel channel = (SocketChannel) selectionKey.channel(); ByteBuffer responseBuffer = ByteBuffer.allocate(1024); // 9. 知道服务端要返回响应的数据给客户端,客户端在这里接收
int readBytes = channel.read(responseBuffer); if (readBytes > 0) {
// 切换读模式
responseBuffer.flip();
System.out.println(new String(responseBuffer.array(), 0, readBytes));
}
} // 10. 取消选择键(已经处理过的事件,就应该取消掉了)
iterator.remove();
}
}
}
}

nio编程的更多相关文章

  1. JDK NIO编程

    我们首先需要澄清一个概念:NIO到底是什么的简称?有人称之为New I/O,因为它相对于之前的I/O类库是新增的,所以被称为New I/O,这是它的官方叫法.但是,由于之前老的I/O类库是阻塞I/O, ...

  2. Reactor 典型的 NIO 编程模型

    Doug Lea 在 Scalable IO in Java 的 PPT 中描述了 Reactor 编程模型的思想,大部分 NIO 框架和一些中间件的 NIO 编程都与它一样或是它的变体.本文结合 P ...

  3. Java IO编程全解(四)——NIO编程

    转载请注明出处:http://www.cnblogs.com/Joanna-Yan/p/7793964.html 前面讲到:Java IO编程全解(三)——伪异步IO编程 NIO,即New I/O,这 ...

  4. NIO和IO(BIO)的区别及NIO编程介绍

    IO(BIO)和NIO的区别:其本质就是阻塞和非阻塞的区别. 阻塞概念:应用程序在获取网络数据的时候,如果网络传输数据很慢,那么程序就一直等着,直到传输完毕为止. 非阻塞概念:应用程序直接可以获取已经 ...

  5. 关于NIO编程

    NIO概述 什么是NIO? Java NIO(New IO)是一个可以替代标准Java IO API的IO API(从Java 1.4开始),Java NIO提供了与标准IO不同的IO工作方式. Ja ...

  6. Nio编程模型总结

    终于,这两天的考试熬过去了, 兴致冲冲的来整理笔记来, 这篇博客是我近几天的NIO印象笔记汇总,记录了对Selector及Selector的重要参数的理解,对Channel的理解,常见的Channel ...

  7. NIO 编程模型

    NIO 编程模型 Doug Lea 在 Scalable IO in Java 的 PPT 中描述了 Reactor 编程模型的思想,大部分 NIO 框架和一些中间件的 NIO 编程都与它一样或是它的 ...

  8. 手动搭建I/O网络通信框架3:NIO编程模型,升级改造聊天室

    第一章:手动搭建I/O网络通信框架1:Socket和ServerSocket入门实战,实现单聊 第二章:手动搭建I/O网络通信框架2:BIO编程模型实现群聊 在第二章中用BIO编程模型,简单的实现了一 ...

  9. 深入学习Netty(2)——传统NIO编程

    前言 学习Netty编程,避免不了从了解Java 的NIO编程开始,这样才能通过比较让我们对Netty有更深的了解,才能知道Netty大大的好处.传统的NIO编程code起来比较麻烦,甚至有遗留Bug ...

  10. java socket nio编程

    上次写了一个socket的基本编程,但是有个问题,阻塞特别严重,于是小编便去找了nio学习了一下... public class TimeServer { public static void mai ...

随机推荐

  1. Magic xpa 3.x很容易将数据导出到Excel中

    Magic xpa 3.x很方便的将表中数据导出到Excel文件中,还可以自动将表中数据生成各种图表. 通过使用自带的打印数据内部事即可实现. 1.首先将打印数据任务属性设置为“是”. 2.可使用主程 ...

  2. my first note

    please do not laugh It is very glad to be here, I will study hard.

  3. DataStrom框架深造

    根据前一版DataStrom的使用,继续进行了改造和升级;前一版框架只是对服务按照名称注册和调用固化接口 最近研究后台框架,接触了ZBUS框架,我很喜欢ZBUS的前一版,该作者继续升级,已经在向AMQ ...

  4. 无法启动此程序,因为计算机中丢失VCRUNTIME140.dll 尝试重新安装此程序以解决此问题

    最近在阿里云上租了个服务器,想借此发布一些自己制作的网页.于是就打算安装一下环境,考虑到搭建动态网站所要安装的环境比较多,于是就选择了wampserver这样一个集成环境的安装. 由于我的服务器很新( ...

  5. debian apache2 修改默认路径

    1.修改 /etc/apache2/sites-enable/000-default.conf 将DocumentRoot改成你的路径 2.修改 /etc/apache2/apache2.conf & ...

  6. thinkphp中上传图片以及制成缩略图

    form表单中必须有的一个属性: enctype=”multipart/form-data” html的写法: <form action="__SELF__" method= ...

  7. 四、Python-元组

    列表非常适合用于存储在程序运行期间可能变化的数据集.列表是可以修改的,这对处理网 站的用户列表或游戏中的角色列表至关重要. Python将不能修改的值称为不可变的,而不可变的列表被称为元组 一.元组( ...

  8. 如何解决fiddler的响应显示乱码问题

    fiddler中Response出现乱码, 这是因为HTML被压缩了, 我们可以通过两种方法去解压.方法1:点击Response Raw上方的"Response is encoded any ...

  9. 团队作业-Week9-软件项目的风险

    一.软件项目中的风险 软件项目的风险无非体现在以下四个方面:需求.技术.成本和进度.IT项目开发中常见的风险有如下几类: (1)需求风险 ①需求已经成为项目基准,但需求还在继续变化: ②需求定义欠佳, ...

  10. 文件系统扫描工具-fsck

    文件系统扫描工具-fsck 注意的是fsck扫描文件系统时一定要在单用户模式.修复模式或把设备umount后进行.建议在单用户模式下运行.如果扫描正常运行中的系统,会造成系统文件损坏. fsck不仅可 ...