• 流定义

    • 一个流可以理解为一个数据的序列。
  • Java中的I/O流

    • Java中I/O是以流为基础进行输入输出的,所有数据被串行化写入输出流,或者从输入流读入。
    • Java把这些不同来源和目标的数据都统一抽象为数据流。
    • Java语言的输入输出功能是十分强大而灵活的,美中不足的是看上去输入输出的代码并不是很简洁,因为你往往需要包装许多不同的对象。
  • Java流的分类

按流向分:

  • 输入流:程序可以从中读取数据的流。
  • 输出流:程序能向其中写入数据的流。

按数据传输单位分:

  • 字节流:以字节为单位传输数据的流
  • 字符流:以字符为单位传输数据的流

按功能分

  • 节点流:用于直接操作目标设备的流
  • 过滤流:是对一个已存在的流的链接和封装,通过对数据进行处理为程序提供功能强大、灵活的读写功能。
  • Java.io包
    • JDK所提供的所有流类位于java.io包中
    • Java.io包几乎包含了所有操作输入、输出需要的类

所有这些流类代表了输入源和输出目标。

  • Java.io包涉及的领域很广泛,包括

标准输入输出、文件的操作、网络上的数据流、字符串流、对象流、zip文件流。

  • 注意事项
    1. 使用后必须关闭流
    2. 合理选用合适的流能大大提升读写效率
    3. 尽量使用BufferedXx(缓冲流),减少访问次数等提升效率
    4. 字符流使用了缓冲区,而字节流没有使用缓冲区。使用字符流未关闭流时,不会有输出。
    5. 如果是音频文件、图片、歌曲,就用字节流好点如果是关系到中文(文本)的,用字符流好点,字符流处理的单元为 2 个字节的 Unicode 字符,分别操作字符、字符数组或字符串而字节流处理单元为 1 个字节,操作字节和字节数组。注意:进行转码,避免出现乱码问题,字节流通常处理二进制文件
    6. 字节流**默认不使用缓冲区**;字符流**使用缓冲区**

1 Java.io包结构图

image.png

  • java.io包中最重要的就是五个类和一个接口。
    • 五个类指的是**File**和四大抽象类**OutputStream****InputStream****Writer****Reader**
    • 一个接口指的是**Closeable**
  • 很多I/O工具类都实现了**Closeable**接口,因为需要进行资源的释放。
  • 所有关于流的类都继承自四大抽象类

**InputStream**/**OutputStream**即输入流、输出流是用于读取或写入字节的。
**Reader**/**Writer**则是用于操作字符,增加了字符编解码等功能,适用于类似从文件中读取或者写入文本信息。本质上计算机操作的都是字节,不管是网络通信还是文件读取,Reader/Writer相当于构建了应用逻辑和原始数据之间的桥梁。

  • 在上述抽象类基础上,**BufferedInputStream****BufferedOutputStream****BufferedReader**以及**BufferedWriter**实现了缓冲区,可以避免频繁的磁盘读写,进而提高I\O处理效率

这种设计利用了缓冲区,将批量数据进行一次操作,但在使用缓冲输出流时需要进行**flush()**操作

2 字符流

1 BufferedReader类

  • BufferedReader类继承自Reader类,可以从控制台、文件等读取字符

  • 构造**BufferedReader**类实例

    • BufferedReader类的构造函数如下,需要接收一个**Reader**类型参数

BufferedReader(Reader in)
那么对于不同的数据源,如控制台和文件,**Reader**参数的处理是不同的

  • 绑定到控制台字符流

    • **System.in**
      • Java的控制台输入由**System.in**完成。
      • System.in继承自InputStream,因此是一个输入字节流
    • 由于System.in是字节流,需要使用转换流**InputStreamReader****System.in**转换为字符流
      1. BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
  • 绑定到文件的字符流

    • 计算机是以字节为单位存储文件的,因此首先需要创建FileReader对象,把字节流转换为字符流,FileReader是继承自**InputStreamReader**的。
    • FileReader的构造函数接收表示文件路径的字符串参数和File类型参数
      1. File file = new File("123456.txt");
      2. BufferedReader bufferReader = new BufferedReader(new FileReader(file));
  • 读取字符流**BufferedReader**的方法:**bf.read()****bf.readline()**

