对于io流其实一直在使用,但是其中原理不是很熟悉,至于能干什么,概念是比较熟悉的,但是深入了解还是欠缺,于是我开始整理这部门整体话知识,进行了一次调研;
整体架构:
先说一下io流是什么整体概念
Java的IO流是实现输入/输出的基础,在Java中把不同的输入/ 输出源抽象表述为"流"。流是一组有顺序的,有起点和终点的字节集合,流有输入和输出,输入时是流从数据源流向程序。输出时是流从程序传向数据源
,而数据源可以是内存
,文件
,网络或程序
等。
能做什么: 数据在两设备间的传输称为流,流的本质是数据传输
,根据数据传输特性将流抽象为各种类
输入流:只能从中读取数据,而不能向其写入数据
输出流:只能向其写入数据,而不能从中读取数据
字节流和字符流和用法几乎完全一样,区别在于字节流和字符流所操作的数据单元不同。字节流能处理所有类型的数据(如图片、avi等),而字符流只能处理字符类型的数据。只要是处理纯文本数据,就优先考虑使用字符流。 除此之外都使用字节流。
可以向一个特定的IO设备(如磁盘、网络)读/写数据的流,称为节点流,节点流也被成为低级流。
处理流是对一个已存在的流进行连接或封装,通过封装后的流来实现数据读/写功能,处理流也被称为
高级流。
//节点流,直接传入的参数是IO设备
FileInputStream fis = new FileInputStream("test.txt");
//处理流,直接传入的参数是流对象
BufferedInputStream bis = new BufferedInputStream(fis);
Java NIO(New I/O)是在Java 1.4版本
中引入的新的输入输出API,用于替代传统的Java IO(java.io)API。Java NIO提供了更高级、更灵活的I/O操作方式,以应对日益增长的网络和并发需求。
在Java 1.4之前,Java IO是使用阻塞式IO模型,即当一个IO操作发生时,程序会被阻塞,直到该操作完成。这种模型在处理大量并发连接时存在性能和可伸缩性的问题。
为了解决这些问题,Java NIO引入了非阻塞式IO模型,通过使用通道(Channel)和缓冲区(Buffer)来进行数据的读取和写入。通道可以同时处理多个连接,而不需要为每个连接创建一个线程,从而大大提高了性能和可伸缩性。
Java NIO还引入了选择器(Selector)的概念,用于实现多路复用
,即同时监控多个通道的事件
。选择器可以高效地检测到有事件发生的通道,并及时进行处理,而不需要通过轮询来判断通道的状态变化。
Java NIO的引入使得Java在处理网络、并发和高性能应用方面有了更好的支持。它在大规模的网络服务、高并发的Web服务器、分布式系统等领域得到了广泛的应用。
需要注意的是,虽然Java NIO在性能和可伸缩性方面带来了很大的改进,但它的编程模型和API相对复杂,对开发人员的要求也更高。因此,在使用Java NIO时需要仔细理解其概念和原理,并根据具体的需求和场景来进行合理的选择和使用。
NIO的核心组件是通道(Channel)
和缓冲区(Buffer)
。通道是对底层IO设备(如文件、套接字)的一个抽象,可以通过通道进行数据的读写操作。缓冲区是一个固定大小的内存块
,可以在通道和应用程序之间传输数据。
NIO的特点包括:
非阻塞IO:NIO可以使用单线程处理多个通道的IO操作
,通过使用选择器(Selector)
来监听多个通道的事件状态,从而实现非阻塞IO
。
内存映射文件:NIO提供了内存映射文件(MappedByteBuffer)的功能,可以将文件直接映射到内存中,避免了传统IO中频繁的数据复制操作。
选择器:选择器是NIO的核心组件之一,可以通过选择器来监听多个通道的事件状态,从而实现对多个通道的管理。
总之,NIO提供了一种更高效、更灵活的IO方式,适用于处理大量并发连接和高速IO操作的场景。
那么既然NIO是 JDK 1.4自带的那么我们简单实现一个,说一下前提
使用Java NIO,首先需要了解以下几个核心概念和类:
通道(Channel):通道是Java NIO中用于读取和写入数据的抽象。可以通过FileChannel、SocketChannel、ServerSocketChannel等具体实现类来创建通道。
缓冲区(Buffer):缓冲区是Java NIO中用于存储数据的容器。可以使用ByteBuffer、CharBuffer等具体实现类创建不同类型的缓冲区。
选择器(Selector):选择器是Java NIO中用于多路复用非阻塞IO操作的工具。可以使用Selector类创建选择器,并注册通道到选择器上进行监控。
下面是一个简单的示例,演示如何使用Java NIO进行文件读取:
import java.io.FileInputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class NIOExample {
public static void main(String[] args) {
try (FileInputStream fis = new FileInputStream("path/to/input/file");
// 获取通道
FileChannel channel = fis.getChannel()) {
// 创建缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 从通道读取数据到缓冲区
int bytesRead = channel.read(buffer);
while (bytesRead != -1) {
System.out.println("Read " + bytesRead + " bytes");
// 切换缓冲区为读模式
buffer.flip();
// 从缓冲区读取数据
while (buffer.hasRemaining()) {
System.out.print((char) buffer.get());
}
// 清空缓冲区,准备下次读取
buffer.clear();
// 继续从通道读取数据到缓冲区
bytesRead = channel.read(buffer);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
以上示例中,首先创建一个文件输入流和文件通道,然后创建一个缓冲区。通过循环从通道读取数据到缓冲区,再从缓冲区读取数据进行处理。最后,清空缓冲区并继续读取,直到读取完所有数据。
这只是一个简单的示例,Java NIO还有更多的功能和用法,如文件写入、网络通信等。
对比简单的IO框架读写文件 感受一下区别:
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class IOExample {
public static void main(String[] args) {
// 读取文件
try (BufferedReader reader = new BufferedReader(new FileReader("path/to/input/file"))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
// 写入文件
try (BufferedWriter writer = new BufferedWriter(new FileWriter("path/to/output/file"))) {
writer.write("Hello, World!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
以上示例中,通过使用BufferedReader和FileReader来读取文件内容,逐行打印到控制台。通过使用BufferedWriter和FileWriter来写入文件,将字符串"Hello, World!"写入文件。
在使用Java IO读取和写入文件时,需要注意及时关闭文件流以释放资源。使用Java 7的try-with-resources语句可以方便地自动关闭文件流,无需显式调用close()方法。
此外,还可以使用其他Java IO类来实现不同的读写操作,如InputStream、OutputStream、FileInputStream、FileOutputStream等,具体选择根据需求来决定。
注意,Java IO是阻塞式IO模型,当进行IO操作时,程序会被阻塞,直到操作完成。如果需要非阻塞式IO操作,使用Java NIO。因为NIO是多路复用的网络模型架构,那么就涉及到NIO如何实现这种多路复用,其实在组件中已经有了头绪,就是选择器,通道,缓冲区
参考知乎上的答案
- 同步与异步: 同步和异步关注的消息通信机制 synchronous communication / asynchronous communicaton。 所谓的同步就是在调用时候,在没有得到结果之前,该调用的结果不反悔,但是一旦调用了,就有返回值,换句话说,就是由调用者主动等待调用返回结果
- 阻塞和非阻塞:阻塞和非阻塞关注的是程序在等待调用的结果例如消息,和返回值状态。阻塞调用是指调用结果返回之前,该线程会被挂起,调用线程只有得到结果之后才会返回回来,那么非阻塞调用是指调用在不能返回之前,这个通道没有阻塞当前进线程
那么在知道这个概念之后,同步和异步,阻塞和非阻塞,说一下IO多路复用方案
其实在开发中,一般I/O多路复用机制都是依赖于一个事件的多路复用分离器,可以这么理解,两个人聊天,找一个传话人就可以,由他去处理,这样来实现多路分离器。所以说分离器对象可以将来自事件源I/O 事件分离出来,并分发到对应的read/write 事件分离器中,作为开发人员预先注册需要处理的事件和事件处理器;那么通过事件处理器来负责将请求事件传递给事件处理器进行处理
那么市面上有两种常用的事件分离模式,分别是reactor 和 proactor 。说一下这两种事件分离模式的区别,Reactor模式采用同步I/O 而Proactor 是采用异步I/O ,在Reactor模式中,事件分离器负责等待文件描述符或者socket作为读写准备操作就绪,然后将就绪的事件传递给对应的处理器,最后交由处理器来完成工作。
而Proactor 模式,处理器或者事件分离器,只是负责发起异步读写操作,I/O操作本身由操作系统完成,传递给操作系统的参数,需要包括用户定义的缓冲区地址和数据大小,操作系统才能从中得到写出操作所需要的数据,或者写入从socket读到的数据,事件分离器捕获I/O操作完成事件,然后将事件传递给对应处理器,比如在windows上,处理器发起一个异步I/O操作,再由事件分离器等待IO。completion事件,典型的异步模式实现,都是建立在操作系统支持异步API基础之上完成的,我们将这种实现称为,系统级别异步或者 真 异步,因为应用程序完成全依赖操作系统来执行真正的I/O操作
说的可能不能理解,举个例子
在Reactor实现读
在Proactor实现的读
结论是:两个模式的共同点,都是对于某个I/O事件的事件通知,告诉某个模块,这个I/O操作可以进行,或者已经完成了,在结构上面,两个相同点和不同点,我罗列一下,方便更直观的看出一些东西
相同点:处理器当提交I/O操作,判断设备是否可以操作异步io 当满足条件的情况下,都是由分离器交给处理器处理,然后交出控制权返回结果
不同点,异步情况下,当调用处理器的时候,表示io已经完成了,不关注后面是否就绪,同步情况下Reactor 会关注是否就绪,可以进行操作
说一下概念: BIO就是同步阻塞模式的IO,一般两端交互,通常我们会通过 while 循环中服务端会调用accept方法去当代客户端的链接强求,那么我们常见的socket链接方式就是这种
代码示例