# 前言

前面的三天也是将 Java・基本的东西看的差不多了,今天还是继续 java 的学习,今天该 IO 操作的学习了,勉强算是高级一点的应用了吧。既然是我个人的学习记录,其中记录的东西也是我觉得值得一写的东西,或许从整体来看不上很全,学到的东西才是自己的,碎碎念就这么多吧,再写就不礼貌了,本篇中所有代码都在 Clown_java/src/main/java/JavaSE at master・clown-q/Clown_java (github.com)

# Java I/O

想要读取硬盘 IO 设备中的内容,需要将数据传输到内存中,但是一个程序是做不到这点的,需要通过媒介操作系统来帮助我们完成和底层硬件之间的交互,硬盘中的内容都是以二进制数据的形式存储,在 JDK 中为了方便使用定义了 “流” 的概念,专门用于 IO 操作。

# 文件字节流

# 输入流

提到 IO 操作不得不提的就是文件读取和写入,这里使用到 FileInputStream 来进行一个文件读取,输入流就是吧文件中的信息输入到程序中

public class FileInputStreamTest {
    public static void main(String[] args) throws IOException {
        FileInputStream fileInputStream = new FileInputStream("test.txt");// 创建一个文件输入流,用来读取文件
        System.out.println(fileInputStream.read()); // 返回第一个字节的 ASCII 编码
        System.out.println(fileInputStream.read());
        fileInputStream.close();  // 释放资源
    }
}

image-20230728112131389

类似于水流的传输,一次 “流” 过来一个字节的数据,这里可以使用循环 “接取” 所有的数据

public class FileInputStreamTest {
    public static void main(String[] args) throws IOException {
        FileInputStream fileInputStream = new FileInputStream("test.txt");// 创建一个文件输入流,用来读取文件
        int i;
        while ((i = fileInputStream.read())!= -1){
            System.out.print((char) i);
        }
        fileInputStream.close();
    }
}

这里使用 i 来做一个赋值,本来我是这样写的

// 错误示范
public class FileOperationTest {
    public static void main(String[] args) throws IOException {
        FileInputStream fileInputStream = new FileInputStream("test.txt");// 创建一个文件输入流,用来读取文件
        while (fileInputStream.read() != -1){
            System.out.print((char) fileInputStream.read());
        }
        fileInputStream.close();
    }
}

这里一个循环其实是使用了两次 read,所以会有一部分数据不会被输出到控制台

是一个错误示范捏

虽然使用循环可以读取所有的字符,但是这种写法很麻烦,能不能一次读取全部内容呢?

当然可以,这里使用一个有参数的 read 方法

image-20230728120628337

public static void main(String[] args) throws IOException {
        FileInputStream fileInputStream = new FileInputStream("test.txt");// 创建一个文件输入流,用来读取文件
        byte[] bytes = new byte[fileInputStream.available()]; // 使用 available 方法来获取剩余字节流的数量
        fileInputStream.read(bytes);
        System.out.println(new String(bytes));
        fileInputStream.close();
    }

这种使用方式只能在目标文件是文本文件时可以使用,其他文件因为是以二进制的格式保存,读出的数据会有很多乱码信息

# 输出流

与输入流向对应,把程序中的数据输出到文件中,使用

这里的入和出的对象的程序本身

public class FileOutputStreamTest {
    public static void main(String[] args) throws IOException {
        FileOutputStream fileOutputStream = new FileOutputStream("test.txt");
        fileOutputStream.write("test".getBytes());
        fileOutputStream.flush(); // 刷新,立即生效
    }
}

image-20230728135017701

功能和 read 刚好相反,但是这里是覆盖文件写入,这里就需要调用另一个构造方法

image-20230728135844459

示例代码块

public class FileOutputStreamTest {
    public static void main(String[] args) throws IOException {
        FileOutputStream fileOutputStream = new FileOutputStream("test.txt",true);
        fileOutputStream.write("test".getBytes());
        fileOutputStream.flush();
    }
}

image-20230728135757456

# 文件拷贝

输入流输出流都了解完了,这里来简单写一下文件 copy

public class FileCopyTest {
    public static void main(String[] args) throws IOException {
        FileInputStream fileInputStream = new FileInputStream("1.jpg");
        FileOutputStream fileOutputStream = new FileOutputStream("2.jpg");
        byte[] bytes = new byte[fileInputStream.available()];
        fileInputStream.read(bytes);
        fileOutputStream.write(bytes);
        fileOutputStream.flush();
        fileInputStream.close();
    }
}

image-20230728152413367

这里就已经复制成功了,当然,这里是文件比较小,大的文件还是用循环,一块一块的复制过去

public class FileCopyTest {
    public static void main(String[] args) throws IOException {
        FileInputStream fileInputStream = new FileInputStream("1.jpg");
        FileOutputStream fileOutputStream = new FileOutputStream("2.jpg");
        byte[] bytes = new byte[1024];
        int len;
        while ((len = fileInputStream.read(bytes)) != -1){
            fileOutputStream.write(bytes,0,len);
        }
        fileOutputStream.flush();
        fileInputStream.close();
    }
}

# 文件字符流

前面简单的记录了一下字节流,流中传输的是字节,但是当想要将中文和英文都在一个文件中的内容读出来,所以字符流 “流” 中传输的是字符流就更加适合,所以字符流只适用于纯文本的文件

