今天面试的时候问了一道IO流的题,鉴于之前在java基础篇的时候学过一遍,后再无使用和复习,就再跟着韩顺平老师的课件复习一遍。

IO流

IO流是什么?

当我们使用程序读取或修改本地文件时, 文件是以流的形式加载在内存中的。

  1. InputStream:输入流,从数据源加载至内存。
  2. OutputStream:输出流,从存在中加载至目的地。

image-20220902174637579

IO流有哪些分类

image-20220902174807812

输入流,输出流是最宽泛的概念,也是我们上面讲的概念。那为什么还需要分成字节流,字符流,节点流,处理流呢?

字节我们都知道:byte,是一个二进制单位,而我们的文件可以是txt(存储字符的),png(存储图片的),mp3(存储音频的),mp4(存储视频的),但传输的时候我们都将其转化为二进制,因为计算机只能识别二进制,也就是字节。所以字节流的意思就是说:将该文件以字节为单位进行读取,可以一个字节一个字节读取,或者一段字节数组进行重复读取。这样不管是什么文件都可以进行传输了。

那为啥还需要字符流呢?这是已因为字节流是直接作用于文件的,而字符流使用了缓冲区,先将数据写入缓冲区进行读写操作,当输出流close或者flush时,才会把内容写入目的地。一般我们在操作文本内容时,使用字符流会更好一些,而图片,视频如果使用字符流,可能导致文件损坏。

节点流代表的是我们实际操作数据的流,相比包装流会更贴近底层一些,也就是我们使用FileInputStream,FileOutputStream等,而包装流是封装在节点流之上,用来加入缓存,封装接口,进行性能优化等。相当于节点流是mybatis,包装流就是mybatis-plus。

image-20220903100526143

具体使用(这里我只引用常用的):

FileInputStream(节点流,字节流)

请使用 FileInputStream 读取 hello.txt 文件,并将文件内容显示到控制台

public void fileInputStream() throws Exception{
        FileInputStream fileInputStream = null;
        int readLength = 0;
        byte[] bytes = new byte[1024];
        try{
            fileInputStream = new FileInputStream("/Users/qinsicheng/IdeaProjects/IOStream/com/qinsicheng/hello.txt");
            while ((readLength = fileInputStream.read(bytes)) != -1) {
                System.out.println(new String(bytes,0,readLength));
            }
        } catch (Exception e) {
            System.out.println("哪里出现问题了");
        } finally {
            fileInputStream.close();
        }
    }

FileOutputStream(节点流,字节流)

请使用 FileOutputStream 在 hello.txt 文件,中写入 “hello,world”, 如果文件不存在,会创建
文件(注意:前提是目录已经存在.)

public void fileOutputStream() throws Exception{
    File file = new File("/Users/qinsicheng/IdeaProjects/IOStream/com/qinsicheng/hello.txt");
    if (!file.exists()) {
        file.createNewFile();
    }
    // 这里构造器如果加true,表示追加模式,如果不写,默认是覆盖原来内容
    FileOutputStream fileOutputStream = new FileOutputStream(file,true);
    try {
        byte[] bytes = "hello,world---".getBytes();
        fileOutputStream.write(bytes);
    } finally {
        fileOutputStream.close();
    }
}

图片文件的拷贝

public void fileCopy() throws Exception{
    FileInputStream fileInputStream = new FileInputStream("/Users/qinsicheng/IdeaProjects/IOStream/com/qinsicheng/wallhaven.png");
    FileOutputStream fileOutputStream = new FileOutputStream("/Users/qinsicheng/IdeaProjects/IOStream/com/qinsicheng/wallhaven_.png");
    int readLen = 0;
    byte[] bytes = new byte[1024];
    try {
        while ((readLen = fileInputStream.read(bytes)) != -1) {
            fileOutputStream.write(bytes,0,readLen);
        }
    } finally {
        fileInputStream.close();
        fileOutputStream.close();
    }
}

FileReader(节点流,字符流)

使用 FileReader 从 story.txt 读取内容,并显示

public void fileReader() throws IOException {
    FileReader fileReader = new FileReader("/Users/qinsicheng/IdeaProjects/IOStream/com/qinsicheng/hello.txt");
    int readLen = 0;
    char[] chars = new char[1024];
    try {
        while ((readLen = fileReader.read(chars)) != -1) {
            System.out.println(new String(chars,0,readLen));
        }
    } catch (IOException e) {
        throw new RuntimeException(e);
    } finally {
        fileReader.close();
    }
}

FileWriter(节点流,字符流)

使用 FileWriter 将 “风雨之后,定见彩虹” 写入到 note.txt 文件中, 注意细节.

@Test
public void fileWriter() throws IOException {
    // 使用 FileWriter   将   “风雨之后,定见彩虹”    写入到   note.txt    文件中,    注意细节.
    FileWriter fileWriter = new FileWriter("/Users/qinsicheng/IdeaProjects/IOStream/com/qinsicheng/hello.txt", true);
    try {
        fileWriter.write("风雨之后,定见彩虹");
    } finally {
        // 一定要关闭流 ,数据才会写入到文件中,或者使用fileWriter.flush();
        fileWriter.close();
    }
}

