I/O 流是什么

I:Input、O:Output,即输入输出。

一个文件,从磁盘到内存,叫输入。从内存到磁盘,叫输出。通过 IO 可以完成磁盘文件的读和写。

以内存为参照物就好理解了。

i/o

流是什么,流可以认为是一种数据流向。从磁盘到内存,从内存到磁盘,这就是流向。

I/O 流的分类

按照流向,分为输入流和输出流。

按照类别分,分为字节流和字符流。

Java I/O 的四大家族

java.io.InputStream 字节输入流、java.io.OutputStream 字节输出流

java.io.Reader 字符输入流、java.io.InputStream 字符输出流

该四大家族都是抽象类。其中,所有的流都实现了 java.io.Closeable 接口,都是可关闭的,都有 close() 方法。流毕竟是一个管道,这个是内存和硬盘之间的通道,用完之后一定要关闭,不然会耗费(占用)很多资源。养成好习惯,用完流一定要关闭。

所有的输出流都实现了:java.io.Flushable 接口,都是可刷新的,都有 flush() 方法。养成一个好习惯,输出流在最终输出之后,一定要记得 flush() 刷新一下。这个刷新表示将管道当中剩余未输出的数据强行输出完,刷新的作用就是清空管道。

一些 I/O 类

文件专属:

  • java.io.FileInputStream
  • java.io.FileOutputStream
  • java.io.FileReader
  • java.io.FileWriter

转换流:(将字节流转换成字符流)

  • java.io.InputStreanReader
  • java.io.OutputStreamWriter

缓冲流专属:

  • java.io.BufferedReader
  • java.io BufferedWriter
  • java.io.BufferedInputStream
  • java.io.BufferedOutputStream

数据流专属:

  • java.io.DataInputStream
  • java.io.DataOutputStream

标准输出流:

  • java.io.PrintWriter
  • java.io.PrintStream

对象专属流:

  • java.io.ObjectInputStream
  • java.io.ObjectOutputStream

字节流

利用字节流读取文件和写入文件,这里以 test.txt 文件为例。test.txt 的内容如下。

my name is van
i am a artist
i am a performance artist
the deep dark fantasy

读取文件,磁盘 → 内存

创建的步骤大致都一样,先创建对象 → 读取文件 → 关闭流

1、先创建一个输入流

FileInputStream fis = new FileInputStream("e:\\test.txt");

2、创建文件完之后,就可以调用 read() 直接读数据了。数每读取一次,文件指针就会移动一位。当移动到文件末尾时,此时 read() 的返回值为 -1,可以用 while 循环让它一直读

int read;
while((read = fis.read()) != -1) {
    System.out.println(read);
}

3、最后关闭文件流

fis.close();

代码运行完之后,控制台输出并不是字符,而是每个字符所对应的 ascii 码,可以强制类型转换成 char 类型,输出的就都是字符了。

109
121
32
110
97
109
101
32
105
115
......

但是,上面这种方式读取文件效率太低了,因为是一个一个读。Java 还可以通过字节数组的方式读取数据,这样效率就会高很多。

