I/O也叫做输入/输出,在java编程语言中,I/O更被看作是一种流;JavaI/O的体系设计与Linux内核I/O有着密不可分的关系;
为了操作系统的安全考虑,Linux进程是无法直接操作I/O设备的,必须通过内核来协助完成I/O动作,而内核会为每个I/O设备维护一个缓冲区Buffer,又叫做缓冲区;
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KV20kEep-1639811866164)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20211214203409731.png)]](https://i-blog.csdnimg.cn/blog_migrate/242e79290683563c14afbe766067237e.png)
数据从I/O设备拷贝到buffer缓冲区(等待数据阶段);
数据再从缓冲区拷贝到Linux进程(拷贝数据阶段);
此过程可分为五种模型:
**阻塞I/O模型(Blocking I/O):**在Linux中默认情况下所有请求都是阻塞的,一个典型的读操作如下图:
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mAA3nwH2-1639811866167)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20211214205226340.png)]](https://i-blog.csdnimg.cn/blog_migrate/cfd33257f434dd6b22f167f86db28b5b.png)
**非阻塞I/O(Non-Blocking I/O):**应用会不断询问内核是否准备好了数据,系统不会阻塞用户进程,而是立即返回,从用户进程角度讲,并不需要等待可以立即返回结果;
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oXlhtvCI-1639811866168)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20211214205537020.png)]](https://i-blog.csdnimg.cn/blog_migrate/d5059ca9a9ad84fc093635ba892f6ca4.png)
I/O 复用(I/O Multiplexing):
信号驱动 I/O(Signal Driven I/O):
异步 I/O(Asynchrnous I/O):
Java标准I/O模型(Blocking I/O)
早期的Java I/O都是基于阻塞I/O模型的,他按照处理的数据类型分为字符流和字节流,按照工作职责分为处理流和装饰流。
字节流: 以 8 位( 即 1byte, 8bit) 作为一个数据单元, 数据流中最小的数据单元是字节;
字符流: 以 16 位( 即 1char, 2byte, 16bit) 作为一个数据单元, 数据流中最小的数据单元是字符, Java 中的字符是 Unicode 编码, 一个字符占用两个字节;
Java依据据字节流定义了了InputStream/OutputStream,依据字符流Java定义了Reader/Writer;Java 再根据不同应用场景或功能, 通过继承这两种抽象基类派生出子类, 用
来满足文件、 网络、 管道等不同场景的 I/O 需求, 从而形成了 Java 的基本 I/O 体系。
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GGpI5frT-1639811866168)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20211214210429635.png)]](https://i-blog.csdnimg.cn/blog_migrate/a5cbb701e5f425454bd847d257433e60.png)
又可按照工作职能分为:
处理流: 真正直接处理 I/O 数据的类, 包括文件流、 数组流、 字符串流、 管道流;
装饰流: 用来装饰加工节点流的, 比如封装某些功能, 包括缓冲流、 转换流、 数据流、 对象流;
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8N2zAs2s-1639811866170)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20211214210645930.png)]](https://i-blog.csdnimg.cn/blog_migrate/23d63a5219714d202031bbbc8cb7d30e.png)
这些标准输入现在正逐渐被NIO取代
Java NIO模型(Non-blocking I/O)
**NIO 出现了(有的也称之为 New I/O),NIO 不但新增加了许多全新的类,而且还对原 java.io 包中的很多类进行了改写,NIO 主要包含三个核心部分:Channel、Buffer 和 **
Selector。
**Channel(通道):**NIO 中所有的读写操作都是从 Channel(通道)开始的,顾名思义,Channel 就是一条能够让数据通过的管道(像喝奶茶的吸管那样),它最重要的实可
以分为两大类:用于本地文件的 Channel 和用于网络的 Channel。
**Buffer(缓冲区):**Buffer 是专门用来缓存输入或输出的数据,分为输入缓冲区和输出缓冲区,它可以解决高速设备与低速设备速度的不匹配问题,也可以减少读写次数,
Channel提供从文件、网络读取数据的渠道,但是读写的数据都必须经过 Buffer。
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Jr1kNGEM-1639811866170)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20211214220230790.png)]](https://i-blog.csdnimg.cn/blog_migrate/a70d17610b3b98c75e3954487282ac54.png)
Selector(选择器):数据总是从 Channel 读取到 Buffer,或者从 Buffer 写入到Channel,单个线程可以监听多个 Channel——Selector 就是这个线程背后的实现机制。
Selector 通过单线程处理多个 Channel,而 Selector(线程)之间需要传递数据的话,需要用到 Pipe,它是用于 Selector 之间数据传递的一种「单向」「管道」。
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-594bXBM5-1639811866171)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20211214220255416.png)]](https://i-blog.csdnimg.cn/blog_migrate/ca78f6a8845ba7ce1a2720587b4a59be.png)
Scatter/Gather
Java NIO 支持一种称之为支持本地矢量 I/O(native vectored I/O)的技术(或者称为 Scatter/Gather 操作),当应用在一个 Channel 上请求一个 Scatter/Gather 操作时,这
个请求会被翻译为适当的本地调用来直接填充或抽取缓冲区,减少或避免了缓冲区拷贝和系统调用,这也意味着,Scatter/Gather 将使用直接缓冲区以从本地 I/O 获取最大性优
势(直接缓冲区的概念请参考名词解释),具体来说:
Scatter:将从一个 Channel 里读取的信息分散到多个 Buffer 中;
Gather:将多个 Buffer 里面的内容按顺序发送到一个 Channel 中。
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gwmJyQKc-1639811866172)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20211214220503446.png)]](https://i-blog.csdnimg.cn/blog_migrate/919093299094a3fbccf5580620852f2a.png)
标准I/O如何使用
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RPuCj1zN-1639811866173)(E:\develops\EdrawSoft\WorkStation\绘图3.png)]](https://i-blog.csdnimg.cn/blog_migrate/3e16273013667a1713c0e631aebfd222.png)
File类及相关应用
File类的构造方法:
File(File parent,String child) 从父抽象路径名和子路径名字符串创建新的 File实例。
File(String pathname) 通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例。
File(String parent, String child) 从父路径名字符串和子路径名字符串创建新的 File实例。
File(URI uri) 通过将给定的 file: URI转换为抽象路径名来创建新的 File实例。
常用方法
public String getAbsolutePath() : 返回此File的绝对路径名字符串。
public String getPath() : 将此File转换为路径名字符串。
public String getName() : 返回由此File表示的文件或目录的名称。
public long length() : 返回由此File表示的文件的长度。
判断功能的方法
public boolean exists() : 此File表示的文件或目录是否实际存在。
public boolean isDirectory() : 此File表示的是否为目录。
public boolean isFile() : 此File表示的是否为文件。
增删的方法
public boolean createNewFile() : 当且仅当具有该名称的文件尚不存在时,创建一个新的空文件。
public boolean delete() : 删除由此File表示的文件或目录。
public boolean mkdir() : 创建由此File表示的目录。
public boolean mkdirs() : 创建由此File表示的目录,包括任何必需但不存在的父目录。
目录的遍历
public String[] list() : 返回一个String数组,表示该File目录中的所有子文件或目录。
public File[] listFiles() : 返回一个File数组,表示该File目录中的所有的子文件或目录
任务一:如果是 Windows 系统,以递归方式读取 C 盘中所有的目录和文件,并打印出每个文件的大小和每个目录中文件的数量;如果是 Linux/Mac 系统,那么递归方式读取/usr中所有的目录和文件,并打印出每个文件的大小和每个目录中文件的数量。
package T11.windowC;
import java.io.File;
import java.text.DecimalFormat;
import static java.lang.Math.pow;
/**
* @Author: Administrator
* Date: 2021/12/14 21:15
* @Version:
* @Description:
*/
public class Test {
public void soutWindowC(File file) {
File[] files = {};
if (file.isFile()) {
System.out.print(file.getName());
String s = fileSize(file);
System.out.println("----" + s);
return;
} else if (file.isDirectory()) {
files = file.listFiles();
if (files == null) {
return;
} else {
for (File file1 : files) {
if (file1.isDirectory()) {
System.out.println(file1.getName());
System.out.print("\t");
soutWindowC(file1);
}
if (file1.isFile()) {
System.out.print(file1.getName());
String s = fileSize(file1);
System.out.println("----" + s);
return;
}
}
}
}
}
String fileSize(File file) {
DecimalFormat df = new DecimalFormat("#.00");
String fileSize = "";
if (file.length() < 1024) {
fileSize = df.format(file.length()) + "B";
}
else if (file.length() < pow(1024, 2)) {
fileSize = df.format(file.length()/1024) + "KB";
}
else if (file.length() < pow(1024, 3)) {
fileSize = df.format(file.length()/pow(1024, 2)) + "MB";
}
else if (file.length() < pow(1024, 4)) {
fileSize = df.format(file.length()/pow(1024, 3)) + "GB";
}
return fileSize;
}
@org.junit.Test
public void test() {
File file = new File("D:/");
soutWindowC(file);
}
}
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZM3d4dTQ-1639811866177)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20211215112934307.png)]](https://i-blog.csdnimg.cn/blog_migrate/1f23a96ce72bc7bdae86e1fea425f9a6.png)
字节操作流
InputStream(字节输入流):
public void close() : 关闭此输入流并释放与此流相关联的任何系统资源。当完成流的操作时,必须调用此方法,释放系统资源。
public abstract int read() : 从输入流中读取下一个数据字节。值字节返回int ,范围为0至255 。如果由于到达流末尾而没有可用字节,则返回值-1 。此方法将阻塞,直到输入数据可用,检测到流的末尾或抛出异常。子类必须提供此方法的实现.
public int read(byte[] b) : 从输入流中读取一些字节数,并将它们存储到字节数组 b中 。
OutputStream(字节输出流):
public void close() : 关闭此输出流并释放与此流相关联的任何系统资源。当完成流的操作时,必须调用此方法,释放系统资源。
public void flush() : 刷新此输出流并强制任何缓冲的输出字节被写出。
public void write(byte[] b) : 将 b.length字节从指定的字节数组写入此输出流。
public void write(byte[] b, int off, int len) : 从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流。
public abstract void write(int b) : 将指定的字节输出流。
public class InputStreamDemo {
@Test
public void test() throws IOException {
FileInputStream fis = new FileInputStream("E://old/battleField.png");
FileOutputStream fos = new FileOutputStream("E://new/battleField.png");
byte[] b= new byte[1024];
int len;
while((len = fis.read(b))!=-1){
fos.write(b, 0, len);
}
}
}
字符操作流
Reader(字符输入流):
public void close() : 关闭此输入流并释放与此流相关联的任何系统资源。当完成流的操作时,必须调用此方法,释放系统资源。
public abstract int read() : 从输入流中读取下一个数据字节。值字节返回int ,范围为0至255 。如果由于到达流末尾而没有可用字节,则返回值-1 。此方法将阻塞,直到输入数据可用,检测到流的末尾或抛出异常。子类必须提供此方法的实现.
public int read(byte[] b) : 从输入流中读取一些字节数,并将它们存储到字节数组 b中 。
Writer(字符输出流):
public void close() : 关闭此输出流并释放与此流相关联的任何系统资源。当完成流的操作时,必须调用此方法,释放系统资源。
public void flush() : 刷新此输出流并强制任何缓冲的输出字节被写出。
public void write(byte[] b) : 将 b.length字节从指定的字节数组写入此输出流。
public void write(byte[] b, int off, int len) : 从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流。
public abstract void write(int b) : 将指定的字节输出流。
public class ReaderDemo {
@Test
public void test() throws IOException {
FileReader reader = new FileReader("E://old/battleField.png");
FileWriter writer = new FileWriter("E://new/battleField.png");
char c[] = new char[1024];
int len;
while ((len = reader.read(c)) != -1)
{
writer.write(c, 0, len);
writer.flush();
}
}
}
NIO应该如何使用
public class NIODemo {
@Test
public void test() throws IOException {
FileChannel channelIn = new FileInputStream("E://old/battleField.png").getChannel();
FileChannel channelOut = new FileOutputStream("E://new/battleField.png").getChannel();
channelIn.transferTo(0, channelIn.size(), channelOut);
}
}
感谢您的阅读,如果本篇文章对您有帮助,欢迎点赞,关注,您的阅读是我莫大的鼓励!
本文详细介绍了Java中的I/O概念及其与Linux内核I/O的关系,对比了阻塞I/O模型与非阻塞I/O模型的特点。此外,还深入探讨了Java NIO的核心组件,包括Channel、Buffer和Selector,并提供了具体的使用示例。
432

被折叠的 条评论
为什么被折叠?



