一、IO流概述

学习IO流时,主要掌握如何new流对象,以及调用该流对象的方法实现读写。

1.何为IO?

Java的IO流是实现输入输出的基础,它可以方便地实现数据的输入输出操作,在Java中把不同的输入输出源(键盘、文件、网络连接等)抽象表达为“流”,通过流的方式允许Java程序使用相同的方式来访问不同的输入输出源。Java把所有传统的流类型(类或抽象类)都放在java.io包中,用以实现输入输出功能。
image.png

2.IO流分类

2.1 输入流和输出流

以内存为参照,按照流的流向来分:

  • 输入流:往内存中去,只能从中读取数据,而不能向其写入数据。
  • 输出流:从内存中出来,只能向其写入数据,而不能从中读取数据。

    2.2 字节流和字符流

    按照操作的数据单元不同,分为:

  • 字节流:一次读取一个字节,即8个二进制位,这种流是万能的,可以读取任何类型的文件,如文本文件、图片、声音文件、视频文件等。

  • 字符流:一次读取一个字符,只能读取纯文本文件,不能读取图片、声音文件、视频文件等。

字节流主要由InputStream和OutputStream作为基类,而字符流主要由Reader和Writer作为基类。

2.3 节点流和处理流

可以从\向一个特定的IO设备读\写数据的流,称为节点流,节点流也被称为低级流。当使用节点流进行输入输出时,程序直接连接到实际的数据源,和实际的输入输出节点连接。
Day18—IO流1 - 图2
处理流则用于对一个已存在的流进行连接或封装,通过封装后的流来实现数据读写功能。处理流也被称为高级流、包装流。当使用处理流进行输入输出时,程序并不会直接连接到实际的数据源,没有和实际的输入输出节点连接。
Day18—IO流1 - 图3
使用处理流好处:只用使用相同的处理流,程序就可以采用完全相同的输入输出代码来访问不同的数据源,随着处理流所包装节点流的变化,程序实际访问的数据源也发生变化。既可以消除不同节点流的实现差异,也可以提供更方便的方法来完成输入输出功能。

3.流的四大家族

Java把所有设备的有序数据抽象成流模型,Java的IO流设涉及40多个类,它们都是从如下4个抽象基类派生出来的:

  • java.io.InputStream 字节输入流基类
  • java.io.OutputStream 字节输出流基类
  • java.io.Reader 字符输入流基类
  • java.io.Writer 字符输出流基类