BufferReader(包装流,字符流)

使用BufferReader读取文件,并打印到控制台

@Test
public void bufferedReader() throws IOException {
    BufferedReader bufferedReader = new BufferedReader(new FileReader("/Users/qinsicheng/IdeaProjects/IOStream/com/qinsicheng/hello.txt"));
    String readLen;
    try {
        // readLine() 按照行读取,效率高
        while ((readLen = bufferedReader.readLine()) != null) {
            System.out.println(readLen);
        }
    } catch (IOException e) {
        throw new RuntimeException(e);
    } finally {
        // 这里包装流关闭时,节点流底层自动关闭了
        bufferedReader.close();
    }
}

BufferWriter(包装流,字符流)

bufferWriter 写 文件

public void bufferWriter() throws IOException {
    BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("/Users/qinsicheng/IdeaProjects/IOStream/com/qinsicheng/hello.txt"));
    try {
        bufferedWriter.write("hello");
        // 换行
        bufferedWriter.newLine();
        bufferedWriter.write("world");
    } finally {
        bufferedWriter.close();
    }
}

bufferReader,bufferedWriter 拷贝文件

public void fileCopyByBuffered() throws IOException {
    BufferedReader bufferedReader = new BufferedReader(new FileReader("/Users/qinsicheng/IdeaProjects/IOStream/com/qinsicheng/hello.txt"));
    BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("/Users/qinsicheng/IdeaProjects/IOStream/com/qinsicheng/hello_.txt"));
    String readLen;
    try {
        // 注意这里的bufferedReader.readLine()为换行符时,默认返回的是空字符,所以导致原本换行的地方,现在直接拼接了
        while ((readLen = bufferedReader.readLine()) != null) {
            if ("".equals(readLen)) {
                // 这样就能保证正常输出了,还有一点比方说:\n\n\n  实际上中间只空了两行
                readLen+="\n";
            }
            bufferedWriter.write(readLen);
        }
    } finally {
        bufferedWriter.close();
        bufferedReader.close();
    }
}

BufferInputStream(包装流,字节流)

BufferOutputStream(包装流,字节流)

BufferInputStream,BufferOutputStream拷贝文件

public void fileCopyByBufferedStream() throws IOException {
    BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("/Users/qinsicheng/IdeaProjects/IOStream/com/qinsicheng/wallhaven.png"));
    BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream("/Users/qinsicheng/IdeaProjects/IOStream/com/qinsicheng/wallhaven__.png"));
    byte[] bytes = new byte[1024];
    int readLen = 0;
    try {
        while ((readLen = bufferedInputStream.read(bytes)) != -1) {
            bufferedOutputStream.write(bytes,0,readLen);
        }
    } finally {
        bufferedInputStream.close();
        bufferedOutputStream.close();
    }
}

ObjectInputStream(对象流,字节流)

对象流就是我们将java对象进行序列化,存储在文件中,并可以通过对象流反序列化回来,要序列化的对象一定需要实现Serializable,其属性也需要实现,基本数据类型会自动变成包装类,而包装类是已经实现了的。

序列化一个dog对象到文件中,并再反序列化回来

public void objectInputStream() throws IOException, InterruptedException, ClassNotFoundException {
    // FileOutputStream()  如果没有使用追加默认 会直接将文件清空
    ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("/Users/qinsicheng/IdeaProjects/IOStream/com/qinsicheng/dog.dat"));
    try {
        objectOutputStream.writeObject(new dog("旺财",123));
        objectOutputStream.writeObject(1);
        System.out.println("输出成功");
    } finally {
        objectOutputStream.close();
    }

    Thread.sleep(500);

    // ObjectInputStream 在读取数据文件时候会判断 magic value,version value,来判断文件是否正确,如果不正确会爆出EOP错误
    ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("/Users/qinsicheng/IdeaProjects/IOStream/com/qinsicheng/dog.dat"));
    try {
        Object o = objectInputStream.readObject();
        System.out.println((dog)o);
        System.out.println(objectInputStream.readObject());
        System.out.println("读取成功");
    } finally {
        objectInputStream.close();
    }
}

ObjectOutputStream(对象流,字节流)

InputStreamReader (转化流,字节流)

为什么需要转化流?

因为字符编码问题,当我们使用BufferedReader读txt文件时,默认使用Utf-8编码,现在想用其他编码格式,就可以将字节流转为字符流,然后指定编码规则,其实字符流本身底层使用的还是字节流,只是针对字符内容做出了优化。

字节流FileInputStream 包装成字节流InputStreamReader,对文件进行读取(按照 utf-8/gbk 格式),进而在包装成BufferedReader。