public class CharacterStreamIntput {
    public static void main(String[] args) throws IOException {
        FileReader reader = new FileReader("test.txt");
        int i;
        while ((i = reader.read()) != -1){
            System.out.println((char) i);
        }
        reader.close();
    }
}

image-20230728161235763

这里就可以直接将字符单个读出,当然也有对应的输出流

public class CharacterStreamOuttput {
    public static void main(String[] args) throws IOException {
        FileWriter fileWriter = new FileWriter("test.txt");
        fileWriter.write("杀菌灯sajhd");
        fileWriter.flush();
    }
}

image-20230728163006827

public static void main(String[] args) throws IOException{
        FileReader fileReader = new FileReader("test.txt");
        FileWriter fileWriter = new FileWriter("test1.txt");
        char[] chars = new char[3];
        int len;
        while ((len = fileReader.read(chars)) != -1){
            fileWriter.write(chars,0,len);
        }
        fileReader.close();
        fileWriter.flush();
    }

这里也是实现一下 copy 的功能

# File 对象

当想要获取文件或者目录信息的时候,就使用到 File 这个类

public class FileTest {
    public static void main(String[] args) {
        File file = new File("test.txt");
        System.out.println(file.exists()); // 判断文件是否存在
        System.out.println(file.getAbsoluteFile()); // 获取绝对路径
    }
}

image-20230728170320892

public class FileTest {
    public static void main(String[] args) throws IOException {
        File file = new File("test.txt");
        System.out.println(file.createNewFile()); // 创建一个文件,名字是 test.txt
    }
}

image-20230728170529114

相关的方法有很多这里不在一一列举,这里只是记录一下有这样一个东西

# 缓冲流

缓冲是用来在两种不同速度的设备之间传输信息时平滑传输过程的常用手段,主要是为了解决 cpu 和 IO 设备之间速度不匹配的问题,提高 cpu 和 IO 设备的并行性,这句话相比上课的时候会经常性的听到,Java 对其也有相对应的支持

public class BufferedInputSteamTest {
    public static void main(String[] args) throws IOException {
        FileInputStream fileInputStream = new FileInputStream("test.txt");
        BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
        System.out.println((char) bufferedInputStream.read());
    }
}

使用的方法,没有太大的变化,实例化出对象的时候会用 BufferedInputStream 进行一个包装

image-20230728174527479

这里的 read 加了一个判断,如果缓冲区中有就从缓冲区中区

image-20230728174614043

实际上对使用者来说没有什么影响,值得一提的是,使用缓冲与之前不同的就是可以重复的读取数据

public class BufferedInputSteamTest {
    public static void main(String[] args) throws IOException {
        FileInputStream fileInputStream = new FileInputStream("test.txt");
        BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
        bufferedInputStream.mark(0); // 做一个标记
        System.out.println((char) bufferedInputStream.read());
        System.out.println((char) bufferedInputStream.read());
        System.out.println((char) bufferedInputStream.read());
        bufferedInputStream.reset(); // 回到做标记的地方
        System.out.println((char) bufferedInputStream.read());
        System.out.println((char) bufferedInputStream.read());
        System.out.println((char) bufferedInputStream.read());
    }
}

image-20230728175206675

上面是输入流,下面看输出流,其实也是差不多的,这里就不再赘述

# 打印流

一直在用,但是没有太关注的一个东西 --System.out.print ()

如果使用默认的 System.out 就会直接打印到控制台

image-20230728181310175

实际上 print 是继承自 FilterOutputStream 类

public class PrintTest {
    public static void main(String[] args) throws IOException {
        PrintStream printStream = new PrintStream("test.txt");
        printStream.println("asdasd");
    }
}

可以将结果打印到文件中

image-20230729072640931

print 是输出不换行,println 输出换行,printf 格式化输出

# 对象流

前面 “流” 中控制的数据都是基本数据类型,实际上对象也是支持这种操作的,通过序列化和反序列化的操作来实现,其实之前的序列化和反序列化的学习,urldns,CC 的学习都一直在使用对象流,这里也算是对这块做一个补充,先看下面这个例子

public class ObjectStreamTest {
    public static void main(String[] args) throws IOException {
        FileOutputStream fileOutputStream = new FileOutputStream("test.txt");
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
        Map<Integer,String> map = new HashMap<>();
        map.put(1,"AAA");
        map.put(2,"BBB");
        map.put(3,"CCC");
        objectOutputStream.writeObject(map);
        objectOutputStream.flush();
    }
}

这里将 map 序列化后存入文件 test.txt

image-20230729075249859

当然既然有序列化,也有反序列化

public class ObjectStreamTest {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        FileOutputStream fileOutputStream = new FileOutputStream("test.txt");
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
        Map<Integer,String> map = new HashMap<>();
        map.put(1,"AAA");
        map.put(2,"BBB");
        map.put(3,"CCC");
        objectOutputStream.writeObject(map);
        objectOutputStream.flush();
        FileInputStream fileInputStream = new FileInputStream("test.txt");
        ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
        System.out.println(objectInputStream.readObject());
    }
}

image-20230729080104713

可以看到反序列化也是成功的,他的返回值是一个 object

image-20230729080212000

一个类想要使用这种 “流” 的传输,需要实现 Serializable 接口。值得注意的是,如果一个成员属性不想要被序列化和反序列化使用关键字 transient 修饰即可