前言
一、IO流与系统
IO技术在JDK中算是极其复杂的模块,其复杂的一个关键原因就是IO操作和系统内核的关联性,另外网络编程,文件管理都依赖IO技术,而且都是编程的难点,想要整体理解IO流,先从Linux操作系统开始。
Linux空间隔离
Linux使用是区分用户的,这个是基础常识,其底层也区分用户和内核两个模块:
- User space:用户空间
- Kernel space:内核空间
常识用户空间的权限相对内核空间操作权限弱很多,这就涉及到用户与内核两个模块间的交互,此时部署在服务上的应用如果需要请求系统资源,则在交互上更为复杂:
用户空间本身无法直接向系统发布调度指令,必须通过内核,对于内核中数据的操作,也是需要先拷贝到用户空间,这种隔离机制可以有效的保护系统的安全性和稳定性。
参数查看
可以通过Top命令动态查看各项数据分析,进程占用资源的状况:
us
:用户空间占用CPU的百分比;sy
:内核空间占用CPU的百分比;id
:空闲进程占用CPU的百分比;wa
:IO等待占用CPU的百分比;
对wa
指标,在大规模文件任务流程里是监控的核心项之一。
IO协作流程
此时再看上面图【1】的流程,当应用端发起IO操作的请求时,请求沿着链路上的各个节点流转,有两个核心概念:
- 节点交互模式:同步与异步;
- IO数据操作:阻塞与非阻塞;
这里就是文件流中常说的:【同步/异步】IO,【阻塞/非阻塞】IO,下面看细节。
二、IO模型分析
1、同步阻塞
用户线程与内核的交互方式,应用端请求对应一个线程处理,整个过程中accept(接收)和read(读取)方法都会阻塞直至整个动作完成:
在常规CS架构模式中,这是一次IO操作的基本过程,该方式如果在高并发的场景下,客户端的请求响应会存在严重的性能问题,并且占用过多资源。
2、同步非阻塞
在同步阻塞IO的基础上进行优化,当前线程不会一直等待数据就绪直到完成复制:
在线程请求后会立即返回,并不断轮询直至拿到数据,才会停止轮询,这种模式的缺陷也是显而易见的,如果数据准备好,在通知线程完成后续动作,这样就可以省掉很多中间交互。
3、异步通知模式
在异步模式下,彻底摒弃阻塞机制,过程分段进行交互,这与常规的第三方对接模式很相似,本地服务在请求第三方服务时,如果请求过程耗时很大,会异步执行,第三方第一次回调,确认请求可以被执行;第二次回调则是推送处理结果,这种思想在处理复杂问题时,可以很大程度的提高性能,节省资源:
异步模式对于性能的提升是巨大的,当然其相应的处理机制也更复杂,程序的迭代和优化是无止境的,在NIO模式中再次对IO流模式进行优化。
三、File文件类
1、基础描述
File类作为文件和目录路径名的抽象表示,用来获取磁盘文件的相关元数据信息,例如:文件名称、大小、修改时间、权限判断等。
注意:File并不操作文件承载的数据内容,文件内容称为数据,文件自身信息称为元数据。
public class File01 {
public static void main(String[] args) throws Exception {
// 1、读取指定文件
File speFile = new File(IoParam.BASE_PATH+"fileio-03.text") ;
if (!speFile.exists()){
boolean creFlag = speFile.createNewFile() ;
System.out.println("创建:"+speFile.getName()+"; 结果:"+creFlag);
}
// 2、读取指定位置
File dirFile = new File(IoParam.BASE_PATH) ;
// 判断是否目录
boolean dirFlag = dirFile.isDirectory() ;
if (dirFlag){
File[] dirFiles = dirFile.listFiles() ;
printFileArr(dirFiles);
}
// 3、删除指定文件
if (speFile.exists()){
boolean delFlag = speFile.delete() ;
System.out.println("删除:"+speFile.getName()+"; 结果:"+delFlag);
}
}
private static void printFileArr (File[] fileArr){
if (fileArr != null && fileArr.length>0){
for (File file : fileArr) {
printFileInfo(file) ;
}
}
}
private static void printFileInfo (File file) {
System.out.println("名称:"+file.getName());
System.out.println("长度:"+file.length());
System.out.println("路径:"+file.getPath());
System.out.println("文件判断:"+file.isFile());
System.out.println("目录判断:"+file.isDirectory());
System.out.println("最后修改:"+new Date(file.lastModified()));
System.out.println();
}
}
1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.
上述案例使用了File类中的基本构造和常用方法(读取、判断、创建、删除)等,JDK源码在不断的更新迭代,通过类的构造器、方法、注释等去判断类具有的基本功能,是作为开发人员的必备能力。
在File文件类中缺乏两个关键信息描述:类型和编码,如果经常开发文件模块的需求,就知道这是两个极其复杂的点,很容易出现问题,下面站在实际开发的角度看看如何处理。
2、文件业务场景
如图所示,在常规的文件流任务中,会涉及【文件、流、数据】三种基本形式的转换:
基本过程描述:
- 源文件生成,推送文件中心;
- 通知业务使用节点获取文件;
- 业务节点进行逻辑处理;
很显然的一个问题,任何节点都无法适配所有文件处理策略,比如类型与编码,面对复杂场景下的问题,规则约束
是常用的解决策略,即在约定规则之内的事情才处理。
上面流程中,源文件节点通知业务节点时的数据主体描述:
public class BizFile {
/**
* 文件任务批次号
*/
private String taskId ;
/**
* 是否压缩
*/
private Boolean zipFlag ;
/**
* 文件地址
*/
private String fileUrl ;
/**
* 文件类型
*/
private String fileType ;
/**
* 文件编码
*/
private String fileCode ;
/**
* 业务关联:数据库
*/
private String bizDataBase ;
/**
* 业务关联:数据表
*/