BufferedReader对象创建后,我们便可以使用其read()方法从中读取一个字符,或者用readLine()方法读取一个字符串。
注意:bf.read()bf.readline()的区别于Scanner类的动态方法nextLine()nextInt()等的区别完全一致,如空白符的接收。

  • 实例 ```java System.out.println(“请输入一个字符和一个字符串:”); BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

char c = (char)br.read(); br.readLine(); //接收一个换行符 String str = br.readLine();

System.out.println(“c: “ + c); System.out.println(“string: “ + str);

  1. - 上述代码的输入与输出为
  2. ```java
  3. 请输入一个字符和一个字符串:
  4. a
  5. cuiyichen
  6. c: a
  7. string: cuiyichen
  • 使用Scanner类也可以来获取字符输入(如控制台、文件等),但是**BufferedReader****Scanner**是有区别的
    • **BufferedReader**是支持同步的,而**Scanner**不支持。

如果我们处理多线程程序,应当使用BufferedReader

  • **BufferedReader**相对于**Scanner**来说要快一点,因为Scanner对输入数据进行类解析,而 BufferedReader只是简单地读取字符序列。
  • BufferedReader相对于Scanner有足够大的缓冲区内存。

Scanner有很少的缓冲区(1KB字符缓冲)相对于BufferedReader(8KB字节缓冲),但是这是绰绰有余的。

###BufferedReader类API(java.io包)

构造函数

  • **BufferedReader(Reader in)**

接收一个字节流参数,构造一个BufferedReader实例

  • 可以使用InputStreamReader/FileReader将字节流System.in和文件File转换为字符流

动态方法

  • **int bf.read() throws IOException**

从输入流读取一个字符并把该字符作为整数值返回

  • 当流结束的时候返回**-1**
  • 以空白符作为结束符
    • **String bf.readLine( ) throws IOException**

从输入流读取一行字符串并返回

  • 以换行符为结束符。

2 向控制台输出(PrintStream类)

  • 控制台的输出由System.out.printf()System.out.print()System.out.println()方法完成。

这些方法都由类**PrintStream**定义并实现,而**System.out**实际上是**PrintStream**对象的一个引用

  • PrintStream继承自OutputStream类,并且实现了OutputStreamwrite()方法。

因此,write()也可以用来往控制台写操作,但不常用。

3 操作文件

1 文件的读取与写入

  • 文件的输入(读取)

读取文件,类似于读取“标准输入流”,需要构造一个Scanner对象。
与读取“标准输入流”不同的是,该对象需要指定文件路径与字符编码,而不是System.in

  1. Scanner in = new Scanner(Path.of(“c:\\mydirectory\\myfile.txt”), StandardCharsets.UTF_8);
  • 文件路径中包含反斜杠符号,需要再加一个额外的反斜杠转义
  • 例子中指定了UTF-8字符编码,读取一个文本文件时,必须指定它的字符编码
  • 构造完成后,就可以使用任何一个Scanner方法对文件进行读取。
  • 如果文件不存在,抛出异常。
  • 文件的输出(写入)

A. 覆盖式写入
覆盖式和追加式写入文件,都需要构造一个**PrintWrite**类对象,但构造方式不同。
对于覆盖式写入,**PrintWrite**类对象的构造方式:

  1. PrintWriter out = new PrintWriter(“c:\\mydirectory\\myfile.txt”, StandardCharsets.UTF_8)
  • PrintWriter(String filePath, String encoding) (java.io.*)

将数据流连接到指定文件,第一个参数指定文件路径,第二个参数指定字符编码。注意此处文件路径的类型为**String**而不是**Path**

  • 如果文件不存在,创建该文件
  • PrintWriter构造完成后,可以像输出到System.out一样使用printprintln以及printf命令。

    1. //将内容x输出到文件中
    2. PrintWriter.print(x);
    3. PrintWriter.println(x);
    4. PrintWriter.printf(x);
  • 写入完成后一定要关闭输出流,避免数据丢失!

  • 构造PrintWriter对象后直接对其写入为覆盖式写入,即文件原有内容清空

B. 追加式写入
追加式写入需要构造一个**FileWriter**类对象

  1. FileWriter fw=new FileWriter("d:\\test.txt", StandardCharsets.UTF_8, true);
  • FileWriter(String filePath, String encoding, boolean append) (java.io.*)

第一个参数指定文件的路径,第二个参数指定字符编码,第三个参数指定是否追加该文件。

  • 将之前构建的FileWriter对象作为参数传给PrintWriter的构造函数。

    1. PrintWriter out = new PrintWriter(fw);
  • PrintWriter对象构造完成后,就可以向其进行追加式写入

2 File类

  • File类定义
    • File类是文件路径目录路径的抽象表示。
    • File类无法对文件本身的内容进行操作

