昨天面试问到了有关Java NIO的问题,没有答上来。于是,在网上看到了一篇很有用的系列文章讲Java IO的,浅显易懂。后面的备注里有该系列文章的链接。内容不算很长,需要两个小时肯定看完了,将该系列文章看完之后,我又参看了一些其他的资料,形成了一点自己不成熟的理解,将之记录下来,算是我自己学到的一点东西吧!有什么问题,希望大神能够不吝赐教。

IO 的分类?

我自己的观点,IO大致可以分为本地IO(磁盘IO)和网络IO。所谓的本地IO,即本地的应用程序从磁盘里面读写数据,不涉及网络的概念;而网络IO则是发送端发送数据(写),接收端接收数据(读),当然,也不一定是发送端只能发送数据,接收端只能接收数据,如果是基于TCP/IP协议的话,可以一边发一边收,因为它是全双工的协议。

磁盘IO

我们知道应用程序访问物理设备只能通过系统调用的方式来工作的,读写分别用read()和write()来操作。

而系统调用就存在用户空间内核空间的问题。

为什么要分成这两个空间呢?因为操作系统要保证应用程序不能随便修改内核的数据,是为了安全起见。不然,你随便写一个能修改内核数据的app,有可能导致系统崩溃。

分开后,就涉及到了一个复制的问题。

下面介绍几种常用的访问文件的方式:

标准访问文件的方式

以读取数据为例:

应用程序---系统调用-read()--->应用缓存(用户地址空间)--->高速缓存(内核地址空间)---->物理磁盘

写入数据的过程也类似。

因为用户空间和内核空间都有缓存,所以无论是读入数据还是写入数据,都要先经过缓存这一步。比如读的时候,先要检查缓存是否有,没有才从磁盘中读;写的时候,要先写到缓存中,如果不是显式地调用sync同步命令,什么时候写入磁盘就要看操作系统了。

缺点:隔离用户空间和地址空间是为了保证系统安全,但也增加了复制的麻烦,比如写入数据的时候,先要写入应用缓存中,然后再复制到内核空间里面的告诉缓存中,最后才是磁盘。

直接IO方式

以读取数据为例:

应用程序---系统调用-read()--->磁盘

很明显,少了一个经过内核空间的步骤。

这种访问方式的应用场合:

比如,数据库管理系统,系统明确知道应该缓存哪些数据,应该失效哪些数据,还可以将一些数据做预加载,这样可以加速数据的访问效率;但是如果由操作系统来负责缓存,则很难做到。

缺点:访问的数据不在应用缓存中,直接从磁盘加载的速度非常缓慢。

同步访问文件的方式

数据的读取和写入同步,只有当数据被成功写入到磁盘时才返回给应用程序成功标志。

异步访问文件的方式

访问数据的线程发出请求之后,线程接着去处理其他事情,而不是阻塞等待。

内存映射方式

内存中的某一块区域与磁盘中的文件关联起来的,当要访问内存中的一段数据时,转换为访问文件的某一段数据,这种方式同样可以减少数据从内核空间缓存到用户空间缓存的数据复制操作。因为这样两个空间的数据是共享的

Java访问磁盘文件的接口

1.Java里面的四组IO接口:

基于字节操作的IO接口:InputStream 和 OutputStream

基于字符操作的IO接口:Writer和 Reader

此处字符和字节一定要搞清楚,上次电话面试就弄错了,汗。。。。。

字节IO流向字符IO流转换只需要一个装饰类即可,关于此处的细节,会在装饰器模式的Java IO流部分讲到。。

基于磁盘操作的IO接口:File

基于网络操作的IO接口: Socket

2.Java访问磁盘文件的方式

需要知道一句话:数据在磁盘中的唯一最小描述就是文件,也就是说上层应用程序只能通过文件来操作磁盘上的数据,文件也是操作系统与磁盘驱动交互的最小单元。

下面是一个Java从磁盘中读取文件的过程:

start--->传入文件路径--->创建File对象来标识此文件---->创建StreamDecoder解码对象(由该对象将输入流解码为字符流还是字节流)--->创建FileInputStream对象(

这个才是真正读取文件的对象,同时创建磁盘文件的文件描述符 FileDescriptor,此对象负责控制这个磁盘文件)--->over

Java Socket

在Java里面通过Socket实现通信的过程:

