IO流知识概括

195 阅读10分钟

@TOC

File类的使用

在这里插入图片描述

在这里插入图片描述

IO流原理及流的分类

在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 节点流的类型: 在这里插入图片描述 处理流的类型: 在这里插入图片描述 注意:

  • 程序中打开的文件 IO 资源不属于内存里的资源,垃圾回收机制无法回收该资源,所以应该显式关闭文件 IO 资源。

节点流(文件流)

在这里插入图片描述

FileInputStream.read()方法为什么是顺序读取

import java.io.*;
public class TestFileInputStream {
  public static void main(String[] args) {
    int b = 0;
    FileInputStream in = null;
    try {
      in = new FileInputStream("d:\\share\\java\\io\\TestFileInputStream.java");
    } catch (FileNotFoundException e) {
      System.out.println("找不到指定文件");
      System.exit(-1);
    }
   
    try {
      long num = 0;
      while((b=in.read())!=-1){
        System.out.print((char)b);
        num++;
      }
      in.close(); 
      System.out.println();
      System.out.println("共读取了 "+num+" 个字节");
    } catch (IOException e1) {
      System.out.println("文件读取错误"); System.exit(-1);
    }
  }
}

read()方法是读一个字节,用while循环读取的话,为什么第二次循环就自动read后面的字节了呢?

  • 要解释为什么会自动读取后面的字节,首先需要了解大体上文件在硬盘物理结构上的存取方式。这涉及到很多很多专业知识,比如文件系统等,
  • 简单来说就是,当你保存文件的时候步骤基本上是:操作系统首先在DIR区中找到空区写入文件名、大小和创建时间等相应信息,然后在Data区找到闲置空间将文件按照簇尽可能找到连续的数据区,依次将写入流从头开始每个字节进行顺序保存。
  • 当要进行文件的读取,Java封装的FileInputStream.read方法也会调用操作系统的API依次读取这些数据。在读取文件数据的时候必须是顺序的,不可能说先读取第一个字节,后读取倒数第二个字节。循环读取的时候就read方法将读取的位置++,因此造成每次read都是顺序读取后面的字节,直到遇到文件末尾标记。
  • 另外说一下读取出来的每一个字节在你这个程序中的保存方式: ①文件流FileInputStream的读取是单向的,也就是说从第一个字节到最后一个字节。如果文件总共|1|2|3|4|5|个字节,那么文件读取的顺序肯定是12345,假设当前已经读到3,那么接下来要么你停止读取,要么你读取4,不可能再回头读取2和1,也不能直接去读取5。通过 in.read()读取出来的数据是个临时变量,java会自动在堆中为它分配一个内存空间,但是当 in.read()执行结束后,垃圾回收器会立刻将其删除,因此在你这个程序中,读取出来的文件其实只保存了最后一个字节,这个字节放在b变量中。如果你想保存整个文件数据,你可以建立一个与文件长度等长的byte数组。

缓冲流

在这里插入图片描述 在这里插入图片描述

流的关闭顺序

一般情况下是:先打开的后关闭,后打开的先关闭 另一种情况:看依赖关系,如果流a依赖流b,应该先关闭流a,再关闭流b。例如,处理流a依赖节点流b,应该先关闭处理流a,再关闭节点流b 可以只关闭处理流,不用关闭节点流。处理流关闭的时候,会调用其处理的节点流的关闭方法。 注意: 如果将节点流关闭以后再关闭处理流,会抛出IO异常。 如果关闭了处理流,在关闭与之相关的节点流,也可能出现IO异常。

flush()方法详解

前言:

大家在使用Java IO流中OutputStream、PrintWriter ……时,会经常用到它的flush()方法。

一.为什么要flush:

与在网络硬件中缓存一样,流还可以在软件中得到缓存,即直接在Java代码中缓存。这可以通过BufferedOutputStream或BufferedWriter 链接到底层流上来实现。因此,在写

完数据时,flush就显得尤为重要。

例如: 在这里插入图片描述 上图中WEB服务器通过输出流向客户端响应了一个300字节的信息,但是,这时的输出流有一个1024字节的缓冲区。所以,输出流就一直等着WEB服务器继续向客户端响应信 息,当WEB服务器的响应信息把输出流中的缓冲区填满时,这时,输出流才向WEB客户端响应消息。

为了解决这种尴尬的局面,flush()方法出现了。flush()方法可以强迫输出流(或缓冲的流)发送数据,即使此时缓冲区还没有填满,以此来打破这种死锁的状态。

当我们使用输出流发送数据时,当数据不能填满输出流的缓冲区时,这时,数据就会被存储在输出流的缓冲区中。如果,我们这个时候调用关闭(close)输出流,存储在输出流的缓冲区中的数据就会丢失。所以说,关闭(close)输出流时,应先刷新(flush)换冲的输出流,话句话说就是:“迫使所有缓冲的输出数据被写出到底层输出流中”。

二.解读flush()源码:

下面以BufferedOutputStream类为例:

publicclass BufferedOutputStream extends FilterOutputStream

{

 publicsynchronizedvoid flush()  throws IOException

 {

    flushBuffer();

    out.flush();

 }

privatevoid flushBuffer()  throws IOException

{
    if(count > 0)

    {
        out.write(buf, 0, count);
        count = 0;
    }
 }

} 看到这里大家明白了吧,其实flush()也是通过out.write()将数据写入底层输出流的。

转换流

在这里插入图片描述 在这里插入图片描述 在这里插入图片描述

标准输入,输出流

在这里插入图片描述

打印流

在这里插入图片描述

数据流

在这里插入图片描述

对象流

