Java输入输出流

这是我参与11月更文挑战的第7天,活动详情查看:2021最后一次更文挑战

前言

今天我们来说一下在Java里对文件的操作,IO流(输入输出流)。首先在计算机中文件在计算机上的信息集合,可以是文本、图片、视频等 。文件是以二进制的方式存放的

输入和输出流

输入流:可以从其中读入一个字节序列的对象称作输入流

输出流:可以从其中写入一个字节序列的对象称作输出流

字符序列的来源地和目的地可以是文件(通常是)、网络连接、内存块等

IO流常用类

InputStream和OutputStream

​ 在常用类中抽象类InputStream和OutPutStream构成了输入和输出类层次结构的基础。它们也被称为字节输入/输出流。它们是所有类字节输入/输出流的父类。

类图:

截屏2021-11-07 下午3.10.28

Reader和Writer

​ 因为字节流是以一个字节来作为处理的基本单位,对于使用Unicode形式储存的信息(文本信息)不是特别方便的去进行处理,因为在Unicode中每个字符都使用了多个字节来表示。抽象类Reader和Writer中存在一个继承出来一个专门用于处理Unicode字符的单独类层次结构,它们是基于两个字节的Char值(即一个Unicode码元)进行读写操作的,而不是一个字节

类图:

image-20211107154148241

IO流的使用

对文件的操作

image-20211107154501277

对非文本文件建议使用字节流

package com.cheng.IOLearn;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class FileCopy {
    public static void main(String[] args) {
        // 1、创建文件的输入流,将文件读入到程序
        // 2、创建文件到输出流,将读取到的文件数据,写入到指定到文件
        FileInputStream fileInputStream = null;
        FileOutputStream fileOutputStream = null;
        String filePath = "//Users//chenzouquan//Desktop//未命名文件夹//截屏2021-10-20 下午5.21.49.png";
        String destPath = "//Users//chenzouquan//Desktop//未命名文件夹//text.png";
        try {
            fileInputStream = new FileInputStream(filePath);
            fileOutputStream = new FileOutputStream(destPath);
        // 定义一个字节数组,提高读取效率
        byte[] buf = new byte[1024];
        int readLen = 0;
        while ((readLen = fileInputStream.read(buf)) != -1){
            // 读取到后,就写入到文件,即边读边写
            fileOutputStream.write(buf,0,readLen);// 一定要使用这个方法
        }
        System.out.println("拷贝成功");
    } catch (IOException e) {
        e.printStackTrace();
    }finally {
          // 关流
          try {
              if (fileInputStream != null){
                  fileInputStream.close();
              }
              if (fileOutputStream != null){
                  fileOutputStream.close();
              }
          } catch (IOException e) {
              e.printStackTrace();
          }
      }
  }
}
复制代码

对文本文件建议使用字节流


package com.cheng.IOLearn;

import java.io.FileReader;
import java.io.IOException;

public class LearnFileReader {

    public static void main(String[] args) {
      	// 1.指定文本路径
        String strPath = "//Users//chenzouquan//Desktop//未命名文件夹//test.txt";
      	// 2、创建文件的输入流,将文件读入到程序
        FileReader fileReader = null;
        int data = 0;
        try {
            fileReader = new FileReader(strPath);
          	// 读取到后,就写入到文件,即边读边输出
            while( (data = fileReader.read()) != -1){
                System.out.print((char) data);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
          // 关流
            try {
                fileReader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
复制代码

在进行操作的要点:在执行完输入和输出操作后一定要记得关流(close)或者刷新流(flush),负责文件不能保存原因是在程序执行close或者flush之前并没有真正的的修改文件而是在执行close或者flush方法中进行正式的修改文件close方法中会执行flush方法所以我们一般只要进行关流就足够了。

节点流和处理流

节点流:可以从一个数据源进行读写操作(如FileReader/FileWriter,FileInputStream/FileOutputStream...)

处理流:包装一个节点流或者处理流,对它们进行业务拓展给程序提供更好的读写功能(如BufferedReader/BufferedWriter...)

image-20211107160550091

从源码上看这里使用了装饰器模式:即把一个装饰器对象中存入一个要进行操作的对象,在这个装饰器对象里对这个对象进行操作

image-20211107162602675

以BufferedReader为例,他有属性Reader所以可以封装Reader子类进行操作

package com.cheng.IOLearn;

public abstract class Reader_ {
    public void Reader_File() {
    }
}
class FileReader_ extends  Reader_{
    @Override
    public void Reader_File(){
        System.out.println("FileReader_");
    }
}
class StringReader_ extends  Reader_{
    @Override
    public void Reader_File(){
        System.out.println("StringReader_");
    }
}

/**
 * 处理流/包装流
  */
   class BufferedReader_ extends  Reader_{
   private Reader_ reader;

   public BufferedReader_(Reader_ reader) {
       this.reader = reader;
   }

   // 下面可以扩展对应功能

}
复制代码

对象流(序列化和反序列化)

序列化

储存几个数据包括数据类型

Int num; char a; String str;

public class TestObjectOutputStreamLearn {
    public static void main(String[] args) throws IOException {

        // 序列化保存文件位置
        String path = "//Users//chenzouquan//Desktop//未命名文件夹//data.dat";
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(path));

        // 序列化数据
        oos.write(100);
        oos.writeChar('a');
        oos.writeUTF("测试");
        
        // 序列化对象要让需要序列化的对象实现 Serializable接口
        oos.writeObject(new Cat());
        
        // 关流
        oos.close();
        System.out.println("序列化完毕");
    }
}
复制代码

Java对象

public class Cat implements Serializable {

    // 提供序列化版本号,这样在类修改后,序列化和反序列化的时候就会认为这是一次更新而不是一个新的类
    private static final long serialVersionUID = 1L;

    private String name = "hello";

    int age = 11;

    public String CarName(){
        return this.name;
    }

    public Cat(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Cat() {
    }
}
复制代码
反序列化
public class TestObjectOutputStreamLearn {
    public static void main(String[] args) throws IOException, ClassNotFoundException {

        // 文件位置
        String path = "//Users//chenzouquan//Desktop//未命名文件夹//data.dat";
        ObjectInputStream oos = new ObjectInputStream(new FileInputStream(path));

        // 读取数据要和序列化顺序一致
        int i = oos.readInt();
        char c = oos.readChar();
        String s = oos.readUTF();
        System.out.println(i+c+s);
        Object o = oos.readObject();

        // 如果使用原来的对象的方法和属性,要转化为原来对象
        Cat cat = (Cat) o;
        cat.CarName();

        oos.close();
    }
}
复制代码