JavaLearn# (12)IO流文件字节(符)流缓冲字节(符)流数据流对象流序列化

Posted LRcoding

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaLearn# (12)IO流文件字节(符)流缓冲字节(符)流数据流对象流序列化相关的知识,希望对你有一定的参考价值。

1. IO流概述

在Java中,数据的输入/输出操作,以”流“(stream)方式进行,一般位于 java.io 包中

数据源(data source)提供原始数据的原始媒介:数据库、文件、其他程序、内存、网络连接、IO设备

流是一个抽象、动态的概念,是一连串连续动态的数据集合

从源文件到目的文件,中间必须经过中转(把源数据读到程序中,再从程序输出到目的

1.1 IO流分类

  • 流的方向输入,输入是相对于程序(中转站)来说的
    • 输入流:从数据源到程序(InputStream、Reader 结尾)
    • 输出流:从程序到目的地(OutputStream、Writer 结尾)

  • 处理的数据单元
    • 字节流:以字节为单位获取数据,一般是 Stream 结尾的流(顶级类:InputStream、OutputStream)
    • 字符流:以字符为单位获取数据,一般是 Reader/Writer 结尾的流 (顶级类:Reader、Writer)
  • 处理对象不同
    • 节点流:直接数据源目的地读写数据,如 FileInputStream、FileReader
    • 处理流(包装流):不直接连接数据源或目的地,处理流的流,通过处理其他流提高程序的性能,如 BufferedInputStream、BufferedReader

节点流位于 IO操作的第一线,所有的操作都通过它们来进行。处理流是用来提高性能和程序灵活性的

1.2 IO流体系结构

InputStream 和 OutputStream

  • 最基本的字节输入输出流,其他的字节流类都继承自它们
  • 都是抽象类,不能创建实例,只能使用它们的子类

Reader 和 Writer

  • 最基本的字符输入输出流,其他的字符流类都继承自它们
  • 都是抽象类,不能创建实例,只能使用它们的子类

2. 文件【字节】流

FileInputStream

文件字节输入流:FileInputStream 文件字节输出流:FileOutputStream

/**
  *  1.创建流
  *     File file = new File("E:/readme.txt");
  *     InputStream in = new FileInputStream(file);
  */
// 简化写法
InputStream in = new FileInputStream("E:/readme.txt");
OutputStream out = new FileOutputStream("E:/readme2.txt");

/**
  *  2.使用流
  *     准备一个中转站,借助循环和中转站,使用输入流和输出流完成复制
  */
// 先读源文件的一个字节赋给 中转站n
int n = in.read();
while (n != -1) { // 读到了数据,还没有到末尾
    // 写一个字节到文件
    out.write(n);
    // 再读一个字节,赋给n
    n = in.read();
}

// 3.关闭流
in.close();
out.close();

注意:存在问题,只适用于复制小文件,因为中转站太小了,进行修改,使用字节数组,byte

// 准备一个中转站,一次读 1024 个字节
byte[] buf = new byte[1024];

// 使用输入流读取文件,将读到的内容放入到 buf字节数组中,返回的是【真正读取到】的字节
int len = in.read(buf);

while (len != -1) {
    // 将字节数组的内容写入到文件,从第 0个字节开始读,读取 len个字节
    out.write(buf, 0, len);
    len = in.read(buf);
}

3. 文件【字符】流

FileReader

文件字符输入流:FileReader 文件字符输出流:FileWriter 节点流,数据源和目的地是文件

// 创建流
Reader in = new FileReader("E:/readme.txt"); // 还可以带一个参数,默认为 false,表示不追加,直接覆盖
Writer out = new FileWriter("E:/readme2.txt", true); // 设置为 true时,在后面追加

// 使用流
int n = in.read();
while ((n = in.read()) != -1) {
    out.write(n);
}

// 关闭流
in.close();
out.close();

注意扩大中转站,字符流,需要使用 char

char[] cbuf = new char[1024];
int len;
while ((len = in.read(cbuf)) != -1) {
    // 一定要写真正读取到的文件长度
    out.write(cbuf, 0, len);
}

优化:进行异常处理

Reader in = null;
Writer out = null;
try {
    // 创建流
    in = new FileReader("E:/readme.txt");
    out = new FileWriter("E:/readme2.txt");

    // 使用流
    char[] cbuf = new char[1024];
    int len;
    while ((len = in.read(cbuf)) != -1) {
        out.write(cbuf, 0, len);
    }
} catch (IOException e) {
    e.printStackTrace();
} finally {
    // 关闭流
    try {
        // 如果没有创建好,就无需关闭
        if (in != null) {
            in.close();
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    try {
        if (out != null) {
            out.close();
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}
  • 其实只有字节流,字符流的底层还是字节流
  • 字节流可以完成所有类型文件的复制,字符流只可以完成文本文件的复制
  • 异常处理的分析:创建流、使用流要使用一次 try-catch 语句,关闭流要分开进行异常处理

4. 缓冲【字节】流

BufferedInputStream

// 创建【文件字节流】
InputStream in = new FileInputStream("E:/readme.txt");
OutputStream out = new FileOutputStream("E:/readme2.txt");

// 使用【缓冲字节流】包装字节流,提高性能
BufferedInputStream bis = new BufferedInputStream(in); // 默认输入缓冲区大小: 8192
BufferedOutputStream bos = new BufferedOutputStream(out); // 默认输出缓冲区大小: 8192

// 使用流,直接使用扩大中转站的代码
byte[] buf = new byte[1024];
int len;
while ((len = bis.read(buf)) != -1) {
    bos.write(buf, 0, len);
}

// 关闭流(只关闭顶层的即可)
bos.close();
bis.close();

有了缓冲流之后,中转站先从缓存区中读取数据,缓存区若为空,则读取一次硬盘,读8192个字节的内容,放入缓冲区

输出也是先输出到缓冲区,到满8192个字节后,一次性写到硬盘中

注意:关闭时,只关闭高层流即可,底层流自动关闭

何时将输出缓冲区的内容写入到硬盘?

  • 输出缓冲区满,自动写入硬盘(刷新 flush)
  • close() 会先刷新
  • 手动的 flush()

5. 缓冲【字符】流

BufferedReader

优化:一次读取一行,一次写一行,提高效率(普通版本,最终版本用 PrintWriter)

// 创建流
BufferedReader br = new BufferedReader(new FileReader("E:/readme.txt")); // 默认大小:8192
BufferedWriter bw = new BufferedWriter(new FileWriter("E:readme2.txt"));

// 使用流
String str;
// 一次读取一行
while ((str = br.readLine()) != null) {
    // 一次写一行
    bw.write(str);
    bw.newLine(); // 换行
}

// 关闭流
bw.close();
br.close();

readLine() 底层原理:还是一个一个字符的读取,然后 append() 放入到 StringBuilder(或者 char[] )中,直到遇到换行符,将 StringBuilder(char[])转换成 String 并返回

6. 数据流和对象流

能很方便的实现对各种基本类型引用类型数据的读写,并保留其本身的类型

  • 只有字节流,没有字符流
  • 都是处理流,不是结点流
  • 数据流:只能操作基本数据类型和字符串,对象流:还可以操作对象
  • 写入的是二进制数据
  • 写入的数据,需要使用对应的输入流来读取

DataInputStream

// 写操作
public static void write() throws IOException {
    // 创建流
    OutputStream out = new FileOutputStream("E:/readme2.txt");
    // 加上缓冲流,提高读写速度(先写到缓冲区中)
    BufferedOutputStream bos = new BufferedOutputStream(out);
    // 然后用数据流包装缓冲流
    DataOutputStream dos = new DataOutputStream(bos);    // 【DataOutputStream】

    // 使用流
    dos.writeInt(123);
    dos.writeDouble(3.14);
    dos.writeUTF("lwclick");
    dos.writeChar('学');

    // 关闭流
    dos.close();
}

// 读操作
public static void read() throws IOException {
    InputStream in = new FileInputStream("E:/readme2.txt");
    BufferedInputStream bis = new BufferedInputStream(in);
    DataInputStream dis = new DataInputStream(bis);

    // 按照写入的顺序读
    dis.readInt();
    dis.readDouble();
    dis.readUTF();
    dis.readChar();

    dis.close();
}

ObjectInputStream

public static void write() throws IOException {
    // 创建流
    OutputStream out = new FileOutputStream("E:/readme2.txt");
    // 加上缓冲流,提高读写速度(先写到缓冲区中)
    BufferedOutputStream bos = new BufferedOutputStream(out);
    ObjectOutputStream oos = new ObjectOutputStream(bos);   // 【ObjectOutputStream】

    // 使用流
    oos.writeInt(123);
    oos.writeDouble(3.14);
    oos.writeUTF("lwclick");
    oos.writeChar('学');

    // 还可以写对象
    oos.writeObject(new Date());

    // 写自定义的类时,需要实现 Serializable 序列化接口
    // public class Student implements Comparable<Student>, Serializable {}
    oos.writeObject(new Student(1, "zhangsan", 89.0));

    // 关闭流
    oos.close();
}

public static void read() throws IOException, ClassNotFoundException {
    InputStream in = new FileInputStream("E:/readme2.txt");
    BufferedInputStream bis = new BufferedInputStream(in);
    ObjectInputStream ois = new ObjectInputStream(bis);

    // 按照写入的顺序读
    ois.readInt();
    ois.readDouble();
    ois.readUTF();
    ois.readChar();
    ois.readObject();  // Date
    ois.readObject();  // Student

    ois.close();
}

7. 序列化和反序列化

什么是序列化?

  • 序列化(Serialization):将对象(内存) ------> 字节数组 字节序列(外存、网络)
  • 反序列化(DeSerialization):字节数组 字节序列(外存、网络) -------> 对象(内存)

什么时候需要序列化和反序列化?

  • 存储或传输的时候, 比如存储到外存(硬盘)中 传输到网络

如何实现序列化和反序列化?

  • 相应的类要实现 Serializable 接口

    ObjectInputStream ois = new ObjectInputStream(...);
    ois.readObject();  // 反序列化
    
    ObjectOutputStream oos = new ObjectOutputStream(...);
    oos.writeObject(new Student(1, "张三", 89));
    

需要注意的细节:

  • 实现了 Serializable 接口,是为了源码中做判断,instanceof

  • static 属性不参与序列化(如果只是希望某个属性不参与序列化,使用 transient 修饰)

  • 实现了序列化的接口,都给一个固定的序列化版本号(用IDEA自动生成)

  • 如果一个对象中,包含另一个对象的引用,想要实现序列化,那么该对象的引用,也必须实现序列化

    public class Student implements Comparable<Student>, Serializable {
        private static final long serialVersionUID = 3023320127814394755L; // 固定的版本号
        private int sno;
        private transient String name; // 不参与序列化
        private Double score;
        private Clazz clazz; // 对象的引用,也必须实现 Serializable 接口
    }
    
    // Clazz类
    public class Clazz implements Serializable {
    }
    

8. 其他流

8.1 打印流

PrintStream 只有输出流,是一个包装流,字节流

PrintStream ps = System.out;
System.out.println("Hello World!");
// 效果是一样的,都是打印到控制台
ps.println("Hello World!!!");

// 打印到文件中(但是不管什么类型,打印到文件中,都变为 String 类型)
PrintStream toFile = new PrintStream("E:/readme2.txt");
toFile.println(3.14);
toFile.println("lwclick");
toFile.println(new Date());

PrintWriter 只有输出流,是一个包装流,字符流 按行读写

// 创建输入流
BufferedReader br = new BufferedReader(new FileReader("E:/readme.txt"));
// 创建输出流(PrintWriter)
PrintWriter pw = new PrintWriter("E:/readme2.txt"); // 底层代码中,带缓冲(BufferedWriter)

String str;
while ((str = br.readLine()) != null) {
    pw.println(str);
}

br.close();
pw.close();

8.2 转换流

InputStreamReader ,读取来自键盘的一行行数据,写入到硬盘的文件中

用到了设计模式:适配器模式

Reader ----> InputStreamReader -----> InputStream

// 获取来自键盘的输入
InputStream is = System.in;
Reader reader = new InputStreamReader(is); // 将字节流 ----> 字符流     属于一种字符流
BufferedReader br = new BufferedReader(reader);

// 输出到硬盘
PrintWriter pw = new PrintWriter("E:/readme2.txt");

String str = br.readLine();
while (!"exit".equals(str)) {
    pw.println(str);
}

pw.close();
br.close();

8.3 其他流

ByteArrayInputStream bais;  // 字节数组

8.4 Java IO 中使用了哪些设计模式?

  • 适配器模式: InputStreamReader

  • 装饰模式:现组装(继承的一种替代方案)

    InputStream fis = new FileInputStream("E:/readme.txt");
    BufferedInputStream bis = new BufferedInputStream(fis);
    DataInputStream dis = new DataInputStream(bis);
    

针对字节流:

  • 顶级类:InputStream
  • 处理流的顶级类:FilterInputStream extends InputStream

9. 案例:复制文件夹

注意点:

  • 使用字节流(可能有图片、视频、音频等二进制文件)
  • 提高复制速度:使用缓冲 BufferedInputStream …
  • 涉及到知识点:IO流(文件的复制)、递归操作、File类(文件夹的创建)

思路:

  • 复制一个文件

    // 复制一个文件
    private static void copyFile(String sourceName, String destName) {
        BufferedInputStream  bis = null;
        BufferedOutputStream bos = null;
        try {
            InputStream fis = new FileInputStream(sourceName);
            bis = new BufferedInputStream(fis);
    
            OutputStream fos = new FileOutputStream(destName);
            bos = new BufferedOutputStream(fos);
    
            byte[] buf = new byte[1024];
            int len;
            while ((len = bis.read(buf)) != -1) {
                bos.write(buf, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (bos != null) {
                    bos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (bis != null) {
                    bis.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
  • 复制一个文件夹下的所有文件(不包括子文件夹)

    private static void copyDir(String sourceDirNameIO流

    JAVA之IO流

    IO流内容整理

    Java IO流

    20170831 - Q - Java IO操作

    IO流的概述与分类