I/O 设备及其接口线路、控制部件、通道和管理软件。
根据输入输出特性划分: 输入型外围设备、输出型外围设备和存储型外围设备。
按照输入输出信息交换的单位划分:
字符设备:输入型外围设备和输出型外围设备一般为字符设备,它与内存进行信息交换的单位是字节,即一次交换 1 个或多个字节
块设备:块设备一次与内存交换的一个或几个块的信息,存储型外围设备一般为块设备
按照 I/O 控制器功能的强弱,以及和 CPU 之间联系方式的不同,可把I/O设备的控制方式分为四类,它们的主要差别在于中央处理器和外围设备并行工作的方式不同,并行工作的程度不同。
一旦 CPU 启动 I/O 设备,便不断查询I/O的准备情况,终止了原程序的执行。CPU在反复查询过程中,浪费了宝贵的 CPU 时间;另一方面,I/O 准备就绪后,CPU 参与数据的传送工作,此时 CPU 也不能执行原程序,可见 CPU 和 I/O 设备串行工作,使主机不能充分发挥效率,外围设备也不能得到合理使用,整个系统的效率很低。
使用中断的方式,由于输入输出操作直接由中央处理器控制,每传送一个字符或一个字,都要发生一次中断,因而仍然消耗大量中央处理器时间。程序中断方式 I/O,由于不必忙式查询 I/O 准备情况,在 CPU 和 I/O 设备可实现部分并行,与程序查询的串行工作方式相比,使 CPU 资源得到较充分利用
虽然程序中断方式消除了程序查询方式的忙式测试,提高了 CPU 资源的利用率,但是在响应中断请求后,必须停止现行程序转入中断处理程序并参与数据传输操作。如果 I/O 设备能直接与主存交换数据而不占用 CPU,那么,CPU 资源的利用率还可提高,这就出现了直接存储器存取(Direct Memory Access ,DMA)方式。
在 DMA(直接主存存取)方式中,主存和 I/O 设备之间有一条数据通路,在主存和 I/O 设备之间成块传送数据过程中,不需要 CPU 干予,实际操作由 DMA 直接执行完成 。DMA设有中断机制,用于向 CPU 提出 I/O 中断请求和保存 CPU 发来的 I/O 命令及管理 DMA的传送过程。
DMA 方式与程序中断方式相比,使得CPU对IO的干预从字(字节) 为单位的减少到以数据块为单位。而且,每次 CPU干予时,并不要做数据拷贝,仅仅需要发一条启动 I/O 指 令 ,以及完成 I/O 结束中断处理。 但是,每发出一次I/O指令,只能读写一个数据块,如果用户希望一次读写多个离散的数据块,并能把它们传送到不同的内存区域,或相反时,则需要由CPU分别发出多条启动I/O指令及进行多次 I/O 中断处理才能完成。
通道方式进一步减少了CPU对I/O操作的干予,减少为对多个数据块,而不是仅仅一个数据块,及有关管理和控制的干予。
通道可分为三种类型:字节多路通道,选择通道,数组多路通道
设备控制器是 CPU 和设备之间的一个接口,它接收从 CPU发来的命令,控制I/O设备操作,实现主存和设备之间的数据传输操作。
设备控制器的主要功能为: 接收和识别 CPU 或通道发来的命 令,例如磁盘控制器能接收读、写、查找、搜索等各种命令; 实现数据交换,包括设备和控制器之间的数据传输;通过数据总线或通道,控制器和主存之间的数据传输; 发现和记录设备及自身的状态信息,供 CPU处理使用; 设备地址识别。
总结:
在程序直接控制方式中,对读入的每个字节,CPU需要对外设状态进行循环检查,直到确定该字节已经在I/O控制器的数据寄存器中,由于CPU的高速性和I/O设备的低速性,致使CPU的绝大部分时间都处于等待I/O设备完成数据I/O的循环测试中,造成了 CPU资源的极大浪费。
所以允许I/O设备主动打断CPU的运行并请求服务,使得其向I/O控制器发送读命令后可以继续做其他有用的工作。I/O控制器从CPU接收一个读命令,然后从外围设备读数据。一旦数据读入到该I/O控制器的数据寄存器,便通过控制线给CPU发出一个中断信号,表示数据已准备好,然后等待CPU请求该数据。I/O控制器收到CPU发出的取数据请求后,将数据放到数据总线上,传到CPU的寄存器中。因此对于IO操作只有在开始和结束时才需要CPU的参与。
在设备管理子系统中,引入缓冲区的目的主要有:缓和CPU与I/O设备间速度不匹配的矛盾;减少对CPU的中断频率,放宽对CPU中断响应时间的限制;解决基本数据单元大小(即数据粒度)不匹配的问题;提高CPU和I/O设备之间的并行性。
它是内存空间的一部分。也就是说,在内存空间中预留了一定的存储空间,这些存储空间用来缓冲输入或输出的数据,这部分预留的空间就叫做缓冲区,显然缓冲区是具有一定大小的。缓冲区根据其对应的是输入设备还是输出设备,分为输入缓冲区和输出缓冲区。
会使用固定大小的缓冲区,读写都是从缓冲区发生的,缓冲区不会被清除直到它被填满,全缓冲的典型代表是对磁盘文件的读写。
缓冲数据直到遇见一个换行符或者达到缓冲区的固定大小,等按下回车键换行时才进行实际的I/O操作。典型代表是键盘输入数据。
不带缓冲也就是不进行缓冲,任何读写都会立刻发生(并且会产生阻塞)。一些出错信息可以直接尽快地显示出来。
CPU的Cache,它中文名称是高速缓冲存储器,读写速度很快,几乎与CPU一样。由于CPU的运算速度太快,内存的数据存取速度无法跟上CPU的速度,所以在cpu与内存间设置了cache为cpu的数据快取区。当计算机执行程序时,数据与地址管理部件会预测可能要用到的数据和指令,并将这些数据和指令预先从内存中读出送到Cache。一旦需要时,先检查Cache,若有就从Cache中读取,若无再访问内存,现在的CPU还有一级cache,二级cache。简单来说,Cache就是用来解决CPU与内存之间速度不匹配的问题,避免内存与辅助内存频繁存取数据,这样就提高了系统的执行效率。
磁盘也有cache,硬盘的cache作用就类似于CPU的cache,它解决了总线接口的高速需求和读写硬盘的矛盾以及对某些扇区的反复读取。
浏览器缓存(Browser Caching)是为了节约网络的资源加速浏览,浏览器在用户磁盘上对最近请求过的文档进行存储,当访问者再次请求这个页面时,浏览器就可以从本地磁盘显示文档,这样就可以加速页面的阅览,并且可以减少服务器的压力
缓存是指把常用数据存储到可以快速获取的区域,以备重复利用,一般叫做cache. 缓存能提高效率;缓冲是指在数据流转过程中,不同层次速度不一致时,利用缓冲区来缓解上下层之间速率问题,一般叫做buffer,缓冲能提高速度,把分散的写操作集中进行,减少磁盘碎片和硬盘的反复寻道,从而提高系统性能(write-buffer和read-cache)。
cache是读取硬盘中的数据时,把最常用的数据保存在内存的缓存区中,再次读取该数据时,就不去硬盘中读取了,而在缓存中读取,buff是硬盘写入数据时,先把数据放入缓冲区,然后再一起向硬盘写入
不过在讲IO之前,我们先把文件File类了解一些,我们知道File的定义——文件和目录(文件夹)路径名的抽象表示形式。
File(String pathname):根据一个路径得到File对象
File(String parent, String child):根据一个目录和一个子文件/目录得到File对象
File(File parent, String child):根据一个父File对象和一个子文件/目录得到File对象
public boolean createNewFile():创建文件 如果存在这样的文件,就不创建了
public boolean mkdir():创建文件夹 如果存在这样的文件夹,就不创建了
public boolean mkdirs():创建文件夹,如果父文件夹不存在,会帮你创建出来
注:大家可能会认为这里用了关键字new,那么文件File就会出来了,其实不是,因为File的定义是:文件和目录(文件夹)路径名的抽象表示形式。它仅仅是文件的表示形式,不是真实的文件。 所以需要调用这样一句代码 file.createNewFile()。
public boolean delete()
要删除一个文件夹,请注意该文件夹内不能包含文件或者文件夹,所以delete一次只能删除一个文件,
如果想删除一个文件夹,那必须要循环遍历了
public boolean renameTo(File dest)
如果路径名相同,就是改名。如果路径名不同,就是改名并剪切
public boolean isDirectory():判断是否是目录
public boolean isFile():判断是否是文件
public boolean exists():判断是否存在
public boolean canRead():判断是否可读
public boolean canWrite():判断是否可写
public boolean isHidden():判断是否隐藏
public String getAbsolutePath():获取绝对路径
public String getPath():获取相对路径
public String getName():获取名称
public long length():获取长度。字节数
public long lastModified():获取最后一次的修改时间,毫秒值
//后者功能更为强大一点,因为返回的是File对象,所以可以得到File的各种属性
public String[] list():获取指定目录下的所有文件或者文件夹的名称数组
public File[] listFiles():获取指定目录下的所有文件或者文件夹的File数组
public String[] list(FilenameFilter filter)
public File[] listFiles(FilenameFilter filter)
对磁盘读写或网络通信统称为IO操作(不包括内存IO)。对于java程序来说,从文件到内存,便是输入流(InputStream),而从内存到文件便是输出流(OutputStream)
按数据类型分为字节流和字符流。
字节流和字符流区别:字节流一次读取一个字节,但也可以自定义一次读取多少byte,字符流一次读取一个字符,但也可以自定义一次读取多少char字符缓冲流,读取可以按行来读(字节缓冲流没有),读到换行就结束,不会读出换行符,为null时文件读取结束。字符流只是在字节流的基础上进行解码或者编码(从文字到0、1的映射称为编码,反过来从0、1到文字叫解码),一次读取和写入的字节数不同而已(gbk是2个,utf-8是3个)。
对于字符流容易出现乱码情况,gbk是以2个字节代表一个字符(中文或者英文),utf-8用3个字节表示一个中文(字符也可能是一个字节的英文,因为它是可变的)。用eclipse的默认编码写进去文件是gbk,它把每个字符按照一个字符解码成2byte,如果用utf-8取读取该文件,它是一次读3个字节为一个字符,和gbk表示的不是同一个字符,所以会乱码。
read() : 从输入流中读取数据的下一个字节,返回0到255范围内的int字节值。如果因为已经到达流末尾而没有可用的字节,则返回-1。在输入数据可用、检测到流末尾或者抛出异常前,此方法一直阻塞。
int len;
while((len = in.read()) != -1)
System.out.println(len);
read(byte[] b) : 从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中。以整数形式返回实际读取的字节数。在输入数据可用、检测到文件末尾或者抛出异常前,此方法一直阻塞。b数组采用的是覆盖存储,把原先的读取的数组值覆盖掉。
byte b[] = new byte[2];
int len;
while((len = in.read(b)) != -1)
System.out.println(new String(b,"utf-8"));
对于字符流中一次读取一个字符,也可以一次读取多个需把byte数组替换成char数组,在字符缓冲流中多了readLine() 方法可以读取一个文本行,读到末尾为null表示结束。
BufferedReader br = new BufferedReader(new FileReader(""));
String len;
while((len = br.readLine()) != null)
System.out.println(len);
read和buff都能在内存有个缓冲区,使文件读取直接到内存,而不用保存到数据寄存器。如果用read()方法读取一个文件,每读取一个字节就要访问一次硬盘,这种读取的方式效率是很低的。可以使用read(byte b[])方法一次读取多个字节,当读取的文件较大时,也会频繁的对磁盘操作。而有了缓冲区,先把数据读到缓冲区,下次读下一个字节就可以直接从缓冲区取了,这样就可以减少对硬盘的操作。这种从直接内存中读取数据的方式要比每次都访问磁盘的效率高很多。写文件先写入到缓冲区,在当缓冲区满了后物理写入文件。buff底层也是调用read()方法填充到内置的buff数组中。
那为什么不创建一个非常大的b数组呢,这样不就相当于定义了一个大的缓冲区,前面也说了b数组和char数组都采用的是覆盖储存,最后一次读取数据如果没有把数组用完,那么上一次读取的数据又会在一次读出来;而buff主要封装了缓冲和标记/回读的功能,有了缓冲区可以用read方法一个个读,而不会损坏磁盘,如果知道精确的文件数用read(byte[] b)效率也很高。
输出流自带一个缓冲区,会先写到缓冲区,满了再写到文件中,所以有时候没满是不会写到文件,要用flush方法,可以一直write,但不会自己写入换行,widows是加"\r\n"表示换行。输入流也可以一次写入多少字节,或者多少字符。加true为追加,默认为false,java默认的缓冲大小为8k,当没有超过这个值时,里面的内容就不会自动写入文件当中;而当超过这个值时,里面的内容就会自动写入文件当中。写入文件的编码是eclipse默认编码。
字节输出流write()可以写入一个字节或者字节数组,字符输出流则是字符数组或者字符串。对于字节缓冲输出流write()也是一样的参数,但是字符缓冲输出流多了newLine()方法,这个方法会调用系统的换行符。另外都有append追加方法。
//字节输出流
OutputStream output = new FileOutputStream("");
//BufferedOutputStream bos = new BufferedOutputStream(output);
byte b[] = new byte[10];
output.write(b);
Writer w = new FileWriter("");//后面有true表示追加
//BufferedWriter bw = new BufferedWriter(w);
w.write("");
具有转码功能,能过把二进制数据流转换成字符流。在文件中是以字节流形式储存的,所以写到文件中会使用编译器默认字符集将字符串编码为 byte 序列(字符流写入到文件中,字节流写入文件中,以前是什么样到文件也还是那个形式排列),如果要转其他码byte序列就会重新组合(因为不同字符集编码中文所占字节数不同),但与写进去的编码不同就会产生乱码,所以说想转其他解码就要用到转换流。字节怎么写入字符呢,这就要用到转换流,不然自己要字符转字节getBytes。
OutputStream output = new FileOutputStream("");
OutputStreamWriter osw = new OutputStreamWriter(output);//后面加字符编码
osw.write("");
InputStream in = new FileInputStream("");
InputStreamReader ir = new InputStreamReader(in);
ir.read();
有的时候,我们可能需要将内存中的对象持久化到硬盘上,或者将硬盘中的对象信息读到内存中,这个时候我们需要使用对象输入输出流。
当使用对象流写入或者读取对象的时候,必须保证该对象是序列化的,这样是为了保证对象能够正确的写入文件,并能够把对象正确的读回程序。所谓的对象的序列化就是将对象转换成二进制数据流的一种实现手段,通过将对象序列化,可以方便的实现对象的传输及保存,要想实现对象的序列化需要实现Serializable接口(使用transient关键字能保证成员变量的值不被序列化到文件中,只会短暂保存在内存中)。
ObjectInputStream ObjectOutputStream类分别是InputStream和OutputStream的子类,对象输出流使用writeObject(Object obj)方法,将一个对象obj写入到一个文件,使用readObject()读取一个对象。
//序列化写入文件
OutputStream outputStream = new FileOutputStream("");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
objectOutputStream.writeObject(对象);
//序列化读取对象
InputStream inputStream = new FileInputStream("");
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
Object oj = objectInputStream.readObject();
打印流提供了非常方便的打印功能,可以打印任何类型的数据信息,例如:小数,整数,字符串。JAVA对PrintStream功能进行了扩充,增加了格式化输出功能。直接使用Print即可。但是输出的时候需要指定输出的数据类型。
PrintStream、PrintWriter的方法名是完全一致的,一般用PrintWriter,灵活性更强,PrintWriter类实现了在PrintStream类中的所有print方法。区别是PrintStream参数要用字节流,而PrintWriter参数要用字符流,但都可以传入文件对象。
PrintStream ps = new PrintStream(new FileOutputStream("")) ;
ps.printf("姓名:%s;年龄:%s;成绩:%s;性别:%s",name,age,score,sex) ;
datainputstrem和dataoutputstrem可以用来输出各种不同格式的数据,,可以转换成int,float,long等等,随机流RandomAccessFile实现了这接口(不是IO流里的)。
管道流是用来在多个线程之间进行信息传递的Java流,管道流仅用于多个线程之间传递信息,若用在同一个线程中可能会造成死锁,管道的读写操作是互相阻塞的,当缓冲区为空时,读操作阻塞;当缓冲区满时,写操作阻塞
我们知道传统的I/O都是阻塞式(blocked)的,原因是I/O操作比起cpu来实在是太慢了,可能差到好几个数量级都说不定。如果让 cpu 去等I/O 的操作,很可能时间片都用完了,I/O 操作还没完成呢,不管怎样,它会导致 cpu 的利用率极低。所以,解决办法就是:一旦线程中执行到 I/O 有关的代码,相应线程立马被切走,然后调度就绪队列中另一个线程来运行。这时执行了 I/O 的线程就不再运行,即所谓的被阻塞了。它也不会被放到就绪队列中去,因为很可能再次调度到它时,I/O 可能仍没有完成。线程会被放到所谓的阻塞队列中。而后cpu 会收到一个比如说来自硬盘的中断信号,并进入中断处理例程,手头正在执行的线程因此被打断,回到就绪队列。而先前因 I/O 而等待的线程随着 I/O 的完成也再次回到就绪队列,这时 cpu 可能会选择它来执行。也就是说阻塞IO在阻塞时候的操作不是在用户线程执行, 而是操作系统来完成(需转换成内核态,这个时候Java的线程只能等待), 等操作系统那边准备好数据以后用户线程才继续。
进行传统上的 IO 操作时,口语上我们也会说“阻塞”,但这个“阻塞”与java线程的阻塞状态是两码事,对于JVM线程状态的角度看,进行阻塞IO操作的时候线程状态是runnable可运行状态,runnable状态包含的运行和就绪。JVM把线程可能正在等待来自于操作系统的其它资源或者运行中都归纳为runnable。现今主流的 JVM 实现都把 Java 线程一一映射到操作系统底层的线程上,把调度委托给了操作系统,我们在虚拟机层面看到的状态实质是对底层状态的映射及包装。JVM 本身没有做什么实质的调度,把底层的 ready 及 running 状态映射上来也没多大意义,因此,统一成为runnable 状态是不错的选择
当程序阻塞时,会降低程序的效率,于是人们就希望能引入非阻塞的操作方法。 所谓非阻塞方法,就是指当线程执行这些方法时,如果操作还没有就绪,就立即返回,不会阻塞着等待操作就绪。 Java.nio 提供了这些支持非阻塞通信的类。
后面的看IO流知识总结(二)