File类只是对文件路径的描述以及获得文件的周边信息(例如文件的字节大小、文件的路径、文件的修改时间、文件是否可读可写等信息),并不能对文件的内容进行操作。

  • File类更像是路径的封装类
  • 创建File类实例

由于File类是路径的抽象表示,因此构建File对象时需要传入表示路径的参数

  • 具体构造器见File类API
  • 创建File对象,只是把字符串路径封装为File对象,不考虑路径的真假情况

路径可以是存在的,也可以是不存在的

  • 路径

路径分为绝对路径和相对路径

  • 绝对路径

绝对路径是一个完整的路径,是以盘符(如C:)开始的路径,如

  1. c:\\a.txt
  2. C:\\Users\itcast\\IdeaProjects\\shungyuan\\123.txt
  3. D:\\demo\\b.txt
  • 相对路径

相对路径是一个简化的路径,相对指的是相对于当前项目的根目录(如F:\work\IDEAProject\test)
如果文件123.txt位于当前项目的根目录,那么路径可以简化书写即将F:\work\IDEAProject\test\123.txt简化为123.txt

  • 分隔符

Windows和Linux系统的文件路径分隔符是不同的,如下

  • Windows:C:\develop\a\a.txt
  • Linux:C:/develop/a/a.txt

File类提供了类常量separator该常量是与系统有关的分隔符,被表示为一个字符串,则上述路径可改写为
“C:”+File.separator+”develop”+File.separator+”a”+File.separator+”a.txt”

  1. File file = new File("F:\\work\\IDEAProject\\test\\123.txt");
  2. File file2 = new File("F:" + File.separator + "work" + File.separator + "IDEAProject" +
  3. File.separator + "test" + File.separator + "123.txt");
  4. System.out.println(file.exists()); //out: true
  5. System.out.println(file2.exists()); //out: true
  • File类主要用于文件\目录的创建、查找、删除,文件\目录相关信息的查询等,具体功能见File类API

  • 实例

    • U盘发现器

      1. static void checkUSBDisk() {
      2. File[] files = File.listRoots();
      3. while (true) {
      4. //判断之前的盘符是否少于当前的盘符(判断U盘是否插入)
      5. if (files.length < File.listRoots().length) {
      6. ArrayList files1 = new ArrayList(Arrays.asList(files));
      7. ArrayList files2 = new ArrayList(Arrays.asList(File.listRoots()));
      8. //剔除原有的盘符
      9. files2.removeAll(files1);
      10. //取出多出来的盘符,并通过提示框输出
      11. for (Object f : files2)
      12. JOptionPane.showMessageDialog(null, "U盘插入:" + f);
      13. files = File.listRoots(); //更新盘符
      14. }
      15. //判断U盘是否拔出
      16. else if (files.length > File.listRoots().length) {
      17. ArrayList files1 = new ArrayList(Arrays.asList(files));
      18. ArrayList files2 = new ArrayList(Arrays.asList(File.listRoots()));
      19. files1.removeAll(files2);
      20. for (Object f : files1)
      21. JOptionPane.showMessageDialog(null, "U盘拔出:" + f);
      22. files = File.listRoots();
      23. }
      24. //让线程休眠一段时间,避免死循环耗费太多资源
      25. try {
      26. Thread.sleep(1000);
      27. } catch (InterruptedException e) {
      28. e.printStackTrace();
      29. }
      30. }
      31. }

      程序输出
      image.pngimage.png

    • 检索目录下的文件

      1. public class MyClass {
      2. public static void main(String[] args) {
      3. listDirectory("F:\\work\\IDEAProject\\test");
      4. }
      5. static void listDirectory(String pathname) {
      6. File f = new File(pathname);
      7. if (f.isDirectory()) {
      8. System.out.println("###Directory of [" + pathname + "]###");
      9. String[] files = f.list();
      10. for (String filename : files) {
      11. File file = new File(pathname, filename);
      12. if (file.isDirectory())
      13. System.out.println("[" + filename + "] is a directory.");
      14. else if (file.isFile())
      15. System.out.println("[" + filename + "] is a file.");
      16. }
      17. } else
      18. System.out.println(pathname + " is not a directory!");
      19. }
      20. }

      程序输出

      1. ###Directory of [F:\work\IDEAProject\test]###
      2. [.idea] is a directory.
      3. [123456.txt] is a file.
      4. [FirstProject] is a directory.
      5. [out] is a directory.
      6. [src] is a directory.
      7. [test.iml] is a file.


      ###File类API(java.io包)

      构造器

  • 无论路径是否存在文件或目录,都不影响File对象的创建

  • **File(String pathName)**