对象流简介:

  • ObjectInputStream和OjbectOutputSteam:用于存储和读取基本数据类型数据或对象的处理流。它的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来。
  • 序列化:用ObjectOutputStream类保存基本类型数据或对象的机制
  • 反序列化:用ObjectInputStream类读取基本类型数据或对象的机制
  • ObjectOutputStream和ObjectInputStream不能序列化static和transient修 饰的成员变量

序列化简介:

  • 对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。当其它程序获取了这种二进制流,就可以恢复成原来的Java对象
  • 序列化的好处在于可将任何实现了Serializable接口的对象转化为字节数据, 使其在保存和传输时可被还原
  • 序列化是 RMI(Remote Method Invoke – 远程方法调用)过程的参数和返 回值都必须实现的机制,而 RMI 是JavaEE 的基础。因此序列化机制是 JavaEE 平台的基础
  • 如果需要让某个对象支持序列化机制,则必须让对象所属的类及其属性是可 序列化的,为了让某个类是可序列化的,该类必须实现如下两个接口之一。否则,会抛出NotSerializableException异常 ①SerializableExternalizable

序列化详解:

  • 凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量: ①private static final long serialVersionUID; ②serialVersionUID用来表明类的不同版本间的兼容性。简言之,其目的是以序列化对象进行版本控制,有关各版本反序列化时是否兼容。 ③如果类没有显示定义这个静态常量,它的值是Java运行时环境根据类的内部细节自动生成的。若类的实例变量做了修改,serialVersionUID 可能发生变化,故建议显式声明。 <1>一是默认的1L,比如:private static final long serialVersionUID = 1L;
  • 简单来说,Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。(InvalidCastException)

使用对象流序列化对象:

  • 若某个类实现了 Serializable 接口,该类的对象就是可序列化的: ①创建一个 ObjectOutputStream ②调用 ObjectOutputStream 对象的 writeObject(对象) 方法输出可序列化对象 ③注意写出一次,操作flush()一次
  • 反序列化 ①创建一个 ObjectInputStream ②调用 readObject() 方法读取流中的对象 ③强调:如果某个类的属性不是基本数据类型或 String 类型,而是另一个引用类型,那么这个引用类型必须是可序列化的,否则拥有该类型的Field 的类也不能序列化

readObject()与反序列化创建对象底层原理:

  • 其中正是readObject方法返回了一个对象,这个对象就是根据序列化生成的文件而创建的对象,所以反序列化如何创建对象关键就在于readObject方法的实现: ①Debug跟进去发现调用了desc.newInstance()方法。原理是利用反射创建了一个对象,本质是调用非序列化父类的无参构造器。 ②如果该类是Externalizable类型的,则调用它自身的访问权限是public无参构造方法。 ③如果该类是Serializable类型的,则调用该类的第一个非Serializable类型的父类的无参构造方法。 ④所以这就是为什么通过反序列化创建对象的时候,并不会执行被序列化对象的构造方法。对于实现Serializable接口的类,并不要求该类具有一个无参的构造方法, 因为在反序列化的过程中实际上是去其继承树上找到一个没有实现Serializable接口的父类(最终会找到Object),然后构造该类的对象,再逐层往下的去设置各个可以反序列化的属性(也就是没有被transient修饰的非静态属性)。
  • 链接: ①Java反序列化创建对象探析JAVA对象流序列化时的readObject,writeObject,readResolve是怎么被调用的

随机存取文件流

在这里插入图片描述 在这里插入图片描述

IO流总结

总结:

  • ByteArrayInputStream:字节数组输入流,该类的功能就是从字节数组(byte[])中进行以字节为单位的读取,也就是将资源文件都以字节的形式存入到该类中的字节数组中去,我们拿也是从这个字节数组中拿
  • PipedInputStream:管道字节输入流,它和PipedOutputStream一起使用,能实现多线程间的管道通信
  • FilterInputStream:装饰者模式中处于装饰者,具体的装饰者都要继承它,所以在该类的子类下都是用来装饰别的流的,也就是处理类。
  • BufferedInputStream:缓冲流,对处理流进行装饰,增强,内部会有一个缓存区,用来存放字节,每次都是将缓存区存满然后发送,而不是一个字节或两个字节这样发送。效率更高
  • DataInputStream:数据输入流,它是用来装饰其它输入流,它“允许应用程序以与机器无关方式从底层输入流中读取基本 Java数据类型和String类型。”     
  • FileInputSream:文件输入流。它通常用于对文件进行读取操作     
  • File:对指定目录的文件进行操作,具体可以查看讲解File的博文。注意,该类虽然是在IO包下,但是并不继承自四大基础类。
  • ObjectInputStream:对象输入流,用来提供对“基本数据或对象”的持久存储。通俗点讲,也就是能直接传输对象(反序列化中使用),
  • 随机访问文件流 RandomAccessFile,该类的实例支持读取和写入随机访问文件。 随机访问文件的行为类似于存储在文件系统中的大量字节。
  • 链接: ①java IO体系的学习总结Java IO流经典练习题

分类: 在这里插入图片描述 在这里插入图片描述

NIO2中Path,Paths,Files类的使用

在这里插入图片描述 在这里插入图片描述 在这里插入图片描述

CommonsIO

简介:

  • Commons IO是针对开发IO流功能的工具类库。
  • 主要包括六个区域: ①工具类——使用静态方法执行共同任务 ②输入——用于InputStream和Reader实现 ③输出——用于OutputStream和Writer实现 ④过滤器——各种文件过滤器实现 ⑤比较器——各种文件的java.util.Comparator实现 ⑥文件监听器——监听文件系统事件的组件
  • Commons之Commons-io