今天发表一篇一年前已经总结好的关于NIO的知识点,希望对新学的朋友有帮助,当时是写在Doc文档上面,最近有写博文的时间和心情,所以发表出来。由于在DOC copy出来,所以格式有点乱,希望大家见谅。如果写得不好,请大家给点建议。今天这篇是常识篇01,接下来还会有02。
(一)、为什么要使用java nio而不是流I/O呢?
使用某样技术前,先要对比一下,它的优点和缺点。在JDK1.4之前,我们通过流I/O的方式来进行输入输出操作。而在程序中,I/O操作是最耗时的。Java NIO与流I/O的作用是一样的,但是他们的使用方式和运行的效率不同。Java NIO的效率更快,主要是java nio是通过管道和缓冲区来完成的。下图对比一下流IO和NIO读取数据的方式。
(二)、何为缓冲区(Buffer)?
在上面已经提到了两个概念:管道和缓冲区,那何为缓冲区?缓冲区是一块内存块,其实就是一个数组。NIO中,读取数据时,直接读到缓冲区,写数据时,直接写到缓冲区,也就是说访问所有的数据都是通过缓冲区来进行的。操作缓冲区主要是通过Buffer类下的子类来进行。在JDK文档中可以看到Buffer类是抽象的,它的子类有:ByteBuffer、MappedByteBuffer、CharBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer。而ByteBuffer是经常用到的。下面以图片的方式来对ByteBuffer的属性方法进行讲解。
- ByteBuffer的几个属性:
- capacity:缓冲区能够容纳的数据元素的最大数量。比如说:ByteBuffer buffer = ByteBuffer.allocate(12); 那么capacity就等于12.
- Limit:下一个要被写或读的元素。从字面上理解为允许,当写入数据时,limit通常和capacity相等,当读数据时,limit代表buffer中的有效数据的长度(下面有图)
- Position:缓冲区的读写当前的下标。
- Mark:临时存放的位置下标。调用reset()前,要确保已经调用过mark(),因为调用reset()相当于position=mark。
- 例子:仔细观察position、limit、capacity、remaining的变化(在此之前要从http://www.javanio.org/上下载了一个基于图形界面的buffer模拟器),不过为了方便大家已经上传到了附件中
-
- 通过以上5步我们可以总结出,如果我们要读出缓冲区的数据时,先要调用flip()[其实调用flip()后相当于limit=position,position=0];如果我们要写数据到缓冲区时,先要clear()[其实调用clear(),相当于position=0,limit=capacity=12]。
-
掌握上面的几个方法对读写操作基本是没什么问题了。但是仅仅是对读写操作掌握了是不够的。下面来了解一下其他的方法:
(1)slice()
从上面的两个图中可以看出,调用了slice()相当于新建一个缓冲区,而且这个缓冲区的Capacity = limit-position;position=0; - (2)compact():position = capacity-position ;limit=capacity
- (3)duplicate():新建一个与之相同的缓冲区
- (4)rewind():position=0
在这里我想缓冲区的概念应该了解得差不多了。
(三)、何为通道(Channel)?
- 从字面上可以理解为传输数据的通道。其实通道就是一种途径,连接到外设或者文件等等的一种途径。通道和流的几个不同点:1、通道可以同时读写而流要不就是读要不就是写 ;2、通道可以进行异步读写;3、通道总是通过缓冲区来进行读写;通道与流最大的区别在于,通道可以是双向的,而流只能是单向。Channel是一个接口,从JDK文档中可以看出,它只有两个方法:isOpen()、close()。读写是输入输出中最基本的过程,从一个通道中读取数据只能通过缓冲区,写数据进通道也只能通过缓冲区,所以在nio中,通道和缓冲区是人和自己影子的关系,永不分离。Channel的几个常用的比较重要的实现类有:
FileChannel:主要是应用于文件的读写
SocketChannel:主要对基于TCP协议的网络数据的读写
ServerSocketChannel:主要是监听到新的连接创建一个SocketChannel
DatagramChannel:主要对基于UDP协议的网络数据的读写
(1)、NIO对文件的操作:文件通道(FileChannel)
- 读取三步走:
- ①、从FileInputStream获取Channel
- ②、创建Buffer
- ③将数据读取到buffer中 写入三步走和读取三步走差不多。
- 先看一个例子吧:(这个例子很简单,主要是加深了解上面的ByteBuffer和Channel)
public class NIOFileOperate { public static void main(String[] args) { try { FileInputStream input = new FileInputStream(new File( "D:\\soft\\java\\netbeans-6.9-ml-windows.exe")); //得到channel FileChannel fc1 = input.getChannel(); FileOutputStream out = new FileOutputStream(new File( "e://netbeans-6.9-ml-windows.exe")); FileChannel fc2 = out.getChannel(); //直接创建缓冲区 ByteBuffer buffer = ByteBuffer.allocateDirect(1024 * 100); while (fc1.read(buffer) != -1) { //翻转缓冲区 buffer.flip(); //position与capacity之间是否有元素,有则代表有数据可读 if (buffer.hasRemaining()) { fc2.write(buffer); } //重设缓冲区 buffer.clear(); } fc1.close(); fc2.close(); } catch (Exception e) { e.printStackTrace(); } } }
(2)、NIO对网络套接字的操作:
socket通道(SocketChannel、DatagramChannel、ServerSocketChannel)DatagramChannel和SocketChannel都实现了读和写的功能,而ServerSocketChannel只是负责监听网络中传入的连接和创建新的SocketChannel对象,它是不负责传送数据的。
①、 ServerSocketChannel:它只是一个基于通道的socket监听器,它和ServerSocket的作用差不多,只是它多了一个channel,很明显它增加了通道的功能。在JDK文档中我们可以看到它是抽象的,那么怎么创建他的一个对象呢,它有一个open()方法。我们在服务器端常用到下面的一段代码:
//创建一个未绑定的与ServerSocket有关联的服务器通道
ServerSocketChannel ssChannel = ServerSocketChannel.open();
ServerSocket serverSocket = ssChannel.socket();
//绑定IP地址和端口
serverSocket.bind(new InetSocketAddress(8080)); );//设置非阻塞通道,默认为阻塞通道
ssChannel.configureBlocking(false)
②、 SocketChannel:经常用到。在JDK文档中我们可以看到创建SocketChannel的对象的方法有两个:open()、open(InetSocketAddress)。调用finishConnect()方法来完成连接过程。创建一个非阻塞的SocketChannel
socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress(“localhost”,8080));
while(!socketChannel.finishConnect()){
//一直等,直到连接成功
}
(四)、选择器(selector)?
选择器是一个可以检查一个或多个通道,并确定那些通道是准备好的(读或写)的NIO组件。正因为选择器的存在,才使得一个线程可以管理多个通道。下面用图的方式来说明一下selector的作用:
(1)、创建一个Selector
Selector selector = Selector.open();
(2)、把通道注册到选择器
channel.configureBlocking(false);
SelectorKey key = channel.register(selector,SelectionKey.OP_READ)
上面的SelectionKey.OP_READ ,是一个“兴趣集”,其实就是一个事件集。他把你感兴趣的事件注册到选择器。当你感兴趣的事件发生在通道中,Selector会捕获到。SelectKey的四个主要的常量:
1. SelectionKey.OP_CONNECT
2. SelectionKey.OP_ACCEPT
3. SelectionKey.OP_READ
4. SelectionKey.OP_WRITE
如果多于一个事件要被注册呢,那就用‘|’号把两个事件连在一起。
如: SelectionKey.OP_READ | SelectionKey.OP_WRITE
下面是常用到的代码段:
Set<SelectionKey> selectedkeys = selector.selectorKeys(); Iterator<SelectorKey> it = selectedKeys.iterator(); While(it.hasNext()){ SelectionKey key = it.next(); if(key.isAcceptable()){ //代表是一个新连接 }else if(key.isReadable()){ //通道的读事件 }else if(key.isWritable()){ //通道的写事件 } it.remove();//删除处理过的事件 }
下面以一个完整的例子来总结上面的缓冲区+通道+选择器:
Selector管理多管道 (当客户端发来数据时,服务器端把收到的数据发回客户端)
public class ServerTest { private final static int PORT = 1111; private ServerSocketChannel serverChannel; private ServerSocket socket; private Selector selector; private InetSocketAddress socketAddr; private ByteBuffer buffer = ByteBuffer.allocateDirect(1024); public ServerTest() { try { //创建一个新的选择器 selector = Selector.open(); //分配一个未绑定的服务器套接字通道 serverChannel = ServerSocketChannel.open(); //得到与通道相关的socket对象 socket = serverChannel.socket(); socketAddr = new InetSocketAddress(PORT); //将scoket绑定到特定的端口上 socket.bind(socketAddr); //配置通道使用非阻塞模式,在非阻塞模式下,可以编写多道程序同时避免使用复杂的多线程 serverChannel.configureBlocking(false); //把serversocket注册到selector serverChannel.register(selector, SelectionKey.OP_ACCEPT); System.out.println("服务器已经启动!"); while (true) { int num = -1; try { //监控注册在selector上的SelectableChannel num = selector.select(); } catch (IOException e) { e.printStackTrace(); } if (num == 0) { continue; } //selectedKey返回一个SelectionKey的集合,其中每一个SelectionKey代表一个进行IO的channel Iterator<SelectionKey> it = selector.selectedKeys().iterator(); while (it.hasNext()) { SelectionKey key = it.next(); //是否是一个新的连接 if (key.isAcceptable()) { ServerSocketChannel channel = (ServerSocketChannel) key .channel(); SocketChannel socketChannel = null; try { socketChannel = channel.accept(); } catch (IOException e) { e.printStackTrace(); } registerChannel(selector, socketChannel, SelectionKey.OP_READ); } //通道是否有数据要读 if (key.isReadable()) { try { readDataFromSocket(key); } catch (Exception e) { e.printStackTrace(); } } } it.remove(); } } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { new ServerTest(); } protected void readDataFromSocket(SelectionKey key) throws Exception { SocketChannel socketChannel = (SocketChannel) key.channel(); int count; buffer.clear(); while ((count = socketChannel.read(buffer)) > 0) { buffer.flip(); //判断是否有新的数据,有数据就把它写回通道中 while (buffer.hasRemaining()) { socketChannel.write(buffer); } buffer.clear(); } if (count < 0) { System.out.println("Close!"); socketChannel.close(); } System.out.println("read end"); } protected void registerChannel(Selector selector, SelectableChannel channel, int ops) { if (channel == null) { return; } try { channel.configureBlocking(false); channel.register(selector, ops); } catch (Exception e) { e.printStackTrace(); } } }
相关推荐
[第4节] JavaNIO流-通道1.flv [第5节] Java NIO流-通道2.flv [第6节] Java NIO流-socket通道操作.flv [第7节] Java NIO流-文件通道操作.flv [第8节] Java NIO流-选择器 .flv [第9节] Java NIO流-选择器操作.flv...
在Java NIO流篇1的学习中,我们将深入探讨NIO的核心概念和使用方法。 首先,我们从【第1节】 Java NIO流-简介和概述开始。这一部分主要介绍了NIO的基本理念和相对于BIO的优势。NIO的核心特点是它支持非阻塞I/O,这...
Java NIO(New IO)是Java 1.4版本引入的一个新模块,是对传统IO模型的补充和扩展。本教程将深入讲解Java NIO中的流和通道概念,以帮助开发者更好地理解和利用这一强大的功能。 首先,我们要理解Java NIO的核心组件...
Java的Socket编程和NIO(非阻塞I/O)可以构建可靠的通信机制。 6. **数据存储**:游戏往往需要持久化数据,如用户进度、成就等。学习如何使用数据库(如H2、SQLite)或者文件系统来存储和读取数据。 7. **性能优化...
Java语言基础教程-Java NIO流篇.txt 网盘永久链接 为方便java nio学习爱好者而上传
在本篇文章中,我们将深入探讨Java NIO如何读取文件。 一、NIO的基本概念 1. 缓冲区(Buffer):NIO的核心组件,用于存储数据。Java提供了多种Buffer类,如ByteBuffer、CharBuffer、IntBuffer等,分别对应不同数据...
本篇文章将深入探讨如何使用Java NIO(非阻塞I/O)来实现阻塞多线程通信,这对于高性能服务器端应用尤其重要。我们将会分析`EchoServer.java`、`EchoClient.java`和`SocketUtils.java`这三个文件中的关键知识点。 ...
Java NIO(New IO)是Java 1.4版本引入的一个新特性,是对传统IO模型的补充和扩展,提供了一种更高效的数据处理方式。在本教程中,我们将深入探讨Java NIO流的两个关键部分:文件通道操作和选择器。 ### 文件通道...
### Java NIO 核心概念详解 #### 一、Java NIO 基本介绍 Java NIO(New IO 或 NonBlocking IO)是 Java 1.4 版本开始引入的一种全新的 I/O API,旨在提高 I/O 吞吐量。与传统的阻塞 I/O 相比,NIO 的设计思想更为...
本篇文章将深入探讨Java NIO的基本概念、核心组件以及实际应用。 一、Java NIO概述 传统的Java I/O基于流(Stream)和缓冲区(Buffer)的模型,是阻塞式的,即在进行读写操作时会一直等待数据准备好或全部写入完成。而...
"Java Learning Path---资源篇"这个压缩包文件,很可能是提供了一系列有关Java学习的资料,包括但不限于教程、代码示例、实战项目和社区链接等。下面,我们将详细探讨Java学习中的关键知识点,并推荐一些可能在资源...
在本套"JAVA学习资料-高级篇"中,我们聚焦于Java编程的进阶主题,旨在帮助有经验的JAVA开发人员提升技能,深入理解Java平台的精髓。这份资源包含了一系列深度探讨Java特性和最佳实践的材料,对于那些已经在Java开发...
Java技能百练——网络篇是针对Java开发者在网络编程方面的深入学习和实践。在这个主题中,我们将探讨Java在处理网络通信、数据传输以及网络服务开发等多个关键领域的应用。下面,我们将详细解析Java网络编程的一些...
本资源包“Java项目开发实践---网络篇”涵盖了Java进行网络通信的基础与实践,旨在帮助开发者深入理解并掌握相关知识。 1. **Java网络编程基础** Java提供了丰富的API来处理网络通信,主要集中在`java.net`包下。...
本篇文章将深入探讨如何在Java NIO中使用Selector处理客户端的I/O请求。 首先,我们需要理解Selector的工作原理。Selector是一个多路复用器,它可以监控多个通道的事件状态,如连接就绪、数据可读或可写等。通过...
本篇将详细探讨Java NIO在写文件方面的应用。 1. **通道(Channels)** 在Java NIO中,数据的读取和写入都是通过通道(Channel)进行的。通道可以理解为连接到I/O设备的桥梁,例如文件、网络套接字等。要使用NIO写...
Java IO与NIO是Java平台中用于处理输入输出操作的核心技术。它们在处理数据传输、文件操作、网络通信等方面起着至关重要的作用。本篇将深入探讨这两个领域,旨在帮助开发者更好地理解和应用这些概念。 首先,Java ...