根据给定路径名字符串创建一个File实例

  • 路径可以是以文件结尾,也可以是以文件夹结尾
  • 路径可以是相对路径,也可以是绝对路径
  • 路径可以是存在,也可以是不存在
  • **File(String parent, String child)**

根据父路径名字符串和子路径名字符串创建一个File实例。

  • 把路径分成了两部分,父路径和子路径可以单独书写,使用起来非常灵活,父路径和子路径都可以变化
  • **File(File parent, String child)**

根据父抽象路径名和子路径名字符串创建一个File实例。

静态方法

  • **File[] File.listRoots()**

返回一个File数组,其内表示的是机器盘符

动态方法

  • **String file.getAbsolutePath()**

返回file的绝对路径

  • **String file.getPath()**

返回创建file时使用的路径

  • file.toString()方法内部就是调用的file.getPath()方法
    • **String file.getParent()**

获得file表示的文件\目录的完整父目录(和构造函数中的父目录无关)

  • 如果没有上级目录,则返回null
    • **String file.getName()**

返回file表示的文件\目录的名称

  • **long file.length()**

返回file表示的文件的大小(字节为单位)

  • 如果file表示的是目录的路径,则返回的值不是固定的,且没有什么意义
  • **boolean file.exists()**

返回file表示的文件\目录是否实际存在

  • **boolean file.isDirectory**

返回file表示的是否为目录

  • **boolean file.isFile()**

返回file表示的是否为文件

  • **boolean file.canExecute()**
  • **boolean file.canRead()**
  • **boolean file.canWrite()**

判断file是否可读/可写/可执行

  • **boolean file.createNewFile() throws IOException**

如果file表示的文件不存在,则创建相应文件并返回true,否则不创建文件并返回false

  • 如果文件的上级目录不存在,则抛出**IOException**异常
    • **boolean file.mkdir()**

创建目录,如果要创建的目录已经存在或上一级目录不存在则返回false,否则返回true

  • **boolean file.mkdirs()**

创建多级目录,如果要创建的目录已经存在则返回false,否则返回true

  • **boolean file.delete()**

删除文件\目录,删除成功则返回true,不存在相应文件\目录则返回false

  • 目录必须为空才能删除
  • **boolean file.renameTo(File f)**

剪切并重命名file为f。

  • 此方法可以将指定路径下的文件剪切到另一个路径下,并且能够重命名。
  • 这个方法实际上是把原来的文件复制了然后粘贴到指定的路径下后再把原来的文件删除了,所以需要重新写文件名才可以实现剪切
  • **String[] file.list()**

返回一个字符串数组,其中的值是file表示的目录下的所有文件名和目录名。

  • 如果file表示的是文件,则返回null
    1. File file = new File("F:\\work\\IDEAProject\\test");
    2. System.out.println(Arrays.toString(file.list()));
    3. //out: [.idea, 123456.txt, FirstProject, out, src, test.iml]

3 FileInputStream类和FileOutputStream类

  • 两个类分别继承自**InputStream****OutputStream**分别用于按字节从文件中读取数据和向文件写入数据

  • 读取和写入时用于表示一个字节字母和数字的是它们的哈希值

FileInputStream

  • **FileInputStream**类用于从文件读取数据

  • 构造**FileInputStream**类实例

有两个构造方法可以用来创建FileInputStream对象。

  • 使用字符串类型的文件名来创建一个FileInputStream对象

    1. InputStream f = new FileInputStream("C:/java/hello");
  • 使用一个File对象来创建一个FileInputStream对象

    1. File f = new File("C:/java/hello");
    2. InputStream in = new FileInputStream(f);
  • 创建了FileInputStream对象,就可以使用其方法来读取流或者进行其他的流操作。

###FileInputStream类API(java.io包)

构造函数

  • **FileInputStream(String name) throws FileNotFoundException**
  • **FileInputStream(File file) throws FileNotFoundException**

构造FileInputStream对象,可以传入字符串文件路径或File对象来指定目标文件位置

动态方法

  • **int fis.read()throws IOException**

读取流中一个字节的数据并返回。

  • 返回类型为整型,如果已经到结尾则返回-1
    • **int fis.read(byte[] r)throws IOException**

读取流中r.length长度字节的数据并存至数组r,如果r.length大于流中实际

  • 返回实际读取的字节数。
  • **int fis.available() throws IOException**

返回流中还可以读取到的字节数。

  • **void fis.close() throws IOException**