public void InputStreamReader() throws IOException {
    // 将字节流FileInputStream 包装成字符流 InputStreamReader,对文件进行读取(utf-8/gbk格式),进而包装成BufferedReader
    FileInputStream fileInputStream = new FileInputStream("/Users/qinsicheng/IdeaProjects/IOStream/com/qinsicheng/hello.txt");
    // 底层默认使用的就是UTF-8  ,我们也可以使用gbk等
    InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream, "utf-8");
    BufferedReader bufferedReader = new BufferedReader(inputStreamReader);

    try {
        String s = bufferedReader.readLine();
        System.out.println("读取到的内容="+s);
    } catch (IOException e) {
        throw new RuntimeException(e);
    } finally {
        bufferedReader.close();
    }
}

OutputStreamWriter(转化流,字节流)

使用gbk编码输出文本

public void OutputStreamWriter() throws Exception {
    FileOutputStream fileOutputStream = new FileOutputStream("/Users/qinsicheng/IdeaProjects/IOStream/com/qinsicheng/hello.txt");
    OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream, "gbk");
    BufferedWriter bufferedWriter = new BufferedWriter(outputStreamWriter);
    try {
        bufferedWriter.write("hello Jvav 世界");
    } finally {
        bufferedWriter.close();
    }
}

序列化需要注意的点

  1. 读写顺序一定一致
  2. 序列化,反序列化对象,需要实现Serializable
  3. 序列化的类中加入 SerialVersionUID,提高版本的兼容性
  4. 序列化对象时,其属性也都必须实现序列化接口
  5. 序列化具有继承性

课后练习

  1. 创建文件,并写入内容
  2. 利用转化流,改变字符编码
  3. 读取properties文件,创建对象
  4. 将对象序列化
 @Test
    public void finalTest1() throws Exception{
        // 1. 判断磁盘是否有mytemp文件,如果没有就创建mytemp
        // 创建文件前需要保证该路径是否存在,如果不存在应先创建对应的包
        File file = new File("/Users/qinsicheng/IdeaProjects/IOStream/com/qinsicheng/pack1/pack2/pack3/mytemp");
        if (!file.getParentFile().exists()) {
            boolean mkdirs = file.getParentFile().mkdirs();
            System.out.println(mkdirs?"创建前提包":"创建失败");
        }
        if (!file.exists()) {
            boolean newFile = file.createNewFile();
            System.out.println(newFile?"成功创建":"创建失败");
        } else {
            // 2. 如果该文件已经存在 则输出文件已经存在,请不要重复创建了
            System.out.println("文件已经存在,请不要重复创建了");
        }

        // 3. 在该文件中追加hello , world
        FileOutputStream fileOutputStream = new FileOutputStream(file,true);
        try {
            fileOutputStream.write("hello,world".getBytes());
            System.out.println("数据写入成功");
        } finally {
            fileOutputStream.close();
        }
    }


    @Test
    public void finalTest2() throws Exception{
        // 1. 使用BufferedReader 读取一个文本文件 为每一行加一个句号,并输出到屏幕
        FileReader fileReader = new FileReader("/Users/qinsicheng/IdeaProjects/IOStream/com/qinsicheng/hello.txt");
        BufferedReader bufferedReader = new BufferedReader(fileReader);
        String readLen;
        try {
            while ((readLen = bufferedReader.readLine()) != null) {
                // 如果不加\n 默认换行只返回"",所有内容挤在一起
                System.out.println(readLen+".\n");
            }
            System.out.println("输出结束");
        } finally {
            bufferedReader.close();
        }

        // 2.如果文本是gbk模式 出现了乱码
        InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream("/Users/qinsicheng/IdeaProjects/IOStream/com/qinsicheng/hello.txt"), "gbk");
        BufferedReader bufferedReader1 = new BufferedReader(inputStreamReader);
        char[] chars = new char[1024];
        int readSize = 0;
        try {
//            while ((readSize = inputStreamReader.read(chars)) != -1) {
//                System.out.println(new String(chars,0,readSize));
//            }
            while ((readLen=bufferedReader1.readLine()) != null) {
                System.out.println(readLen);
            }
        } finally {
            bufferedReader1.close();
        }
    }

    @Test
    public void finalTest3() throws IOException {
        // 编写一个dog.properties 含有:name,age
        // 编写一个dog类,创建一个dog对象,并读取dog.properties相应的属性,并完成初始化。
        Properties properties = new Properties();
        // 这里需要注意 IDEA的properties文件默认使用的编码ISO-8859-1,要么改为utf-8,要么读的时候改变字符编码格式
        FileReader fileReader = new FileReader("/Users/qinsicheng/IdeaProjects/IOStream/com/qinsicheng/dog.properties");
        properties.load(fileReader);
        properties.list(System.out);
        // 实例化dog对象
        dog dog = new dog(properties.getProperty("name"), Integer.parseInt(properties.getProperty("age")));
        System.out.println(dog);
        // 关闭流
        fileReader.close();
        // 将dog对象序列化到dog.dat文件
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("/Users/qinsicheng/IdeaProjects/IOStream/com/qinsicheng/dog.dat"));
        objectOutputStream.writeObject(dog);
        System.out.println("输出成功");
        objectOutputStream.close();
    }

​ 我们上面列举到的实例,只是针对本地文件读取,这个最简单的,而真正需要我们去运用的是在网络IO中。不过基础还是得先打好。