源码网商城,靠谱的源码在线交易网站 我的订单 购物车 帮助

源码网商城

java NIO 详解

  • 时间:2022-08-28 20:07 编辑: 来源: 阅读:
  • 扫一扫,手机访问
摘要:java NIO 详解
[b]Java NIO提供了与标准IO不同的IO工作方式:[/b] Channels and Buffers(通道和缓冲区):标准的IO基于字节流和字符流进行操作的,而NIO是基于通道(Channel)和缓冲区(Buffer)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。 Asynchronous IO(异步IO):Java NIO可以让你异步的使用IO,例如:当线程从通道读取数据到缓冲区时,线程还是可以进行其他事情。当数据被写入到缓冲区时,线程可以继续处理它。从缓冲区写入通道也类似。 Selectors(选择器):Java NIO引入了选择器的概念,选择器用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个的线程可以监听多个数据通道。 下面就来详细介绍Java NIO的相关知识。 [b][i]Java NIO 概述 [/i][/b] Java NIO 由以下几个核心部分组成: Channels Buffers Selectors 虽然Java NIO 中除此之外还有很多类和组件,但在我看来,Channel,Buffer 和 Selector 构成了核心的API。其它组件,如Pipe和FileLock,只不过是与三个核心组件共同使用的工具类。因此,在概述中我将集中在这三个组件上。其它组件会在单独的章节中讲到。 [u]Channel 和 Buffer [/u] 基本上,所有的 IO 在NIO 中都从一个Channel 开始。Channel 有点象流。 数据可以从Channel读到Buffer中,也可以从Buffer 写到Channel中。这里有个图示: [img]http://files.jb51.net/file_images/article/201410/2014102910153022.png[/img] Channel和Buffer有好几种类型。下面是JAVA NIO中的一些主要Channel的实现: FileChannel DatagramChannel SocketChannel ServerSocketChannel 正如你所看到的,这些通道涵盖了UDP 和 TCP 网络IO,以及文件IO。 与这些类一起的有一些有趣的接口,但为简单起见,我尽量在概述中不提到它们。本教程其它章节与它们相关的地方我会进行解释。 以下是Java NIO里关键的Buffer实现: ByteBuffer CharBuffer DoubleBuffer FloatBuffer IntBuffer LongBuffer ShortBuffer 这些Buffer覆盖了你能通过IO发送的基本数据类型:byte, short, int, long, float, double 和 char。 Java NIO 还有个 Mappedyteuffer,用于表示内存映射文件, 我也不打算在概述中说明。 Selector Selector允许单线程处理多个 Channel。如果你的应用打开了多个连接(通道),但每个连接的流量都很低,使用Selector就会很方便。例如,在一个聊天服务器中。 这是在一个单线程中使用一个Selector处理3个Channel的图示: [img]http://files.jb51.net/file_images/article/201410/2014102910153023.png[/img] 要使用Selector,得向Selector注册Channel,然后调用它的select()方法。这个方法会一直阻塞到某个注册的通道有事件就绪。一旦这个方法返回,线程就可以处理这些事件,事件的例子有如新连接进来,数据接收等。 Java NIO vs. IO (本部分原文地址,作者:Jakob Jenkov,译者:郭蕾,校对:方腾飞) 当学习了Java NIO和IO的API后,一个问题马上涌入脑海: 引用 我应该何时使用IO,何时使用NIO呢?在本文中,我会尽量清晰地解析Java NIO和IO的差异、它们的使用场景,以及它们如何影响您的代码设计。 Java NIO和IO的主要区别 下表总结了Java NIO和IO之间的主要差别,我会更详细地描述表中每部分的差异。 IO NIO Stream oriented Buffer oriented Blocking IO Non blocking IO Selectors 面向流与面向缓冲 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调用。 数据处理。 用来处理数据的线程数。 API调用 当然,使用NIO的API调用时看起来与使用IO时有所不同,但这并不意外,因为并不是仅从一个InputStream逐字节读取,而是数据必须先读入缓冲区再处理。 数据处理 使用纯粹的NIO设计相较IO设计,数据处理也受到影响。 在IO设计中,我们从InputStream或 Reader逐字节读取数据。假设你正在处理一基于行的文本数据流,例如: 
[url=javascript:;] }
这个循环遍历已选择键集中的每个键,并检测各个键所对应的通道的就绪事件。 注意每次迭代末尾的keyIterator.remove()调用。Selector不会自己从已选择键集中移除SelectionKey实例。必须在处理完通道时自己移除。下次该通道变成就绪时,Selector会再次将其放入已选择键集中。 SelectionKey.channel()方法返回的通道需要转型成你要处理的类型,如ServerSocketChannel或SocketChannel等。 (6)wakeUp() 某个线程调用select()方法后阻塞了,即使没有通道已经就绪,也有办法让其从select()方法返回。只要让其它线程在第一个线程调用select()方法的那个对象上调用Selector.wakeup()方法即可。阻塞在select()方法上的线程会立马返回。 如果有其它线程调用了wakeup()方法,但当前没有线程阻塞在select()方法上,下个调用select()方法的线程会立即“醒来(wake up)”。 (7)close() 用完Selector后调用其close()方法会关闭该Selector,且使注册到该Selector上的所有SelectionKey实例无效。通道本身并不会关闭。 (8)完整的示例 这里有一个完整的示例,打开一个Selector,注册一个通道注册到这个Selector上(通道的初始化过程略去),然后持续监控这个Selector的四种事件(接受,连接,读,写)是否就绪。 文件通道 (本部分原文链接,作者:Jakob Jenkov,译者:周泰,校对:丁一) Java NIO中的FileChannel是一个连接到文件的通道。可以通过文件通道读写文件。 FileChannel无法设置为非阻塞模式,它总是运行在阻塞模式下。 打开FileChannel 在使用FileChannel之前,必须先打开它。但是,我们无法直接打开一个FileChannel,需要通过使用一个InputStream、OutputStream或RandomAccessFile来获取一个FileChannel实例。下面是通过RandomAccessFile打开FileChannel的示例:
[u]复制代码[/u] 代码如下:
RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw"); FileChannel inChannel = aFile.getChannel();
从FileChannel读取数据 调用多个read()方法之一从FileChannel中读取数据。如:
[u]复制代码[/u] 代码如下:
ByteBuffer buf = ByteBuffer.allocate(48); int bytesRead = inChannel.read(buf);
首先,分配一个Buffer。从FileChannel中读取的数据将被读到Buffer中。 然后,调用FileChannel.read()方法。该方法将数据从FileChannel读取到Buffer中。read()方法返回的int值表示了有多少字节被读到了Buffer中。如果返回-1,表示到了文件末尾。 向FileChannel写数据 使用FileChannel.write()方法向FileChannel写数据,该方法的参数是一个Buffer。如:
[u]复制代码[/u] 代码如下:
String newData = "New String to write to file..." + System.currentTimeMillis(); ByteBuffer buf = ByteBuffer.allocate(48); buf.clear(); buf.put(newData.getBytes()); buf.flip(); while(buf.hasRemaining()) { channel.write(buf); }
注意FileChannel.write()是在while循环中调用的。因为无法保证write()方法一次能向FileChannel写入多少字节,因此需要重复调用write()方法,直到Buffer中已经没有尚未写入通道的字节。 关闭FileChannel 用完FileChannel后必须将其关闭。如:
[u]复制代码[/u] 代码如下:
channel.close();
FileChannel的position方法 有时可能需要在FileChannel的某个特定位置进行数据的读/写操作。可以通过调用position()方法获取FileChannel的当前位置。 也可以通过调用position(long pos)方法设置FileChannel的当前位置。 这里有两个例子:
[u]复制代码[/u] 代码如下:
long pos = channel.position(); channel.position(pos +123);
如果将位置设置在文件结束符之后,然后试图从文件通道中读取数据,读方法将返回-1 —— 文件结束标志。 如果将位置设置在文件结束符之后,然后向通道中写数据,文件将撑大到当前位置并写入数据。这可能导致“文件空洞”,磁盘上物理文件中写入的数据间有空隙。 FileChannel的size方法 FileChannel实例的size()方法将返回该实例所关联文件的大小。如:
[u]复制代码[/u] 代码如下:
long fileSize = channel.size();
FileChannel的truncate方法 可以使用FileChannel.truncate()方法截取一个文件。截取文件时,文件将中指定长度后面的部分将被删除。如:
[u]复制代码[/u] 代码如下:
channel.truncate(1024);
这个例子截取文件的前1024个字节。 FileChannel的force方法 FileChannel.force()方法将通道里尚未写入磁盘的数据强制写到磁盘上。出于性能方面的考虑,操作系统会将数据缓存在内存中,所以无法保证写入到FileChannel里的数据一定会即时写到磁盘上。要保证这一点,需要调用force()方法。 force()方法有一个boolean类型的参数,指明是否同时将文件元数据(权限信息等)写到磁盘上。 下面的例子同时将文件数据和元数据强制写到磁盘上:
[u]复制代码[/u] 代码如下:
channel.force(true);
Socket 通道 (本部分原文链接,作者:Jakob Jenkov,译者:郑玉婷,校对:丁一) Java NIO中的SocketChannel是一个连接到TCP网络套接字的通道。可以通过以下2种方式创建SocketChannel: 打开一个SocketChannel并连接到互联网上的某台服务器。 一个新连接到达ServerSocketChannel时,会创建一个SocketChannel。 打开 SocketChannel 下面是SocketChannel的打开方式:
[u]复制代码[/u] 代码如下:
SocketChannel socketChannel = SocketChannel.open(); socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80));
关闭 SocketChannel 当用完SocketChannel之后调用SocketChannel.close()关闭SocketChannel:
[u]复制代码[/u] 代码如下:
socketChannel.close();
从 SocketChannel 读取数据 要从SocketChannel中读取数据,调用一个read()的方法之一。以下是例子:
[u]复制代码[/u] 代码如下:
ByteBuffer buf = ByteBuffer.allocate(48); int bytesRead = socketChannel.read(buf);
首先,分配一个Buffer。从SocketChannel读取到的数据将会放到这个Buffer中。 然后,调用SocketChannel.read()。该方法将数据从SocketChannel 读到Buffer中。read()方法返回的int值表示读了多少字节进Buffer里。如果返回的是-1,表示已经读到了流的末尾(连接关闭了)。 写入 SocketChannel 写数据到SocketChannel用的是SocketChannel.write()方法,该方法以一个Buffer作为参数。示例如下:
[u]复制代码[/u] 代码如下:
String newData = "New String to write to file..." + System.currentTimeMillis(); ByteBuffer buf = ByteBuffer.allocate(48); buf.clear(); buf.put(newData.getBytes()); buf.flip(); while(buf.hasRemaining()) { channel.write(buf); }
注意SocketChannel.write()方法的调用是在一个while循环中的。Write()方法无法保证能写多少字节到SocketChannel。所以,我们重复调用write()直到Buffer没有要写的字节为止。 非阻塞模式 可以设置 SocketChannel 为非阻塞模式(non-blocking mode).设置之后,就可以在异步模式下调用connect(), read() 和write()了。 connect() 如果SocketChannel在非阻塞模式下,此时调用connect(),该方法可能在连接建立之前就返回了。为了确定连接是否建立,可以调用finishConnect()的方法。像这样:
[u]复制代码[/u] 代码如下:
socketChannel.configureBlocking(false); socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80)); while(! socketChannel.finishConnect() ){ //wait, or do something else... }
write() 非阻塞模式下,write()方法在尚未写出任何内容时可能就返回了。所以需要在循环中调用write()。前面已经有例子了,这里就不赘述了。 read() 非阻塞模式下,read()方法在尚未读取到任何数据时可能就返回了。所以需要关注它的int返回值,它会告诉你读取了多少字节。 非阻塞模式与选择器 非阻塞模式与选择器搭配会工作的更好,通过将一或多个SocketChannel注册到Selector,可以询问选择器哪个通道已经准备好了读取,写入等。Selector与SocketChannel的搭配使用会在后面详讲。 ServerSocket 通道 (本部分原文链接,作者:Jakob Jenkov,译者:郑玉婷,校对:丁一) Java NIO中的 ServerSocketChannel 是一个可以监听新进来的TCP连接的通道,就像标准IO中的ServerSocket一样。ServerSocketChannel类在 java.nio.channels包中。 这里有个例子:
[u]复制代码[/u] 代码如下:
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.socket().bind(new InetSocketAddress(9999)); while(true){ SocketChannel socketChannel = serverSocketChannel.accept(); //do something with socketChannel... }
打开 ServerSocketChannel 通过调用 ServerSocketChannel.open() 方法来打开ServerSocketChannel.如:
[u]复制代码[/u] 代码如下:
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
关闭 ServerSocketChannel 通过调用ServerSocketChannel.close() 方法来关闭ServerSocketChannel. 如:
[u]复制代码[/u] 代码如下:
serverSocketChannel.close();
监听新进来的连接 通过 ServerSocketChannel.accept() 方法监听新进来的连接。当 accept()方法返回的时候,它返回一个包含新进来的连接的 SocketChannel。因此,accept()方法会一直阻塞到有新连接到达。 通常不会仅仅只监听一个连接,在while循环中调用 accept()方法. 如下面的例子:
[u]复制代码[/u] 代码如下:
while(true){ SocketChannel socketChannel = serverSocketChannel.accept(); //do something with socketChannel... }
当然,也可以在while循环中使用除了true以外的其它退出准则。 非阻塞模式 ServerSocketChannel可以设置成非阻塞模式。在非阻塞模式下,accept() 方法会立刻返回,如果还没有新进来的连接,返回的将是null。 因此,需要检查返回的SocketChannel是否是null。如:
[u]复制代码[/u] 代码如下:
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.socket().bind(new InetSocketAddress(9999)); serverSocketChannel.configureBlocking(false); while(true){ SocketChannel socketChannel = serverSocketChannel.accept(); if(socketChannel != null){ //do something with socketChannel... } }
Datagram 通道 (本部分原文链接,作者:Jakob Jenkov,译者:郑玉婷,校对:丁一) Java NIO中的DatagramChannel是一个能收发UDP包的通道。因为UDP是无连接的网络协议,所以不能像其它通道那样读取和写入。它发送和接收的是数据包。 打开 DatagramChannel 下面是 DatagramChannel 的打开方式:
[u]复制代码[/u] 代码如下:
DatagramChannel channel = DatagramChannel.open(); channel.socket().bind(new InetSocketAddress(9999));
这个例子打开的 DatagramChannel可以在UDP端口9999上接收数据包。 接收数据 通过receive()方法从DatagramChannel接收数据,如:
[u]复制代码[/u] 代码如下:
ByteBuffer buf = ByteBuffer.allocate(48); buf.clear(); channel.receive(buf);
receive()方法会将接收到的数据包内容复制到指定的Buffer. 如果Buffer容不下收到的数据,多出的数据将被丢弃。 发送数据 通过send()方法从DatagramChannel发送数据,如:
[u]复制代码[/u] 代码如下:
String newData = "New String to write to file..." + System.currentTimeMillis(); ByteBuffer buf = ByteBuffer.allocate(48); buf.clear(); buf.put(newData.getBytes()); buf.flip(); int bytesSent = channel.send(buf, new InetSocketAddress("jenkov.com", 80));
这个例子发送一串字符到”jenkov.com”服务器的UDP端口80。 因为服务端并没有监控这个端口,所以什么也不会发生。也不会通知你发出的数据包是否已收到,因为UDP在数据传送方面没有任何保证。 连接到特定的地址 可以将DatagramChannel“连接”到网络中的特定地址的。由于UDP是无连接的,连接到特定地址并不会像TCP通道那样创建一个真正的连接。而是锁住DatagramChannel ,让其只能从特定地址收发数据。 这里有个例子:
[u]复制代码[/u] 代码如下:
channel.connect(new InetSocketAddress("jenkov.com", 80));
当连接后,也可以使用read()和write()方法,就像在用传统的通道一样。只是在数据传送方面没有任何保证。这里有几个例子:
[u]复制代码[/u] 代码如下:
int bytesRead = channel.read(buf); int bytesWritten = channel.write(but);
管道(Pipe) (本部分原文链接,作者:Jakob Jenkov,译者:黄忠,校对:丁一) Java NIO 管道是2个线程之间的单向数据连接。Pipe有一个source通道和一个sink通道。数据会被写到sink通道,从source通道读取。 这里是Pipe原理的图示: 创建管道 通过Pipe.open()方法打开管道。例如:
[u]复制代码[/u] 代码如下:
Pipe pipe = Pipe.open();
向管道写数据 要向管道写数据,需要访问sink通道。像这样:
[u]复制代码[/u] 代码如下:
Pipe.SinkChannel sinkChannel = pipe.sink();
通过调用SinkChannel的write()方法,将数据写入SinkChannel,像这样:
[u]复制代码[/u] 代码如下:
String newData = "New String to write to file..." + System.currentTimeMillis(); ByteBuffer buf = ByteBuffer.allocate(48); buf.clear(); buf.put(newData.getBytes()); buf.flip(); while(buf.hasRemaining()) { <b>sinkChannel.write(buf);</b> } [code] 从管道读取数据 从读取管道的数据,需要访问source通道,像这样: [code] Pipe.SourceChannel sourceChannel = pipe.source();
调用source通道的read()方法来读取数据,像这样:
[u]复制代码[/u] 代码如下:
ByteBuffer buf = ByteBuffer.allocate(48); int bytesRead = inChannel.read(buf);
read()方法返回的int值会告诉我们多少字节被读进了缓冲区。
  • 全部评论(0)
联系客服
客服电话:
400-000-3129
微信版

扫一扫进微信版
返回顶部