注:在Java中类名以Stream结尾的都是字节流,以Reader\Writer结尾的都是字符流。
特点:

  • 以上四大类都是抽象类,它们都实现了java.io.closeable接口,有close()方法,即所有流都是可关闭的,在使用完流之后一定要关闭,不然会占用很多资源。
  • 所有输出流都实现了java.io.Flushable接口,都是可刷新的,都有flush()方法。输出流在最终输出之后,一定要调用flush()方法刷新,将管道当中剩余未输出的数据强行输出完(清空管道),如果没有flush()可能会丢失数据。

    4.java.io中的16个流

    重点掌握字节流,对象流,标准输出流即可。

    4.1 文件操作流

  • java.io.FileInputStream (掌握)

  • java.io.FileOutputStream (掌握)
  • java.io.FileWriter
  • java.io.FileReader

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

  • java.io.InputStreamReader

  • java.io.OutputStreamWriter

    4.3 缓冲流

  • java.io.BufferedInputStream

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

    4.4 数据流

  • java.io.DataInputStream

  • java.io.DataOutputStream

    4.5 对象流

  • java.io.ObjectInputStream (掌握)

  • java.io.ObjectOutputStream (掌握)

    4.5 标准输出流

  • java.io.PrintWriter

  • java.io.PrintStream (掌握)

    二、字节流

    1.路径名问题

    反斜杠是转义字符,在IDEA中复制路径会自动加上一个反斜杠形成双反斜杠。或者使用\来代替反斜杠。
    IDEA中默认的当前路径是Project项目所在的根目录。

    2.FileInputStream

    File类可以使用文件名路径字符序列来创建File实例,该文件路径字符序列既可以是绝对路径,也可以是相对路径。一旦创建了File对象后,就可以调用File对象的方法来操作文件和目录。
    读取文件的几个方法:

  • int read():从输入流中一次读取一个字节。

  • int read(byte[ ] b):从输入流中将最多b.length个字节的数据读入到一个byte[ ]数组中,返回读取到的字节数量。
  • int read(byte[ ] b,int off,int len):从输入流中将最多len个字节的数据读入到一个byte数组中。

    2.1 FileInputStream初步

    1. class Test{
    2. public static void main(String[] args) {
    3. //1.创建文件对象
    4. FileInputStream fis = null;
    5. try {
    6. //IDEA中会自动会文件路径中的单斜杆加上双斜杆或用反斜杆
    7. fis = new FileInputStream("F:\\JavaCode\\test");//test文件中保存了abcde
    8. //fis = new FileInputStream("F:/JavaCode/test");
    9. //2.开始读该文件
    10. int readData = fis.read();//返回的是编码值
    11. System.out.println(readData);//97
    12. readData = fis.read();//调用一次read()方法,读指针往后移动一位,直到读空返回-1
    13. System.out.println(readData);//98
    14. } catch (FileNotFoundException e) {
    15. e.printStackTrace();
    16. } catch (IOException e) {
    17. e.printStackTrace();
    18. } finally {
    19. //3.在finally语句块中确保一定关闭流对象
    20. //关闭流的前提是不为空,避免空指针异常
    21. if (fis != null){
    22. try {
    23. fis.close();
    24. } catch (IOException e) {
    25. e.printStackTrace();
    26. }
    27. }
    28. }
    29. }
    30. }

    2.2 FileInputStream循环读

    ```java class Test{ public static void main(String[] args) {

    1. FileInputStream fis = null;
    2. try {
    3. fis = new FileInputStream("F:/JavaCode/test");
    4. //循环方式读
    5. /*while(true){
    6. int readData = fis.read();
    7. if (readData == -1){
    8. break;
    9. }
    10. System.out.println(readData);
    11. }*/
    12. //改造循环
    13. int readData = 0;
    14. while ((readData = fis.read()) != -1){
    15. System.out.println(readData);
    16. }
    17. } catch (FileNotFoundException e) {
    18. e.printStackTrace();
    19. } catch (IOException e) {
    20. e.printStackTrace();
    21. } finally {
    22. if (fis != null){
    23. try {
    24. fis.close();
    25. } catch (IOException e) {
    26. e.printStackTrace();
    27. }
    28. }
    29. }

    } }

  1. <a name="xcTWw"></a>
  2. ### 2.3 FileInputStream最终版(掌握)
  3. 前面两种方式采用read()方法读取文件,一次读取一个字节,内存和硬盘交互太频繁,时间都耗费在与硬盘交互上了,实际不采用前面两种方式,而是采用数组进行读取。
  4. ```java
  5. class Test{
  6. public static void main(String[] args) {
  7. FileInputStream fis = null;
  8. try {
  9. fis = new FileInputStream("test");//采用相对路径
  10. //采用byte数组读取
  11. byte[] bytes = new byte[4];
  12. int readCount = fis.read(bytes);//返回读取到的字节数量
  13. System.out.println(readCount);//4
  14. System.out.println(new String(bytes));//abcd,将字节数组全部转换为字符串
  15. //String(byte[] bytes):将字节数组转换为字符串
  16. readCount = fis.read(bytes);
  17. System.out.println(readCount);//1
  18. System.out.println(new String(bytes));//ebcd,将字节数组全部转换为字符串
  19. //不应该全部转换,应该读取到多少个字节转换多少个字节
  20. //String(byte[] bytes, int off, int len):从指定位置将len长度字节转换为字符串
  21. System.out.println(new String(bytes,0,readCount));//e
  22. readCount = fis.read(bytes);
  23. System.out.println(readCount);//-1,一个字节都没有读取到返回-1
  24. } catch (FileNotFoundException e) {
  25. e.printStackTrace();
  26. } catch (IOException e) {
  27. e.printStackTrace();
  28. } finally {
  29. if (fis != null){
  30. try {
  31. fis.close();
  32. } catch (IOException e) {
  33. e.printStackTrace();
  34. }
  35. }
  36. }
  37. }
  38. }
  1. class Test{
  2. public static void main(String[] args) {
  3. FileInputStream fis = null;
  4. try {
  5. fis = new FileInputStream("test");
  6. byte[] bytes = new byte[4];
  7. /*while (true){
  8. int readCount = fis.read(bytes);
  9. if (readCount == -1){
  10. break;
  11. }
  12. System.out.print(new String(bytes,0,readCount));
  13. }*/
  14. int readCount = 0;
  15. while ((readCount = fis.read(bytes)) != -1){
  16. System.out.print(new String(bytes,0,readCount));
  17. }
  18. } catch (FileNotFoundException e) {
  19. e.printStackTrace();
  20. } catch (IOException e) {
  21. e.printStackTrace();
  22. } finally {
  23. if (fis != null){
  24. try {
  25. fis.close();
  26. } catch (IOException e) {
  27. e.printStackTrace();
  28. }
  29. }
  30. }
  31. }
  32. }
  33. //abcde

