`

Java NIO 01 - 常识篇

阅读更多

         今天发表一篇一年前已经总结好的关于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();
		}
	}
}

 

  • 大小: 69.5 KB
  • 大小: 5.8 KB
  • 大小: 36.8 KB
  • 大小: 68 KB
  • 大小: 44.7 KB
  • 大小: 63.1 KB
分享到:
评论

相关推荐

    Java视频教程 Java游戏服务器端开发 Netty NIO AIO Mina视频教程

    [第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语言基础教程-Java NIO流篇1

    在Java NIO流篇1的学习中,我们将深入探讨NIO的核心概念和使用方法。 首先,我们从【第1节】 Java NIO流-简介和概述开始。这一部分主要介绍了NIO的基本理念和相对于BIO的优势。NIO的核心特点是它支持非阻塞I/O,这...

    Java语言基础教程-Java NIO流篇2

    Java NIO(New IO)是Java 1.4版本引入的一个新模块,是对传统IO模型的补充和扩展。本教程将深入讲解Java NIO中的流和通道概念,以帮助开发者更好地理解和利用这一强大的功能。 首先,我们要理解Java NIO的核心组件...

    java技能百练--游戏篇

    Java的Socket编程和NIO(非阻塞I/O)可以构建可靠的通信机制。 6. **数据存储**:游戏往往需要持久化数据,如用户进度、成就等。学习如何使用数据库(如H2、SQLite)或者文件系统来存储和读取数据。 7. **性能优化...

    Java语言基础教程-Java NIO流篇.txt

    Java语言基础教程-Java NIO流篇.txt 网盘永久链接 为方便java nio学习爱好者而上传

    java nio 读文件

    在本篇文章中,我们将深入探讨Java NIO如何读取文件。 一、NIO的基本概念 1. 缓冲区(Buffer):NIO的核心组件,用于存储数据。Java提供了多种Buffer类,如ByteBuffer、CharBuffer、IntBuffer等,分别对应不同数据...

    Java Socket学习---nio实现阻塞多线程通信

    本篇文章将深入探讨如何使用Java NIO(非阻塞I/O)来实现阻塞多线程通信,这对于高性能服务器端应用尤其重要。我们将会分析`EchoServer.java`、`EchoClient.java`和`SocketUtils.java`这三个文件中的关键知识点。 ...

    Java语言基础教程-Java NIO流篇3

    Java NIO(New IO)是Java 1.4版本引入的一个新特性,是对传统IO模型的补充和扩展,提供了一种更高效的数据处理方式。在本教程中,我们将深入探讨Java NIO流的两个关键部分:文件通道操作和选择器。 ### 文件通道...

    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入门学习,两个pdf

    本篇文章将深入探讨Java NIO的基本概念、核心组件以及实际应用。 一、Java NIO概述 传统的Java I/O基于流(Stream)和缓冲区(Buffer)的模型,是阻塞式的,即在进行读写操作时会一直等待数据准备好或全部写入完成。而...

    Java Learning Path---资源篇

    "Java Learning Path---资源篇"这个压缩包文件,很可能是提供了一系列有关Java学习的资料,包括但不限于教程、代码示例、实战项目和社区链接等。下面,我们将详细探讨Java学习中的关键知识点,并推荐一些可能在资源...

    JAVA学习资料-高级篇

    在本套"JAVA学习资料-高级篇"中,我们聚焦于Java编程的进阶主题,旨在帮助有经验的JAVA开发人员提升技能,深入理解Java平台的精髓。这份资源包含了一系列深度探讨Java特性和最佳实践的材料,对于那些已经在Java开发...

    java技能百练--网络篇

    Java技能百练——网络篇是针对Java开发者在网络编程方面的深入学习和实践。在这个主题中,我们将探讨Java在处理网络通信、数据传输以及网络服务开发等多个关键领域的应用。下面,我们将详细解析Java网络编程的一些...

    Java项目开发实践---网络篇

    本资源包“Java项目开发实践---网络篇”涵盖了Java进行网络通信的基础与实践,旨在帮助开发者深入理解并掌握相关知识。 1. **Java网络编程基础** Java提供了丰富的API来处理网络通信,主要集中在`java.net`包下。...

    java nio Selector的使用-客户端

    本篇文章将深入探讨如何在Java NIO中使用Selector处理客户端的I/O请求。 首先,我们需要理解Selector的工作原理。Selector是一个多路复用器,它可以监控多个通道的事件状态,如连接就绪、数据可读或可写等。通过...

    java nio 写文件

    本篇将详细探讨Java NIO在写文件方面的应用。 1. **通道(Channels)** 在Java NIO中,数据的读取和写入都是通过通道(Channel)进行的。通道可以理解为连接到I/O设备的桥梁,例如文件、网络套接字等。要使用NIO写...

    Java IO与NIO文档

    Java IO与NIO是Java平台中用于处理输入输出操作的核心技术。它们在处理数据传输、文件操作、网络通信等方面起着至关重要的作用。本篇将深入探讨这两个领域,旨在帮助开发者更好地理解和应用这些概念。 首先,Java ...

Global site tag (gtag.js) - Google Analytics