@Test
public void test2() {
    FileInputStream fis = null;
    try {
        fis = new FileInputStream(path);
        byte[] bytes = new byte[12];
        int i; // 拿到的是读取的字节数,当读取不到数据的时候,直接返回 -1
        while ((i = fis.read(bytes)) != -1) {
            // 不能全部读取数组中的元素,读到哪里指针就指到哪里
            // System.out.println(new String(bytes));
            System.out.print(new String(bytes, 0, i));
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (fis != null) {
            try {
                fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

写入到文件,内存 → 磁盘

步骤:创建输出流 → 写入 → 刷新 → 关闭

1、创建文件输出流

// 默认是直接覆盖,要在文件结尾追加数据,在构造方法中另加一个参数 true,表示可追加。
FileOutputStream fos = new FileOutputStream(path, true);

2、写入数据。由于是字节,所以只能一个一个按字节写或者通过字节数组的方式来写。

byte[] bytes = new byte[]{65, 66, 67, 68};
fos.write(bytes);
fos.write(51);

3、刷新流

fos.flush();

4、关闭

fos.close();

如果不加 true 的话,那么文件中的内容是 ABCD ,加了 true 的话,那么就是文件结尾 ABCD。

复制一个视频文件

通过字节输入流和输出流的配合,可以实现一个简单的文件复制功能。

@Test
public void copyFileTest() {
    FileInputStream fis = null;
    FileOutputStream fos = null;
    try {
        System.out.println("开始复制......");
        long start = System.currentTimeMillis();
        // 读文件
        fis = new FileInputStream("D:\\fakepath\\星愿(茶理理版).mov");
        // 写文件
        fos = new FileOutputStream("E:\\星愿(茶理理版).mov");
        // 每次读取 10kb 数据
        byte[] bytes = new byte[1024 * 10];
        int read;
        while ((read = fis.read(bytes)) != -1) {
            fos.write(bytes);
        }
        // 刷新输出流
        fos.flush();
        long end = System.currentTimeMillis();
        System.out.println("耗时:" + (end - start) + " ms");
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if(fis != null) {
            try {
                fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if(fos != null) {
            try {
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

字符流

字符流是专门操作纯文本文件的,如果要操作图片,音频,word 文档就不可以了。它的使用方式和字节流一样的

读取文件中的数据,通过字符数组的方式。

@Test
public void test1() {
    FileReader reader = null;
    try {
        reader = new FileReader("e:\\test.txt");
        char[] chars = new char[5];
        int read = chars.length;
        while ((read = reader.read(chars)) != -1) {
            System.out.println(new String(chars, 0, read));
        }

    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if(reader != null) {
            try {
                reader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

将字符串写入到文件中。

@Test
public void test2() {
    FileWriter writer = null;
    try {
        writer = new FileWriter("e:\\test2.txt");
        writer.write("On a sultry evening in August, Zhang Yuwei, " +
                     "who works in sales in Shanghai, " +
                     "donned a chiffon shirt and hotpants to attend one of " +
                     "the first performances of the show in Mandarin.");
        writer.write("\n");
        writer.write("八月的一个闷热的晚上,在上海从事销售工作的张雨薇(音译)穿着雪纺纱T恤和热裤去观看" + "普通话版本的最早的几场演出。");
        writer.flush();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if(writer != null) {
            try {
                writer.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

复制纯文本文件。

@Test
public void testCopyFile() {
    FileReader fileReader = null;
    FileWriter fileWriter = null;
    try {
        fileReader = new FileReader("D:\\fakepath\\forge-1.14.4-28.2.23-installer.jar.log");
        fileWriter = new FileWriter("e:\\forge-1.14.4-28.2.23-installer.jar.log");
        char[] chars = new char[100];
        int read;
        while((read = fileReader.read(chars)) != -1) {
            fileWriter.write(chars, 0, read);
        }
        fileWriter.flush();
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if(fileReader != null) {
            try {
                fileReader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if(fileWriter != null) {
            try {
                fileWriter.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}

利用缓冲流读取文本中的一行数据。

@Test
public void test1() {
    BufferedReader bufferedReader = null;
    try {
        // 当一个流的构造方法中需要一个流的时候,这个被传进来的流叫做节点流,比如 FileReader
        FileReader fr = new FileReader("e:\\forge-1.14.4-28.2.23-installer.jar.log");
        // 外部负责包装的这个流,叫做包装流,还有一个名字叫做处理流,比如 BufferedReader
        bufferedReader = new BufferedReader(fr);
        String s;
        while((s = bufferedReader.readLine()) != null) {
            System.out.println(s);
        }
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if(bufferedReader != null) {
            try {
                bufferedReader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

标准输入输出流

什么是标准输入输出流,看似没听过,其实经常会使用,而且很频繁。System.in 就是标准输入流,System.out 就是标准输出流。

默认的标准输入流是通过键盘读取的,标准输出流是输出到控制台的。而最常用的 Scanner 类也只是对标准输入流进行了封装而已。

默认的标准输入输出流其实是这样的。

// 输入
InputStream in = System.in;
in.read();
// 输出
PrintStream out = System.out;
out.println("fmisfsdfoidk");

既然输入可以用键盘、输出可以打印到控制台上。那肯定是修改成可以通过文件来进行输入、输出结果打印到文件上。

这是将结果输出到控制台上。

@Test
public void test2() {
    PrintStream ps = null;
    try {
        ps = new PrintStream(new FileOutputStream("log.txt"));
        System.setOut(ps);
        System.out.println("Hello BestGuo!");
        System.out.println("世界全由你谱写\n" +
                "相处的时间你我已命运相连");
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    }
}

这是通过文件的方式读取数据。

/**
 * 标准输入流,通过文件的方式
 *
 * @throws Exception
 */
@Test
public void testIn() throws Exception {
    System.setIn(new FileInputStream("log.txt"));
    Scanner scanner = new Scanner(System.in);
    while (scanner.hasNext()) {
        System.out.println(scanner.nextInt());
    }
}