1.建立通信链路。

客户端:

创建一个Socket实例,同时操作系统将为这个实例分配一个没有被使用的本地端口号,并创建一个包含本地地址,远程地址,端口号的socket数据结构。这个数据结构将一直保存在系统中直到这个连接关闭。

需要注意的是:在创建的Socket实例的构造函数正确返回之前,需要进行三次握手,握手完成,Socket实例对象创建完成,否则报错。

服务端:

服务端创建一个ServerSocket实例(只要端口号没有被占用,实例都会被创建成功。)同时操作系统也会为ServerSocket实例创建一个底层数据结构,这个数据结构包含指定监听端口的端口号,和监听地址的通配符,通常为“*”,即监听所有地址。

然后调用accept方法进入阻塞状态,等待客户端请求。当一个新的请求时,将为这个连接创建一个新的套接字数据结构(此数据结构包括请求源地址和端口),之后这个新的数据结构会关联到ServerSocket实例的一个未完成的连接数据结构列表中。

再次注意:与上面客户端创建Socket实例过程一样,必须在三次握手之后才能创建真正的实例,然后将关联的数据结构从未完成列表移到已完成列表中。

2.传输数据

通过第一步,创建了两个Socket实例,每个实例都有一个InputStream和OutputStream。这两个实例都可以通过getInputStream()方法和getOutputStream()方法得到。

创建Socket对象时,操作系统将为InputStream和OutputStream分配一定大小的缓存区,此缓存区用于数据的读取和写入。

写入端将数据写到OutputStream对应的SendQ队列中,当队列满时,开始向另一端InputStream的RecvQ队列中发送。如果接收端的RecvQ已满,则输入端的write方法阻塞,直到接收端的Recv队列有空闲,然后开始继续发送。

需要注意的是:如果两边同时开始发送数据怎么办?

这时候,就需要一个协调机制,来防止发生死锁。这个机制暂且不提,有机会再完成更新。

但是NIO则没有这种死锁情况的发生。

网络IO

Java NIO是什么?

我们所熟知的IO方式就是通过InputStream/OutputStream, InputStreamReader/OutputStreamWriter的方式从磁盘里面读取数据,或者向磁盘写数据。

关于本地磁盘读取文件前面已有详细描述,此处不表。

Java NIO用一个老外的话来说就是:

In the standard IO API you work with byte streams and character streams. In NIO you work with channels and buffers.Data is always read from a channel into a buffer, or written from a buffer to a channel.

NIO,即 Non-blocking IO,非阻塞式的IO,上面讲的IO都是阻塞式的。什么是阻塞式的?就是你如果要从磁盘上读取一个文件,在读的过程中你不能干其他事,只有等待它读取完毕,你才能干后面的事情;而非阻塞式,显然,就可以在读取文件的过程中,做别的事情。

NIO的核心概念

上面提到两个概念,channel, buffer,还有一个selector。这三个构成了NIO的核心。

在NIO里面所有的IO都从一个channel开始,channel有点像Stream,但是还是有很多细微的区别的,在下面会被提到。数据可以从channel读到一个buffer中,也可以从buffer写入到channel里面。

在Java NIO里面有以下四种channel:

	FileChannel---对文件进行读写数据

	DatagramChannel-----通过UDP读写数据

	SocketChannel------通过TCP读写数据

	ServerSocketChannel----服务端监听TCP连接

这些channel涵盖了UDP+TCP,网络IO和文件IO。

Java NIO里面的buffer:

	ByteBuffer

	CharBuffer

	DoubleBuffer

	FloatBuffer

	IntBuffer

	LongBuffer

	ShortBuffer

也就是每种基本类型都有相应的Buffer。

一个Selector允许一个单线程操作多个Channel。但是前提是你必须在相应的Channel上注册了这个Selector。然后你调用select方法,如果在channel上没有一个事件是准备好,那么这个方法将被阻塞。

NIO Channel和Streams的区别:

1.你可以对Channel进行读和写,但是Stream通常只能读,或者只能写。

2.Channel可以读写异步进行。

3.Channel总是从Buffer中读数据,或者向Buffer中写数据。

一个使用Channel的例子:

 	RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel(); ByteBuffer buf = ByteBuffer.allocate(48); int bytesRead = inChannel.read(buf);
