I/O流(包括操作系统与内核,用户空间),I/O工作原理,Java I/O流的设计及Java IO系统_m0
在介绍IO流之前,先介绍一下一些基础的概念
一.操作系统与内核
1.1操作系统
- 俗话说,操作系统是连接系统软硬件的一座桥梁,它自身本质上就是一个软件。
- 他的作用就是管理计算机硬件与软件资源的学习通软件。
1.2内核
- 内核主要是操作系统的核心软件,负责管理系统的进程、内存、设备驱动软件、文件和网络系统等等,为应用程序提供对计算机硬件的安全访问服务。
1.3 关系图
二、内核空间和用户空间
2.1:目的:
- 为了避免用户进程直接操作内核,保证内核安全,操作系统将内存寻址空间划分为两部分:
2.2、内核空间(Kernel-space):
- 在Linux中,Linux驱动程序一般工作在内核空间,但也可以工作在用户空间;
- Linux简化了分段机制,使得虚拟地址与线性地址总线是一致的,因此,Linux的虚拟地址空间也为0~4G。Linux内核将这4G字节的空间分为两部分,将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF),供内核使用成为内核空间。而将较低的3G字节(从0x00000000到0xBFFFFFFF),供各个进程使用,称为用户空间。
- 因为每个进程可以通过系统调用进入内核,因此,Linux内核由系统内所有进程共享,于是,从具体进程角度来看,每个进程可以拥有4G字节的虚拟空间。
- 内核空间中存放的是内核代码和数据,而进程的使用空间中存放的是用户程序的代码和数据,不管是内核空间还是用户空间,他们都处于虚拟空间中,虽然内核空间占据了每个虚拟内存中最高1GB字节,但映射到物理内存却总是从最低地址(0x00000000)开始。
- 对内核空间来说,期地址映射事件单的线性映射,0xc0000000就是物理地址和线性地址之间的的位移量,在Linux代码中就叫做PAGE_OFFSET
2.3、用户空间(User-space):
- 用户空间就是用户进程所在的内存区域,而用户进程和系统进程的所有数据都在内存中,
- 供用户进程使用,只能访问受限权限,不能直接访问内存等硬件设备,必须通过系统调用陷入到内核中,只能才能访问这些特殊权限。
- 为了安全,内核空间和用户空间是隔离的,即使用户空间的程序崩溃了,内核也不受影响。
- 处于用户态的程序只能访问用户空间,而处于内核态的程序可以访问用户空间和内核空间。
2.4、内核空间与用户空间通讯
- 内核空间和用户空间一般通过系统调用进行通信。
2.5、用户态和内核态的区别
- 当一个任务(进程)执行系统调用而陷入内核代码执行时,我们就称进程处于内核运行态即内核态。此时处理器处于特权级最高的0级内核中执行;当进程处于内核态时,执行的内核代码会使用当前进程的内核栈。每个进程都有自己的内核栈,当进程在执行自己的内核代码时,则称其处于用户运行态即用户态,此时处理器在特权级最低的3级用户代码中运行。
- 内核态与用户态时操作系统的两种运行级别,Intel x86架构提供Ring0-Ring3四种级别的运行模式,Ring0级别最高,Ring3级别最低。
- Linux使用了Ring三级别运行用户态,Ring0作为内核态,没有使用Ring1和ring2 .Ring3状态不能访问Ring0的地址空间,包括代码和数据。程序特权级别的不同,所具有的权利也不同
2.6、用户态转换为内核态的3种方式
- 这三种方式是系统在运行时由用户态转到内核态的最主要的方式,其中系统调用可以认为是用户进程主动发起的,异常和外围设备中断则是被动的。
2.6.1、系统调用
- 这是用户态进程主动要求切换到内核态的一种方式,用户态进程通过系统调用申请使用操作系统提供的服务进程完成工作,比如fork()实际上就是执行一个创建新进程的一种系统调用,而系统调用的及至的核心还是使用了操作系统为用户特别开方的一个中断来实现。
2.6.2、异常
- 当CPU再执行运行在用户态下的程序时,阿生了某些事先不可预知的异常,这时会触发有当前运行切换到处理此异常的内核相关程序中,也就转到了内核态。
2.6.3、外围设备的中断
- 当外围设备完成用户其去年高俅的操作后,会向CPU发出相应的中断信号,,这时CPU会暂停执行下一条即将要执行的指令转而去执行与中断信号对应的处理程序,如果事先执行的指令是用户态下的程序,那么这个转换的过程自然也就发生了由用户态到内核态的转换,
2.7、系统调用:
- 是操作系统的最小功能单位,通过提供一些基本功能的接口供应用程序调用来调度内核空间管理的资源。当程序运行从用户态到内核态,那么处在用户态的进程需要先保存当前的数据以及运行的指令,方便回到用户态时继续执行,这中间还有很多其他的事情需要做,例如CPU寄存器需要保护和加载,系统调用器的二代吗需要执行等。
三、数据流
- 计算机中的数据是基于随着时间变换高低电压信号传输的,这些数据信号连续不断,有着固定的传输方向,类似水管中水的流动,因此抽象数据流I/O流的概念:指义序有顺序的,有起点和终点的字节集合。
- 抽象出数据流的作用:实现程序逻辑与底层硬件解耦,通过引入数据流作为程序与硬件设备之间的抽象层,面向通用的数据输出接口编程,而不是具体硬件特性,程序和底层硬件可以独立灵活替换和扩展。
- System,IO.Stream类是所有流的抽象基类,Stream类及其派生类提供这些不同类型的输入和输出的一般视图,使程序员不必了解操作系统和基础设备的具体细节。
- 根据基础数据源和储存库,流可能只支持这些功能中的一部分,用户可以通过使用CanRead、CanWrite和Canseek属性,可实现应用程序查询流的功能。
- Read和Write方法读写各种不同格式的数据。对于支持查找的流,使用Seek和Setlength方法以及Position和Length属性可以查询和修改流的当前位置和长度。
- 有些流用于实际执行基础数据的本地缓冲以提高性能,对于这样的流,Flush方法可以用于清除所有内部缓冲区并确保将所有的数据写入基础数据源或存储库。
- 在Stream上调用Close将刷新所有缓冲区处理的数据,本质上使用用户调用Flush方法。Close也会释放操作系统资源,如文件句柄、网络连接或用于任何内部缓冲的内存,BufferedStream类提供了建一个讲过缓冲区的流环绕另一个流功能,一遍提高读写功能。
- 如果需要不带后背存储区(即位存储桶)的流,应使用NULL。
四、IO工作原理
4.1、磁盘IO
典型的IO读写磁盘工作原理如下;
4.2、COU复制
- 在DMA技术出现之前,应用程序与磁盘之间的I/O操作都是CPU的中断完成的,每次用户进程读取磁盘数据时,都需要CPU中断将数据读进暂存器,然后发起I/O请求等待 数据读取和拷贝完成,然后写进其他地方,每次的I/O中断都导致CPU的上下文切换。
4.3、DMA复制
- DMA(Direct Memory Access,直接存储器访问),基于DMA访问方式,系统主内存与硬件设备的数据传输可以省去CPU的全程调度,
- 具体流程:CPU对DMA控制器初始化,向I/O接口发出操作指令,I/O接口提出DMA请求DMA控制器对DMA请求判断优先级即评比,向总线裁决逻辑提出总线要求,当CPU执行完当前总线周期即可释放总线控制权,此时,总线裁决逻辑输出总线应答,表示DMA已经响应,通过DMA控制器通知I/O接口开始DMA传输。
- 值得注意的是:
- 读写操作基于系统调用实现
- 读写操作经过用户缓冲区,内核缓冲区,应用过程并不能直接操作磁盘
- 应用进程读取操作时需阻塞直到读取到数据
- 值得注意的是:
4.4、网络I/O
- 值得注意的是:
- 网络I/O读写操作经过用户缓冲区,Socket缓冲区
- 服务端线程在从调用开始到它返回有数据报准备好这段时间是阻塞的,read返回成功后,线程开始处理数据报
五、Java I/O流设计
5.1、流介绍
- java中所有的数据都是使用流读写的,流是一组有顺序,有方向,有起点和重点的字节集合,是对数据传输的总称
5.2、流体系
5.3、流的实现类
5.4、java中流的划分
- 按照方向划分
- 输入流:从各种输入设备(磁盘、网卡、键盘……)将数据读取到当前程序中
- 输出流:从当前程序将数据写入输出设备(磁盘、网卡、屏幕……)
- 按照数据传输单元划分:
- 字节流:一字节为单位的数据传输流,InputStream和OutputStream
- 字符流:以字符为单位的数据传输流,Reader、Writer
- 按照功能划分
- 节点流:用于直接操作目标设备的流
- 过滤流(高级流):对一个已经存在的流进行包装,以提供更强大灵活地读写功能。
5.5、java I/O原理
六、Java I/O系统
6.1、Java标准类库中各种各样的类以及它们的用法
6.1.1 File类
- File(文件)这个类实际上既能代表一个特定文件的名称,又能代表一个目录下的一组文件的名称;
- 如果它指的是一个文件集,我们就可以对这个类调用list()方法,这个方法会返回一个字符数组,因为元素的个数是固定的,所以我们想取得不同的目录列表,只需要再创建一个不同的File对象就可以了
import java.io.File;
import java.io.FilenameFilter;
import java.util.Arrays;
import java.util.regex.Pattern;
public class DirList {
public static void main(String[] args) {
File path=new File(".");
String[] list;
if (args.length==0){
list=path.list();
}else {
list =path.list(new DirFilter(args[0]));
Arrays.sort(list,String.CASE_INSENSITIVE_ORDER);
for (String dirItem:list){
System.out.println(dirItem);
}
}
}
static class DirFilter implements FilenameFilter{
private Pattern pattern;
public DirFilter(String regex){
pattern=Pattern.compile(regex);
}
@Override
public boolean accept(File dir, String name) {
return pattern.matcher(name).matches();
}
}
}
- 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
- 这里的DirFilter类实现了FilenameFilter接口,DIRFilter这个类存在的唯一原因就是将accept()方法,创建这个类的目的在与把accept()方法提供给list()使用,是的list()可以回调accept(),进而以决定那些文件包含在列表中。因此这种结构也常常成为回调。更具体的说,这是个策略模式的例子。因为list()实现了基本的功能,而且按照FilenameFilter的形式提供了这个策略,以便完善list()在提供服务时所需的算法。
- accept()方法必须接受一个代表某个特定文件所在目录的File对象,以及包含了那个文件名的一个String。List()方法会为此目录对象下的每个文件名调用accept(),来判断该文件是否包含在内,判断借故偶有由accept返回的布尔值表示。
- accept()会使用一个正则表达式的matcher对象,来查看此正则表达式regex是否匹配这个文件的名字,通过结果由accept(),list()方法会返回一个数组。
6.1.2 InputStream类型
- InputStream的作用是用来表示那些从不同数据源产生输入的类,这些数据包括:
-
- 字节数组
-
- String对象
-
- 文件
-
- “管道”,工作方式与实际管道相似,即从一端输入,从另一端输出
-
- 一个有其他种类的流组成的序列,以便我们可以将它们收集合并到一个流内
-
- 其他数据源,如Internet连接等
每一种数据源都有想用的InputStrean子类。另外,FilterInputStream也属于一种InputStream,为装饰器类(decorator)类提供基类,其中,装饰器类可以吧属性或有用的接口与是用户如流连接在一起,
- 其他数据源,如Internet连接等
-
6.1.3 OutputStream类型
- 该类别的类决定了输出所要去往的目标,字节数组(但不是String)、文件或管道。
- FilterOutStream为装饰器类提供了一个基类,装饰器类把属性或者有用的接口与输出流连接了起来。
6.2 I/O流的典型使用方式
6.2.1 缓冲输入文件
- 如果想要打开一个文件用于字符输入,可以使用以String或File对象作为文件名的FileInputReader,为了提高速度,特别希望对那个文件进行缓冲,那么我们可以将所产生的引用传给一个BuferedReader构造器,由于BufferedReader也提供了readLine()方法,所以这是我们的最终对象和进行读取的接口,当readLIne()将返回null时,就达到了文件的末尾。
- 1
- 2
推荐阅读