关闭流并释放与此流有关的所有系统资源。

FileOutputStream

  • **FileOutputStream**类用来向文件中写数据。

    • 如果该流在打开文件进行输出前,目标文件不存在,那么该流会创建该文件
  • 构造**FileOutputStream**类实例

和构造FileInputStream对象一样,同样有两个构造方法可以用来创建FileOutputStream对象。

  • 使用字符串类型的文件名来创建一个FileOutputStream对象
  • 使用一个File对象来创建一个FileOutputStream对象
  • 创建了FileOutputStream对象,就可以使用其方法来写入流或者进行其他的流操作。

  • 虽然FileOutputStream类是输出流,但是该类没有重写其父类OutputStreamflush()方法,因此输出时是不需要调用**fos.flush()**方法的,即每次调用fos.write()方法时,流中的数据就完成了输出。

###FileOutputStream类API(java.io包)

构造函数

  • **FileOutputStream(String name) throws FileNotFoundException**
  • **FileOutputStream(File file) throws FileNotFoundException**

构造FileOutputStream对象,可以传入字符串文件路径或File对象来指定目标文件位置

  • 写入文件的方式为覆盖
  • **FileOutputStream(String name, boolean append) throws FileNotFoundException**
  • **FileOutputStream(File file, boolean append) throws FileNotFoundException**

构造FileOutputStream对象,可以传入字符串文件路径或File对象来指定目标文件位置

  • 写入文件的方式由参数append控制,true为追加式写入,false为覆盖式写入

动态方法

  • **void fos.write(int w) throws IOException**

把一个字节数据w写入输出流中

  • **void fos.write(byte[] w) throws IOException**

把指定数组中w.length长度的字节写到输出流中

  • **void fos.close() throws IOException**

关闭流并释放与此流有关的所有系统资源。

FileInputStream类和FileOutputStream类实例
实例1:将“cui yi chen”写入文件test.txt,在读出到控制台

  • 由于直接使用字节流进行写入和读取,因此涉及byte[]和String的相互转换 ```java String str = “cui yi chen”; FileOutputStream fos = new FileOutputStream(“test.txt”); fos.write(str.getBytes()); fos.close();

FileInputStream fis = new FileInputStream(“test.txt”); byte[] bytes = new byte[fis.available()]; fis.read(bytes); System.out.println(new String(bytes)); //out: cui yi chen fis.close();

  1. - 上述代码中,如果输入文件的内容包含中文就会出现乱码,因为`**FileOutputStream**`**是字节流,将文本按字节写入文件,而一个汉字是两个字节(或三个字节),无法一次写入,就会出现乱码。**
  2. 一种解决方法是**使用转换流**`**OutputStreamWriter**`**将字节输出流转换为字符输出流,同时指定utf-8编码**
  3. - 输入时同样需要使用转换流`InputStreamReader`将字节流转换为字符流
  4. - **由于输入输出使用的都是字节流,因此**`**write()**`**和**`**read()**`**使用Stringchar[]作为参数**
  5. ```java
  6. OutputStreamWriter osw =
  7. new OutputStreamWriter(new FileOutputStream("test.txt"), "utf-8");
  8. osw.append("崔奕宸\n");
  9. osw.append("最牛B!");
  10. osw.close();
  11. InputStreamReader isr = new InputStreamReader(new FileInputStream("test.txt"), "utf-8");
  12. while(isr.ready())
  13. System.out.print((char)isr.read());
  14. //out: 崔奕宸
  15. // 最牛B!

实例2:魔力混剪

  1. File melody = new File("Ash - Melody (Original mix).mp3");
  2. File linjj = new File("林俊杰 - 交换余生.mp3");
  3. File outFile = new File("魔力混剪.mp3");
  4. FileInputStream fis1 = new FileInputStream(melody);
  5. FileInputStream fis2 = new FileInputStream(linjj);
  6. byte[] bytes = new byte[1024 * 8]; //每次取一首歌的1kB
  7. FileInputStream fis = fis1;
  8. boolean flag = true; //true: fis1 false: fis2
  9. FileOutputStream fos = new FileOutputStream(outFile);
  10. while (fis.read(bytes) != -1) {
  11. fos.write(bytes);
  12. fis.skip(1024 * 8); //跳着玩一下,可以不跳
  13. if (flag) {
  14. flag = false;
  15. fis = fis2;
  16. } else {
  17. flag = true;
  18. fis = fis1;
  19. }
  20. }
  21. fis1.close();
  22. fis2.close();
  23. fos.close();