while (bytesRead != -1) { System.out.println("Read " + bytesRead);
buf.flip(); while(buf.hasRemaining()){
System.out.print((char) buf.get());
} buf.clear();
bytesRead = inChannel.read(buf);
}
aFile.close();

通过Buffer读写数据一般要遵循下面四步:

写入Buffer

调用buffer.flip()

将Buffer里面的数据读尽

调用方法buffer.clear()/buffer.compact()

当你将数据写入Buffer时,Buffer将记录你写入数据的数量,如果你想从Buffer中读取data的话,则需要调用flip()方法将Buffer从写入模式转换成读取模式。当你将数据读完时,需要调用clear()方法将Buffer清理干净(或者也可以用compact方法,它只将你读取数据的那一部分清理干净)。

Buffer有3个参数:Capacity,Position,Limit.

Capacity:顾名思义,Buffer的容量。

Position:当你向Buffer写入数据的时候,需要一个位置指示,这个指示就是Position,初始化为0.

Limit:在写模式下,意思是你最多可以向Buffer写入多少数据;在读模式下,意思是你可以最多从Buffer中读取多少数据。

怎么分配Buffer?

	ByteBuffer buf = ByteBuffer.allocate(48);//分配48字节的Buffer

	CharBuffer buf = CharBuffer.allocate(1024);//分配1024字节的Buffer

下面讲讲Selector:

使用Selector的一大优点是你仅仅利用一个单线程就可以操纵多个Channel。因为线程的上下文切换是很消耗资源的。所以线程当然越少越好。

创建一个Selector:

Selector selector = Selector.open();

向Channel注册一个Selector:

channel.configureBlocking(false);//注意这里先要将Channel配置成为非阻塞的
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);

register的第二个参数,意思是你对这个Channel感兴趣的事件是什么或者通过Selector监听什么事件。

下面是监听的不同事件的种类和相应的表示:

Connect----SelectionKey.OP_CONNECT

Accept-----SelectionKey.OP_ACCEPT

Read-------SelectionKey.OP_READ

Write------SelectionKey.OP_WRITE

NIO VS IO

上面讲的其实还不是很细致,有必要找一本Java网络编程的书来看看。这里只讲了一些大概,知道具体是怎么回事,但是更多的细节,无奈水平所限,只能等到将来完善了。

下面对NIO和 IO进行一些比较,进而让我们知道,什么时候用IO好一些什么时候用NIO好一点。

NIO和IO最主要的区别就是:NIO是面向Stream的,而NIO是面向Buffer的。这句话是什么意思呢?

面向Stream,意思是你一次从一个Stream里面读取一个或者多个Bytes,你对这些Bytes做什么,将由你来决定。而且这些Bytes没有缓存在任何地方,所以你不能将这些data随意前后移动。

面向Buffer,意思是你可以将数据读取到一个Buffer里面之后进行处理。如果有必要的话,还可以将这些数据前后移动,这给你带来很大的灵活性。但是,你也不许检查来确保Buffer里面是否已经包含了所有你想要的数据,同时再次读取数据的时候,不会覆盖你尚未处理的数据。

NIO是非阻塞式的,而IO是阻塞式的,区别前面已经介绍过。

NIO和IO各有什么适合的应用场景呢?

如果你需要同时处理成千上万的打开的连接,但是每个连接可能只发送一小部分数据,例如一个聊天软件的服务器,NIO会是一个不错的选择;如果你在带宽非常高的情况下,只有数量很小的连接,并且一次发送大量的数据,传统的IO或许更适合。

备注

这里有一个链接,是一个系列教程,是老外写的讲NIO的。http://tutorials.jenkov.com/java-nio/index.html

还有一个网站专门讲Java并发编程的网站http://ifeve.com/category/talk-concurrent/