3.FileInputStream其余常用方法

  • int available():返回流当中剩余的没有读到的字节数量。
  • long skip(long n):从输入流中跳过并丢弃n个字节的数据。

    1. class Test{
    2. public static void main(String[] args) {
    3. FileInputStream fis = null;
    4. try {
    5. fis = new FileInputStream("test");
    6. System.out.println("总字节数量:" + fis.available());//5
    7. //int readByte = fis.read();//读取一个字节
    8. //System.out.println("还剩下多少个字节没有读:" + fis.available());//4
    9. byte[] bytes = new byte[fis.available()];//byte数组不能创建太大,所以这种方式适合大文件读取
    10. System.out.println(new String(bytes));//不需要循环,直接读一次就可以了
    11. } catch (FileNotFoundException e) {
    12. e.printStackTrace();
    13. } catch (IOException e) {
    14. e.printStackTrace();
    15. } finally {
    16. if (fis != null){
    17. try {
    18. fis.close();
    19. } catch (IOException e) {
    20. e.printStackTrace();
    21. }
    22. }
    23. }
    24. }
    25. }
    1. class Test{
    2. public static void main(String[] args) {
    3. FileInputStream fis = null;
    4. try {
    5. fis = new FileInputStream("test");
    6. fis.skip(3);//跳过3个字节不读
    7. System.out.println(fis.read());//100,d的字节码
    8. } catch (FileNotFoundException e) {
    9. e.printStackTrace();
    10. } catch (IOException e) {
    11. e.printStackTrace();
    12. } finally {
    13. if (fis != null){
    14. try {
    15. fis.close();
    16. } catch (IOException e) {
    17. e.printStackTrace();
    18. }
    19. }
    20. }
    21. }
    22. }

    4.FileOutputStream

    文件字节输出流,将数据写到文件。

    1. class Test{
    2. public static void main(String[] args) {
    3. FileOutputStream fos = null;
    4. try {
    5. //1.创建一个文件对象,文件如果不存在则先创建一个文件
    6. //这种方式会先把文件清空再写入
    7. //fos = new FileOutputStream("myfile");//相对路径,会在Project目录下新建一个文件
    8. //以追加的方式在文件末尾写入,不会清空原文件
    9. fos = new FileOutputStream("myfile", true);
    10. //2.开始写
    11. byte[] bytes = {97, 98, 99, 100, 101};
    12. fos.write(bytes);//将该数组全部写进文件
    13. fos.write(bytes,0,2);//将该数组前2个字节写进文件
    14. String s = "国庆快乐";
    15. byte[] bs = s.getBytes(StandardCharsets.UTF_8);//将字符串转换为byte数组
    16. fos.write(bs);
    17. //3.写完之后一定要刷新
    18. fos.flush();
    19. } catch (FileNotFoundException e) {
    20. e.printStackTrace();
    21. } catch (IOException e) {
    22. e.printStackTrace();
    23. } finally {
    24. if (fos != null){
    25. try {
    26. fos.close();
    27. } catch (IOException e) {
    28. e.printStackTrace();
    29. }
    30. }
    31. }
    32. }
    33. }

    三、文件复制

    1.文件复制原理

    文件复制同样是一个输入输出过程,涉及文件的读写。
    image.png

    2.代码实现

    1. class Test{
    2. public static void main(String[] args) {
    3. FileInputStream fis = null;
    4. FileOutputStream fos = null;
    5. try {
    6. fis = new FileInputStream("myfile");//创建一个输入流对象
    7. fos = new FileOutputStream("myfile1");//创建一个输出流对象
    8. //核心:一边读一边写
    9. byte[] bytes = new byte[1024 * 1024];//一次最多复制1MB
    10. int readCount = 0;
    11. while ((readCount = fis.read(bytes)) != -1){
    12. fos.write(bytes,0,readCount);
    13. }
    14. fos.flush();//刷新
    15. } catch (FileNotFoundException e) {
    16. e.printStackTrace();
    17. } catch (IOException e) {
    18. e.printStackTrace();
    19. } finally {
    20. if (fis != null){
    21. try {
    22. fis.close();
    23. } catch (IOException e) {
    24. e.printStackTrace();
    25. }
    26. }
    27. if (fos != null){
    28. try {
    29. fos.close();
    30. } catch (IOException e) {
    31. e.printStackTrace();
    32. }
    33. }
    34. }
    35. }
    36. }

    四、字符流

    1.FileReader

    文件字符输入流,只能读取普通文本,读取文本时比较方便、快捷。

    1. class Test{
    2. public static void main(String[] args) {
    3. FileReader reader = null;
    4. try {
    5. //创建文件字符输入流
    6. reader = new FileReader("test");
    7. //开始读
    8. char[] chars = new char[4];//一次读取4个字符
    9. int readCount = 0;
    10. while ((readCount = reader.read(chars)) != -1){
    11. System.out.println(new String(chars,0,readCount));
    12. }
    13. } catch (FileNotFoundException e) {
    14. e.printStackTrace();
    15. } catch (IOException e) {
    16. e.printStackTrace();
    17. } finally {
    18. if (reader != null){
    19. try {
    20. reader.close();
    21. } catch (IOException e) {
    22. e.printStackTrace();
    23. }
    24. }
    25. }
    26. }
    27. }

    2.FileWriter

    文件字符输出流,只能输出普通文本。

    1. class Test{
    2. public static void main(String[] args) {
    3. FileWriter out = null;
    4. try {
    5. //创建文件字符输出流
    6. out = new FileWriter("test");
    7. //开始写
    8. char[] chars = {'H','e','l','l','o'};//一次读取4个字符
    9. out.write(chars);//写入一个字符数组
    10. out.write("\n");
    11. out.write("world!");//写入一个字符串
    12. out.flush();//刷新
    13. } catch (FileNotFoundException e) {
    14. e.printStackTrace();
    15. } catch (IOException e) {
    16. e.printStackTrace();
    17. } finally {
    18. if (out != null){
    19. try {
    20. out.close();
    21. } catch (IOException e) {
    22. e.printStackTrace();
    23. }
    24. }
    25. }
    26. }
    27. }

    3.复制普通文本文件

    1. class Test{
    2. public static void main(String[] args) {
    3. FileReader in = null;
    4. FileWriter out = null;
    5. try {
    6. in = new FileReader("test");
    7. out = new FileWriter("test01");
    8. //一边读一边写
    9. char[] chars = new char[1024 * 1024];
    10. int readCount = 0;
    11. if ((readCount = in.read(chars)) != -1){
    12. out.write(chars,0,readCount);
    13. }
    14. out.flush();
    15. } catch (FileNotFoundException e) {
    16. e.printStackTrace();
    17. } catch (IOException e) {
    18. e.printStackTrace();
    19. } finally {
    20. if (in != null){
    21. try {
    22. in.close();
    23. } catch (IOException e) {
    24. e.printStackTrace();
    25. }
    26. }
    27. if (out != null){
    28. try {
    29. out.close();
    30. } catch (IOException e) {
    31. e.printStackTrace();
    32. }
    33. }
    34. }
    35. }
    36. }

    五、缓冲流

    1.BufferedReader

    带有缓冲区的字符输入流,使用这些流的时候,不需要自定义char数组或者byte数组,自带缓冲。

    1. class Test{
    2. public static void main(String[] args) throws IOException {
    3. FileReader reader = new FileReader("test");//节点流
    4. BufferedReader bufferedReader = new BufferedReader(reader);//处理流
    5. String s = bufferedReader.readLine();//读一行,但不带换行符,读空返回null
    6. System.out.println(s);
    7. s = null;
    8. while ((s = bufferedReader.readLine()) != null){
    9. System.out.println(s);
    10. }
    11. bufferedReader.close();//关闭最外层的处理流即可
    12. }
    13. }

    2.字节流转换为字符流

    1. class Test{
    2. public static void main(String[] args) throws IOException {
    3. //字节输入流
    4. FileInputStream in = new FileInputStream("test");
    5. //字节输入流转换为字符输入流
    6. InputStreamReader reader = new InputStreamReader(in);//in是节点流,reader是处理流
    7. //该改造方法只能传一个字符输入流作为参数
    8. BufferedReader bufferedReader = new BufferedReader(reader);//reader是节点流,bufferedReader是处理流
    9. bufferedReader.close();//关闭最外层的处理流即可
    10. }
    11. }

    3.BufferedWriter

    1. class Test{
    2. public static void main(String[] args) throws IOException {
    3. //带有缓冲区的字符输出流
    4. BufferedWriter out = new BufferedWriter(new FileWriter("test",true));
    5. out.write("abc");
    6. out.flush();
    7. out.close();
    8. }
    9. }