36、【JavaSE】【Java 核心类库(下)】I/O 流(1)

1、概述

  • I/O 在计算机领域分别是英语单词 input 与 output 的缩写,也就是输入与输出,简单地讲就是读与写

  • 输入或输出的过程本质上是数据的传输,这个数据传输的过程,可以拿水流来比喻数据的流动,所以就有了 I/O 流的说法。
    I/O 流就是指读写数据时像流水一样从一端流到另外一端,因此得名为“流"。

2、Java 中 I/O 流的分类、体系

Java 中 I/O 流的分类
Java 中 I/O 流的框架图
Java 中 I/O 流体系表

3、字符流

3.1、FileWriter 类
  • java.io.FileWriter类主要用于将文本内容写入到文本文件

  • 常用方法:

FileWriter 类常用方法
/* 基本使用 */

import java.io.FileWriter;
import java.io.IOException;

public class FileWriterTest {

    public static void main(String[] args) {
        FileWriter fw = null;
        try {
            fw = new FileWriter("E:\\test\\a.txt"); // 创建一个流对象,关联一个文本文件
            fw.write('c'); // 通过流向文本文件写入数据
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fw != null) {
                try {
                    fw.close(); // 关闭流
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}

水(数据,这里是字符)通过管道(流对象)流入水桶(文件)。
若构造方法中的参数对应的文件不存在,会创建一个文件。

import java.io.FileWriter;
import java.io.IOException;

public class FileWriterTest {

    public static void main(String[] args) {
        FileWriter fw = null;
        try {
            fw = new FileWriter("E:\\test\\a.txt", true); // true 表示以追加的形式写入文件
            fw.write('e');
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fw != null) {
                try {
                    fw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}
import java.io.FileWriter;
import java.io.IOException;

public class FileWriterTest {

    public static void main(String[] args) {
        FileWriter fw = null;
        try {
            fw = new FileWriter("E:\\test\\a.txt", false); // false 表示以不追究形式写文件,如果原文件存在且有内容,重新创建一个空文件
            fw.write('j'); // 写入字符“j”
            fw.write('v'); // 写入字符“v”,注意,因为是同一个 FileWriter 对象在操作,所以,这里的写是追加写,最后文件中的内容是“jv”
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fw != null) {
                try {
                    fw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}

如果不是以追加的方式写文件(默认情况下java.io.FileWriter是以不追加的形式进行写文件,即使用FileWriter(String filePath)这种构造方法时),原来文件中的内容会被清空(相当于又重新创建了一个新的文件)。注意,这个“追加”与“不追加”是在创建java.io.FileWriter对象时起作用,并不是说:如果选择“不追加”方式,每调用一次write等方法,文件内容就被清空一次,在同一个java.io.FileWriter对象下,写入文件是追加着写。

import java.io.FileWriter;
import java.io.IOException;

public class FileWriterTest {

    public static void main(String[] args) {
        FileWriter fw = null;
        char[] arr = {'H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd'};
        try {
            fw = new FileWriter("E:\\test\\a.txt", false);
            fw.write(arr); // 写入整个字符数组,文件中是“Hello world”
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fw != null) {
                try {
                    fw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}
import java.io.FileWriter;
import java.io.IOException;

public class FileWriterTest {

    public static void main(String[] args) {
        FileWriter fw = null;
        char[] arr = {'H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd'};
        try {
            fw = new FileWriter("E:\\test\\a.txt", false);
            fw.write(arr, 0, 5); // 自 arr 数组中的下标0的字符开始写,长度为5,最后文件中的内容是“Hello”
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fw != null) {
                try {
                    fw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}

关于flush方法,很多流都提供了这个flush方法,这个方法的作用是“在水通过水管流入水桶的过程中,水管中必然会残留一些水,flush会将水管中残留的水弄干净,全部弄到水桶中去”。对于只进行一次读写来说,即像write这样的方法只调用一次,没有必要使用flush方法,但是对于像多次调用write等方法(比如在循环中使用),flush方法会用的比较多。

3.2、FileReader 类
  • java.io.FileReader类主要用于从文本文件读取文本数据内容。

  • 常用方法:

FileReader 类常用方法
/* 基本使用,文件中的内容是“Hello” */

import java.io.FileReader;
import java.io.IOException;

public class FileReaderTest {

    public static void main(String[] args) {
        FileReader fr = null;
        try {
            fr = new FileReader("E:\\test\\a.txt");
            char ch = (char) fr.read();
            System.out.println(ch); // H
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fr != null) {
                try {
                    fr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}
/* 读取文本文件中的全部内容,文件中的内容是“Hello” */

import java.io.FileReader;
import java.io.IOException;

public class FileReaderTest {

    public static void main(String[] args) {
        FileReader fr = null;
        int ch;
        StringBuilder sb = new StringBuilder();
        try {
            fr = new FileReader("E:\\test\\a.txt");
            // 使用 while 循环
            while ((ch = fr.read()) != -1) {
                sb.append((char) ch);
            }
            System.out.println(sb.toString()); // Hello
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fr != null) {
                try {
                    fr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}
/* 文件内容读取至数组中,文件中的内容是“Hello” */

import java.io.FileReader;
import java.io.IOException;

public class FileReaderTest {

    public static void main(String[] args) {
        FileReader fr = null;
        char[] arr = new char[3];
        try {
            fr = new FileReader("E:\\test\\a.txt");
            // 如果文件内容大于数组长度的话,读满数组,剩余的文件内容读不完
            // 如果文件内容小于或等于数组长度的话,数组可能读不满(有剩余空间),文件是可以读完的
            fr.read(arr);
            System.out.println(arr); // Hel
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fr != null) {
                try {
                    fr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}
/* 文件内容读取至数组中,文件中的内容是“Hello” */

import java.io.FileReader;
import java.io.IOException;

public class FileReaderTest {

    public static void main(String[] args) {
        FileReader fr = null;
        char[] arr = new char[3];
        try {
            fr = new FileReader("E:\\test\\a.txt");
            // 第二个参数的含义指的是“读取到数组时自数组的哪个下标开始保存”
            // 第三个参数的含义指的是“从文件中读取的长度”
            // 注意后两个参数,谨防发生数组越界异常
            fr.read(arr, 1, 2); 
            System.out.println(arr); // [0] 无 [1] H [2] e
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fr != null) {
                try {
                    fr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}
3.3、利用java.io.FileWriterjava.io.FileReader实现对文本文件的拷贝
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class FileCopyTest {

    private static void copyOne(String sourcePath, String targetPath) {
        FileReader fr = null;
        FileWriter fw = null;
        try {
            fr = new FileReader(sourcePath);
            fw = new FileWriter(targetPath);
            int ch = -1;
            System.out.println("正在拷贝······");
            while ((ch = fr.read()) != -1) {
                fw.write(ch);
            }
            System.out.println("完成!");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fw != null) {
                try {
                    fw.close(); // 一般写法是:后创建的流先关闭
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fr != null) {
                try {
                    fr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        copyOne("E:\\test\\a.txt", "E:\\test\\xyz.txt");
    }

}

4、字节流

  • 字节流可以处理任意类型的文件,而字符流只能处理文本文件
4.1、FileInputStream 类
  • java.io.FileInputStream类主要用于从输入流中以字节流的方式读取图像数据等这样的非文本文件(主要用于处理非文本文件)。

  • 常用方法:

FileInputStream 类常用方法
4.2、FileOutputStream 类
  • java.io.FileOutputStream类主要用于将图像数据之类的原始字节流写入到输出流中。

  • 常用方法:

FileOutputStream 类常用方法
4.3、使用字节流实现文件拷贝
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class FileStreamTest {

    public static void main(String[] args) {
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            fis = new FileInputStream("E:\\test\\c.docx");
            fos = new FileOutputStream("E:\\test\\cc.docx");
            int res = -1;
            System.out.println("正在拷贝······");
            while ((res = fis.read()) != -1) {
                fos.write(res);
            }
            System.out.println("拷贝完成!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

上面的这种方法,是一个一个字节进行拷贝,读一个字节、写一个字节。这样的效率很低!

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class FileStreamTest {

    public static void main(String[] args) {
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            fis = new FileInputStream("E:\\test\\e.doc");
            int len = fis.available();
            System.out.println("文件大小:" + len + "字节");
            byte[] buffer = new byte[len];
            fos = new FileOutputStream("E:\\test\\ee.doc");
            System.out.println("正在拷贝······");
            fis.read(buffer);
            fos.write(buffer);
            System.out.println("拷贝完成!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

上面的方法是将文件通过字节输入流一次性读完,放入一个和文件一样大小的byte类型的数组中,然后再通过字节输出流一次性写完。这个方法对于一些小文件是可行的,但是对于一些较大的文件,可能会有实际情况下的物理内存没有足够大的空间这种情况(也就意味着无法申请到与文件大小相同的byte数组)。

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class FileStreamTest {

    public static void main(String[] args) {
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            fis = new FileInputStream("E:\\test\\e.doc");
            byte[] buffer = new byte[1024]; // 合理大小的缓冲区
            fos = new FileOutputStream("E:\\test\\eee.doc");
            System.out.println("正在拷贝······");
            int res = -1;
            while ((res = fis.read(buffer)) != -1) {
                // 依次写入
                // 之所以使用 write 的这个重载方法,是因为会有“读”到最后时候,缓冲区是没有装满的,不能将多余的写入文件
                fos.write(buffer, 0, res); 
            }
            System.out.println("拷贝完成!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

上面的方法应该可以说是拷贝文件中比较好的解决方案,效率较好。与第二种方法不同,这种方法不是将文件一次性读完,不是设置与文件大小一样的缓冲区(byte类型的数组),而是说设置一个大小合理的缓冲区,分多次读、写。
缓冲区的大小(byte类型的数组长度)是要求“合理”的,这个是与实际情况密切相关的,可以从统计学的角度去确定缓冲区的大小怎么样才是合理的。一般情况下,缓冲区的大小所选择的是1024的倍数。

5、带有缓冲区的字节流

  • 之前说了,带有合理大小缓冲区能够使读写效率有所提高,Java 本身也提供了一些带有缓冲区的流供使用。Java 提供的带有缓冲区的字节流是java.io.BufferedInputStreamjava.io.BufferedOutputStream;带有缓冲区的字符流是java.io.BufferedReaderjava.io.BufferedWriter
5.1、BufferedInputStream 类
  • java.io.BufferedInputStream类主要用于描述缓冲字节输入流。

  • 常用方法:

BufferedInputStream 类的常用方法
  • java.io.BufferedInputStream在读的时候,所提供的缓冲区默认大小是8192字节。
private static int DEFAULT_BUFFER_SIZE = 8192;

public BufferedInputStream(InputStream in) {
    this(in, DEFAULT_BUFFER_SIZE);
}
  • 通过构造方法可以看出来(需要一个java.io.InputStream类型的引用作为参数),BufferedInputStream 是属于“处理流”,即是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写
5.2、BufferedOutputStream 类
  • java.io.BufferedOutputStream类主要用于描述缓冲输出流,此时不用为写入的每个字节调用系统底层。

  • 常用方法:

BufferedOutputStream 类常用方法
  • java.io.BufferedOutputStream在写的时候,默认也是8192字节。
    public BufferedOutputStream(OutputStream out) {
        this(out, 8192);
    }
  • 与 BufferedInputStream 一样,BufferedOutputStream 也是“处理流”。
5.3、使用带缓冲区的字节流实现文件拷贝
import java.io.*;

public class FileBufferStreamTest {

    public static void main(String[] args) {
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;
        try {
            bis = new BufferedInputStream(new FileInputStream("E:\\test\\e.doc"));
            bos = new BufferedOutputStream(new FileOutputStream("E:\\test\\да.doc"));

            byte[] buffer = new byte[1024]; // 自定义的一个缓冲区
            int res = -1;
            System.out.println("正在拷贝······");
            while ((res = bis.read(buffer)) != -1) {
                bos.write(buffer, 0, res);
            }
            System.out.println("拷贝完成!");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (bos != null) {
                try {
                    bos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (bis != null) {
                try {
                    bis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}

通过上面的代码,可能认为有些奇怪,java.io.BufferedInputStream等提供了缓冲区,为什么在编写代码的时候仍然需要byte[] byte = new byte[·······]
当然,也可以不这么去做,以单个字节的方式去读、写也是可以的,与FileInputStream等不同的是,有缓冲区,并不会每一次读、写都调用系统底层。
在计算机领域,使用“多层(级)缓存”是常事,因为这样会在一定程度上提高读写的效率,所以说,上面的代码中,又自定义了一个byte类型的数组作为缓冲区,是为了更进一步的提供读写效率。

6、带有缓冲区的字符流

6.1、BufferedReader 类
  • java.io.BufferedReader类用于从输入流中读取单个字符、字符数组以及字符串。

  • 常用方法:

BufferedReader 类常用方法
  • BufferedReader 流是“处理流”。
6.2、BufferedWriter 类
  • java.io.BufferedWriter类主要用于写入单个字符、字符数组以及字符串到输出流中。

  • 常用方法:

BufferedWriter 类常用方法
6.3、使用带有缓冲区的字符流对文本文件的拷贝
import java.io.*;

public class FileBufferTest {

    public static void main(String[] args) {
        BufferedReader br = null;
        BufferedWriter bw = null;

        try {
            br = new BufferedReader(new FileReader("E:\\test\\debug.log"));
            bw = new BufferedWriter(new FileWriter("E:\\test\\debug2.log"));

            char[] buffer = new char[1024];
            int len = -1;
            System.out.println("正在拷贝······");
            while ((len = br.read(buffer)) != -1) {
                bw.write(buffer, 0, len);
            }
            System.out.println("拷贝完成!");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (bw != null) {
                try {
                    bw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}
import java.io.*;

public class FileBufferTest {

    public static void main(String[] args) {
        BufferedReader br = null;
        BufferedWriter bw = null;

        try {
            br = new BufferedReader(new FileReader("E:\\test\\debug2.log"));
            bw = new BufferedWriter(new FileWriter("E:\\test\\debug3.log"));

            String res = null;
            System.out.println("正在拷贝······");
            // 按行进行拷贝
            // readLine 如果返回 null 说明已经读到了文件末尾
            while ((res = br.readLine()) != null) {
                bw.write(res);
                bw.newLine();
            }
            System.out.println("拷贝完成!");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (bw != null) {
                try {
                    bw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}

7、打印流

  • 介绍两种打印流,一种是字符打印流java.io.PrintWriter,另一种是字节打印流java.io.PrintStream

  • “打印”一词可以理解为“写”。打印流之所以叫“打印流”,是因为 Java 通过打印流实现在控制台上的输出。

  • Java 中常见的输出(打印)语句System.out.println(······),在java.lang.System类中,out是其中的静态成员变量,而其类型就是java.io.PrintStream

  • 打印流除了可以“写”到控制台,也能够“写”文件的。

public final class System {

    ······

    public static final PrintStream out = null;

    ······

}
7.1、PrintStream 类
  • 常用方法:
PrintStream 类常用方法
7.2、PrintWriter 类
  • 常用方法:
PrintWriter 类常用方法

8、转换流

  • 转换流,其作用在“转换”上,能够实现字节流与字符流之间的转换。

  • java.io.InputStreamReader类可以将字节流转换为字符流java.io.OutputStreamWriter类可以将字符流转换为字节流

8.1、InputStreamReader 类
  • java.io.InputStreamReader类主要用于实现从字节流到字符流的转换。

  • 常用方法:

InputStreamReader 类常用方法
8.2、OutputStreamWriter 类
  • java.io.OutputStreamWriter类主要用于实现从字符流到字节流的转换。

  • 常用方法:

OutputStreamWriter 类常用方法
8.3、案例

不断地提示用户输入要发送的内容,若发送的内容是“bye”则聊天结束,否则将用户输入的内容写入到文本文件(.txt)中。
要求使用 BufferedReader 类来读取键盘的输入(注:System.in代表键盘输入);
要求使用 PrintStream 类负责将数据写入文件。

  • 在写 Java 控制台程序的时候,如果需要输入的话,需要使用java.util.Scanner类,Scanner scanner = new Scanner(System.in),实际上从键盘输入的本质是流,System.injava.lang.System类中的java.io.InputStream类型的静态成员变量,System.in可以表示“键盘输入流”。

The "standard" input stream. This stream is already open and ready to supply input data. Typically this stream corresponds to keyboard input or another input source specified by the host environment or user.

所以说,对于键盘输入而言,可以不用java.util.Scanner类,只要可以将System.in作为参数,就像案例中提到的使用java.io.BufferedReader等进行键盘输入也是可以的。

  • 案例中会涉及到字符流与字节流之间的转换问题。
import java.io.*;

public class ChatTest {

    public static void main(String[] args) {
        BufferedReader br = null;
        PrintStream ps = null;

        try {
            br = new BufferedReader(new InputStreamReader(System.in)); // 字节流转换为字符流
            ps = new PrintStream(new FileOutputStream("E:\\test\\chat.txt")); // 字节打印流

            String str = null;
            System.out.println("请输入内容,回车结束:");
            while (!"bye".equals((str = br.readLine()))) {
                System.out.println("请输入内容,回车结束:");  // 写入控制台
                ps.println(str); // 写入文件
            }
            System.out.println("结束!");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (ps != null) {
                ps.close();
            }
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容