Java IO 和 NIO的更多相关文章

  1. 如何解读 Java IO、NIO 中的同步阻塞与同步非阻塞?

    原文链接:如何解读 Java IO.NIO 中的同步阻塞与同步非阻塞? 一.前言 最近刚读完一本书:<Netty.Zookeeper.Redis 并发实战>,个人觉得 Netty 部分是写 ...

  2. java IO和NIO 的区别

    Java NIO和IO的主要区别 下表总结了Java NIO和IO之间的主要差别. IO                NIO 面向流            面向缓冲 阻塞IO           非 ...

  3. 漫谈Java IO之 NIO那些事儿

    前面一篇中已经介绍了基本IO的使用以及最简单的阻塞服务器的例子,本篇就来介绍下NIO的相关内容,前面的分享可以参考目录: 网络IO的基本知识与概念 普通IO以及BIO服务器 NIO的使用与服务器Hel ...

  4. Java IO、NIO、AIO知识总结

    本文主要讲述下自己对IO的理解,对IO的用法和细则可能没有顾虑到. 本文的理解基于以下几篇文章,他们对各自部分都讲的很细,对我理解IO提供了很大帮助. https://www.cnblogs.com/ ...

  5. java IO和NIO区别

    面向流与面向缓冲 Java NIO和IO之间第一个最大的区别是,IO是面向流的,NIO是面向缓冲区的. Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方 ...

  6. 关于Java IO与NIO知识都在这里

    由于内容比较多,我下面放的一部分是我更新在我的微信公众号上的链接,微信排版比较好看,更加利于阅读.每一篇文章下面我都把文章的主要内容给列出来了,便于大家学习与回顾. Java面试通关手册(Java学习 ...

  7. java IO、NIO、AIO详解

    概述 在我们学习Java的IO流之前,我们都要了解几个关键词 同步与异步(synchronous/asynchronous):同步是一种可靠的有序运行机制,当我们进行同步操作时,后续的任务是等待当前调 ...

  8. Java IO 与 NIO 服务器&客户端通信小栗子

    本篇包含了入门小栗子以及一些问题的思考 BIO package com.demo.bio; import java.io.*; import java.net.ServerSocket; import ...

  9. Java IO与NIO的总结、比较

    一.IO流总结 1.Java I/O主要包括如下3层次: 流式部分——最主要的部分.如:OutputStream.InputStream.Writer.Reader等 非流式部分——如:File类.R ...

随机推荐

  1. ExtJS 饼状图报表

    简单的ExtJS饼状图报表. 先上源码,咱再慢慢解析: Ext.onReady(function(){ var store = Ext.create('Ext.data.JsonStore', { f ...

  2. XMLHttpRequest基础知识

    XMLHttpRequest 发送请求的两个重要方法:open(method,url,async)——参数:请求方式.请求地址.请求同步/异步:send(string)——参数:使用POST方式时,填 ...

  3. 写jQuery插件时,一种更好的合并参数的方法

    看到很多人写jQuery插件时居然这样合并参数: this.defaults = { 'color': 'red', 'fontSize': '12px', 'textDecoration':'non ...

  4. ruby更换为淘宝的源

    我们在使用gem更新的时候,经常会为速度抓狂,其实gem默认的源是https://rubygems.org,比较慢众所周至的原因了. 可以将源更换到国内的taobao源 查看当前有的源 gem sou ...

  5. Win7网络检测 WindowsAPICodePack

    原文:http://www.cnblogs.com/yincheng01/archive/2010/05/30/2213234.html 在Windows7操作系统下,支持的网络类型越来越复杂,微软提 ...

  6. 更有效率的使用 Visual Studio - 快捷键

    工欲善其事,必先利其器.虽然说Vim和Emacs是神器,但是对于使用Visual Studio的程序员来说,我们也可以通过一些快捷键和潜在的一些功能实现脱离鼠标写代码,提高工作效率,像使用Vim一样使 ...

  7. 基于方法的LINQ语句

    LINQ中的查询方法有两站,一种是使用类似于SQL语句的方式,另一种则是基于方法的语句.基于方法的查询方法使用的是C#中面向对象概念的,主要的方法有: 投影:  Select | SelectMany ...

  8. JVM命令行选项及GC日志

    转:http://blog.csdn.net/q291611265/article/details/48028189 一.设置参数 在使用eclipse编译器的时候,可以采用以下的运行方式来设置虚拟机 ...

  9. Unique Binary Search Trees 解答

    Question Given n, how many structurally unique BST's (binary search trees) that store values 1...n? ...

  10. 无序线性搜索(Unordered Linear Search)

    假定有一个元素顺序情况不明的数组.这种情况如果我们要搜索一个元素就要遍历整个数组,才能知道这个元素是否在数组中. 这种方法要检查整个数组,核对每个元素.下面是算法实现: #include<std ...