IO是Java中的一种输入和输出的功能,Java中对这种操作叫做对流的操作。
流代表的是任何有能力产出数据的数据源对象或者是有能力接受数据的接收端对象。
流的本质是数据传输,流不只是对文件可进行读写,还可以对内存、网络、程序操作。
NIO是同步的IO,是因为程序需要IO操作时,必须获得了IO权限后亲自进行IO操作才能进行下一步操作。AIO是对NIO的改进(所以AIO又叫NIO.2),它是基于Proactor模型的。每个socket连接在事件分离器注册 IO完成事件 和 IO完成事件处理器。程序需要进行IO时,向分离器发出IO请求并把所用的Buffer区域告知分离器,分离器通知操作系统进行IO操作,操作系统自己不断尝试获取IO权限并进行IO操作(数据保存在Buffer区),操作完成后通知分离器;分离器检测到 IO完成事件,则激活 IO完成事件处理器,处理器会通知程序说“IO已完成”,程序知道后就直接从Buffer区进行数据的读写。
也就是说:AIO是发出IO请求后,由操作系统自己去获取IO权限并进行IO操作;NIO则是发出IO请求后,由线程不断尝试获取IO权限,获取到后通知应用程序自己进行IO操作。 同步/异步:数据如果尚未就绪,是否需要等待数据结果。 阻塞/非阻塞:进程/线程需要操作的数据如果尚未就绪,是否妨碍了当前进程/线程的后续操作。应用程序的调用是否立即返回! NIO与BIO最大的区别是 BIO是面向流的,而NIO是面向Buffer的。
Buffer是一块连续的内存块,是 NIO 数据读或写的中转地。 为什么说NIO是基于缓冲区的IO方式呢?因为,当一个链接建立完成后,IO的数据未必会马上到达,为了当数据到达时能够正确完成IO操作,在BIO(阻塞IO)中,等待IO的线程必须被阻塞,以全天候地执行IO操作。为了解决这种IO方式低效的问题,引入了缓冲区的概念,当数据到达时,可以预先被写入缓冲区,再由缓冲区交给线程,因此线程无需阻塞地等待IO。
缓冲区实际上是一个容器对象,更直接的说,其实就是一个数组,在NIO 库中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的; 在写入数据时,它也是写入到缓冲区中的;任何时候访问NIO 中的数据,都是将它放到缓冲区中。而在面向流I/O 系统中,所有数据都是直接写入或者直接将数据读取到Stream 对象中。在NIO 中,所有的缓冲区类型都继承于抽象类Buffer,最常用的就是ByteBuffer
整个Java IO包中最重要的就是5个类和一个接口。
5个类指:
* File:用于文件或者目录的描述信息,例如生成新的目录,修改文件名,删除文件,判断文件,过滤文件等
* OutputStream:抽象类,基于字节的输出操作,是所有输出流的父类。
* InputStream:抽象类,基于字节的输入操作,是所有输入流的父类。
* Writer:抽象类,基于字符的输出操作。
* Reader:抽象类,基于字符的输入操作。
一个接口指:Serializable
另外一个特殊的类:RandomAccessFile:随机文件操作,可以从文件任意位置进行存取(输入输出)操作。
IO接口和类的结构图可参考技术栈图
我们在对文件的操作过程中,除了使用字节流和字符流的方式之外,我们还可以使用RandomAcessFile这个工具类来实现。
RandomAccessFile可以实现对文件的读 和 写,但是他并不是继承于以上4中基本虚拟类。
而且在对文件的操作中,RandomAccessFile有一个巨大的优势,他可以支持文件的随机访问,程序快可以直接跳转到文件的任意地方来读写数据。所以如果需要访问文件的部分内容,而不是把文件从头读到尾,使用RandomAccessFile将是更好的选择。
RandomAccessFile的方法虽然多,但它有一个最大的局限,就是只能读写文件,不能读写其他IO节点。
RandomAccessFile的一个重要使用场景就是网络请求中的多线程下载及断点续传。
Java中有输入和输出两种IO流,每种输入输出流又分为字节流和字符流两大类。
- 关于字节:每个字节(byte)有8bit组成
- 关于字符:一个字符代表一个英文字母或一个汉字
Java采用unicode编码,2个字节表示1个字符
- 先进先出,最先写入输出流的数据最先被输入流读取到
- 顺序读取,不能随机访问数据(RandomAccessFile除外)
- 只读只写,每个流只能是输入流或输出流的一种
- 每次进行IO操作,要手动close,因为IO资源并不属于内存资源,并不会被GC回收
- 对于输出操作,flush()会刷新输出流,强制缓冲区中的输出字节被写出; close()关闭输出流,释放和这个流相关的系统资源,调用close()会自动flush
- 流结束的判断:方法read()的返回值为-1时;readLine()的返回值为null时
- 节流没有缓冲区,是直接输出的,而字符流是输出到缓冲区的。因此在输出时,字节流不调用colse()方法时,信息已经输出了,而字符流只有在调用close()方法关闭缓冲区时,信息才输出。要想字符流在未关闭时输出信息,则需要手动调用flush()方法
- 字节流与字符流区别
- 字节流以字节(8bit)为单位,字符流以字符为单位,根据码表映射字符,一次可能读多个字节
- 字节流能处理所有类型的数据(如图片、avi等),而字符流只能处理字符类型的数据
- 只要是处理纯文本数据,就优先考虑使用字符流。除此之外都使用字节流
传统的 Socket 阻塞模式直接导致每个 Socket 都必须绑定一个线程来操作数据,参与通信的任意一方如果处理数据的速度较慢,则都会直接拖累另一方,导致另一方的线程不得不浪费大量的时间在 I/O 等待上,所以,每个 Socket 要绑定一个单独的线程正是传统Socket 阻塞模式的根本“缺陷”。之所以这里加了“缺陷”两个字,是因为这种模式在一些特定场合下效果是最好的,比如只有少量的 TCP 连接通信,双方都非常快速地传输数据,此时这种模式的性能最高。
现在我们可以开始分析“非阻塞”模式了,它就是要解决 I/O 线程与 Socket 解耦的问题,因此,它引入了事件机制来达到解耦的目的。我们可以认为 NIO 底层中存在一个 I/O 调度线程,它不断扫描每个 Socket 的缓冲区,当发现写入缓冲区为空(或者不满)的时候,它会产生一个Socket 可写事件,此时程序就可以把数据写入 Socket 里,如果一次写不完,则等待下次可写事件的通知;而当发现读取缓冲区里有数据的时候,它会产生一个 Socket 可读事件,程序收到这个通知事件时,就可以从 Socket 读取数据了。