Java IO 第2篇:IO 流,掌控一切

IO 流,掌控一切

上一篇文章我们认识了文件操作的源头 File 类,这篇文章就来聊聊文件操作的核心 IO 流。
我们经常可以听到:输入流、输出流、字节流、字符流、节点流、处理流等词语,咋一听,忍不住“哇~~~!”的一声,心里在想:“感觉好复杂的样子,学习 IO 流需要知道这么多东西啊!”,从而有了畏难的情绪。大家千万不要被这些词语吓到,望而却步,它们只不过是从三个维度对 IO 流的总结。
学习 IO 流是有套路的,通过这篇文章的学习,你一定能掌握 IO 流的使用技巧,从而掌控一切文件操作问题。

一、认识 IO 流

1、IO 流的分类

从 IO 流的流向来划分,IO 流分为:输入流、输出流。

从 IO 流要处理的数据来划分,IO 流分为:字节流、字符流。其中,字节流可以处理一切文件数据,包括纯文本,word文档,pdf文档,图片,音频和视频等二进制数据;字符流只能处理纯文本文件。

从 IO 流的功能来划分,IO 流分为:节点流和处理流。其中,节点流是用来包装数据源(File)的,它直接和数据源连接,表示从一个节点读取数据或者把数据写入到一个节点;处理流是用来包装节点流的,它是对一个已经存在的节点流进行连接,处理流通过增加缓存的方式来提高输入输出操作的性能。

总的来说,java.io 包中流的操作主要分为字节流和字符流两类,他俩都有对应的节点流与数据源进行连接,为了提高文件操作的性能,在节点流的基础上提供了处理流,以便增强节点流的功能,同时他俩都有输入和输出操作。

通过上面的分类,大家先对 IO 流先有一个初步的了解,后面结合代码给大家进一步讲解。

2、区分流的输入与输出

在程序中所有的数据都是以流的方式进行传输的,程序需要数据的时候就用输入流读取数据,当程序需要将计算好的数据进行保存到文件或者输出到其他系统时,就用输出流写出数据。

简单来说的话,就是以我们的程序为中心,如果是外部的数据流向程序,那么就是输入流,输入流一定是读取操作;如果是程序里的数据流出到外部,那么就是输出流,输出流一定是写出操作。

输入输出流与文件和程序的关系示意图

3、IO 操作的套路

Java 中 IO 操作也是有套路的,有标准的操作步骤,主要的操作步骤如下:

1、使用 File 类与文件建立联系

2、选择对应的输入流或者输出流

3、进行读或写操作

4、关闭资源

先对这个套路进行一个了解,后面结合代码一下就明白了,原来套路如此简单。

二、万能钥匙字节流

1、认识字节流

字节流主要操作 byte 类型数据,说它是万能钥匙,是因为它可以处理一切文件,包括文本、word文档、Excel文档、pdf文档、图片、语音、视频等,统统都可以处理。

字节流分为字节输入流和字节输出流,在 Java 中 字节输入流用 InputStream 表示,字节输出流用 OutputStream 表示。

字节输入流:InputStream 是一个抽象类,必须依靠其子类 FileInputStream 来读取文件内容,输入到程序中。我们常用的方法是:

int read(byte b[]) //读取byte数组中的内容,返回读入的长度
close() //关闭资源

字节输出流:OutputStream 是一个抽象类,必须依靠其子类 FileOutputStream 来读取文件内容,输入到程序中。我们常用的方法是:

//将一个制定范围的byte数组输出
void write(byte b[], int off, int len) 
close() //关闭资源
flush() // 在关闭资源的时候默认会调用刷新方法

2、字节输出流 FileOutputStream 的使用

我们来看一个例子,把“演示字节输出流的使用\r\n用 FileOutputStream 类操作!”的文本输出到 D:/file/txt/output.txt 文件中。

因为文件操作有可能发生 FileNotFoundException 和 IOException,为了精简代码,便于阅读主要代码,除了本例子以外,后续的例子我会直接使用 throws 关键字抛出异常,并且关闭资源也不放在finally里,这样可以减少 try...catch...finally的代码。

@Test
  public void testOutput() {
    // 1、建立联系, File对象, 输出文件的地址
    // 如果文件不存在则可以创建文件并写入,
    // 但是如果加了文件夹,那么文件夹不存在则会产生FileNotFoundException,系统找不到指定的路径
    String path = "D:/txt/output.txt";
    File file = new File(path);
    // 2、选择流
    // 由于os要在finally中用到,放到try的外部,以提升os的变量作用范围
    OutputStream os = null;
    try {
      // 用FileOutputStream子类实例化父类OutputStream
      // 以追加的方式输出到文件,必须是true,否则就会覆盖原有的文件
      os = new FileOutputStream(file, true);
      // 3、操作
      String info = "演示字节输出流的使用\r\n用 FileOutputStream 类操作!\r\n";
      byte[] b = info.getBytes();// 字符串转字节数组
      os.write(b, 0, b.length);// 写出
      // 要养成这个习惯,为了避免缓存没有写出去,需要显示地flush一下
      os.flush();
    } catch (FileNotFoundException e) {
      e.printStackTrace();
      System.out.println("文件不存在");
    } catch (IOException e) {
      e.printStackTrace();
      System.out.println("文件写出失败");
    } finally {
      try {
        // 4、释放资源
        if (os != null) {
          os.close();
        }
      } catch (Exception e2) {
        System.out.println("关闭文件输出流资源失败");
      }
    }
  }

运行结果:


字节输出流 FileOutputStream 的使用

3、字节输入流 FileInputStream 的使用

上面的例子我们学会了字节输出流的使用,下面用字节输入流 FileInputStream 来读取上面的文件内容。

@Test
  public void testInput() throws IOException {
    // 1、建立联系
    File file = new File("D:/output.txt");
    // 2、选择流
    InputStream is = new FileInputStream(file);
    // 3、读操作:即不断地读取
    byte[] b = new byte[1024]; // 缓存数组
    int len = 0; // 接收实际读取的大小
    while ((len = is.read(b)) != -1) {
      // 能读取到数据则输出,字节数组转成字符串
      String info = new String(b, 0, len);
      System.out.println(info);
    }
    is.close();
  }

运行结果:

演示字节输出流的使用
用 FileOutputStream 类操作!
演示字节输出流的使用
用 FileOutputStream 类操作!

4、使用字节流,完成图片文件的拷贝

下面的例子演示如何通过字节流对图片文件进行拷贝操作,假设把 tomcat.png 拷贝成 tomcat1.jpg。

文件的拷贝操作的思路就是,用字节输入流读取图片 tomcat.png 的内容,用字节输出流写出到 tomcat1.jpg 文件中,根据文件操作的套路,很容易就能写出以下的代码:

@Test
  public void testCopy() throws IOException {
    // 1、使用File类与文件建立联系
    File srcFile = new File("D:/file/image/tomcat.png");
    File destFile = new File("D:/file/image/tomcat1.jpg");
    // 2、选择对应的输入流或者输出流
    InputStream is = new FileInputStream(srcFile);
    OutputStream os = new FileOutputStream(destFile);
      // 3、进行读或写操作
      byte[] b = new byte[1024];
      int len = 0;
      while ((len = is.read(b)) != -1) {
        // 判断每次读取的内容长度,如果不等于-1,表示文件没有读完
        // 选择带参数的write方法,就是为了避免byte缓存比实际内容多的时候,输出多余的空内容
        os.write(b, 0, len);
      }
    os.flush();
    // 4、关闭资源,先创建的后关闭
    os.close();
    is.close();
  }

运行结果:


使用字节流,完成图片文件的拷贝

三、纯文本操作字符流

1、认识字符流

字符流主要操作纯文本类型数据,只能处理 txt、html 等文本类型的数据,在程序中一个字符等于两个字节,Java 提供了 Reader 类和 Writer 类用于专门操作字符流。

字符流也分为字符输入流和字符输出流,在 Java 中 字符输入流用 Reader 表示,输出流用 Writer 表示。

字符输入流:Reader 是一个抽象类,必须依靠其子类 FileReader 来读取纯文本文件内容,输入到程序中。我们常用的方法是:

int read(char cbuf[]) //读取char数组中的内容,返回读入的长度
close() //关闭资源

字符输出流:Writer 是一个抽象类,必须依靠其子类 FileWriter 来读取纯文本文件内容,输入到程序中。我们常用的方法是:

//将一个字符串输出
void write(String str)
//将一个字符数组输出
void write(char cbuf[], int off, int len)
close() //关闭资源
flush() // 在关闭资源的时候默认会调用刷新方法

2、字符输出流 FileWriter 的使用

我们来看一个例子,把“演示字符输出流的使用\r\n用 FileWriter 类操作!”的文本输出到 D:/file/txt/output_char.txt 文件中。

@Test
  public void testWriter() throws IOException {
    // 1、使用File类与文件建立联系
    File file = new File("D:/file/txt/output_char.txt");
    // 2、选择对应的输入流或者输出流
    Writer writer = new FileWriter(file, true);
    String info = "演示字符输出流的使用\r\n用 FileWriter 类操作!\r\n";
    // 3、进行写操作
    writer.write(info); //将一个字符串组输出
    writer.flush();
    // 4、关闭资源
    writer.close();
  }

运行结果:


字符输出流 FileWriter 的使用

3、字符输入流 FileReader 的使用

上面的例子我们学会了字符输出流的使用,下面用字符输入流 FileReader 来读取上面的文件内容。

@Test
  public void testReader() throws IOException {
    // 1、使用File类与文件建立联系
    File file = new File("D:/file/txt/output_char.txt");
    // 2、选择对应的输入流或者输出流
    Reader reader = new FileReader(file);
    char[] cbuf = new char[1024];
    int len = 0;
    // 3、进行写操作
    while ((len = reader.read(cbuf)) != -1) {
      String info = new String(cbuf, 0, len); // 字符数组转成字符串
      System.out.println(info);
    }
    // 4、关闭资源
    reader.close();
  }

运行结果:


演示字符输出流的使用
用 FileWriter 类操作!
演示字符输出流的使用
用 FileWriter 类操作!

4、利用字符流,完成 txt文本文件的拷贝

下面的例子演示如何通过字符流对图片文件进行拷贝操作,把 output_char.txt 拷贝成 output_char1.txt。

@Test
  public void testTxtCopy() throws IOException {
    // 1、使用File类与文件建立联系
    File srcFile = new File("D:/file/txt/output_char.txt");
    File destFile = new File("D:/file/txt/output_char1.txt");
    // 2、选择对应的输入流或者输出流
    Reader read = new FileReader(srcFile);
    Writer write = new FileWriter(destFile);
    // 3、进行读写操作
    char[] cbuf = new char[1024];
    int len = 0;
    while ((len = read.read(cbuf)) != -1) {
      write.write(cbuf, 0, len); //将一个字符数组输出
    }
    write.flush();
    // 4、关闭资源
    write.close();
    read.close();
  }

运行结果:

利用字符流,完成 txt文本文件的拷贝

四、字节流与字符流的区别

1、字符输出流在写出文件时用到了缓存区

除去刚才讲过的,字节流可以处理一切文件,字符流只能处理纯文本文件,两者还有一个明显的差异,那就是字符输出流在操作文件时使用了缓冲区,通过缓冲区再写出到文件,而字节输出流直接操作文件。

1、通过源码可以证明字符输出流用到了缓存区

通过源码可以证明字符输出流用到了缓存区

2、通过两段代码的输出结果证明字符输出流用到了缓存区

  • 验证字符流:
/**
   * 把flush方法和close方法去掉,观察程序运行结果,用字符流输出内容到文件是空的
   */
  @Test
  public void testWriter1() throws IOException {
    // 1、使用File类与文件建立联系
    File file = new File("D:/file/txt/output_char_buffer.txt");
    // 2、选择对应的输入流或者输出流
    Writer writer = new FileWriter(file, true);
    String info = "把flush方法和close方法去掉,观察程序运行结果,输出的内容文件是空的!\r\n";
    // 3、进行写操作
    writer.write(info);
  }

运行结果:

验证字符输入流用到了缓存区
  • 验证字节流:
/**
   * 把flush方法和close方法去掉,观察程序运行结果,用字节流可以输出内容到文件
   */
  @Test
  public void testOutput1() throws IOException {
    // 1、使用File类与文件建立联系
    File file = new File("D:/file/txt/output_char_output.txt");
    // 2、选择对应的输入流或者输出流
    OutputStream os = new FileOutputStream(file, true);
    // 3、进行写操作
    String info = "把flush方法和close方法去掉,观察程序运行结果,输出的内容文件是空的!\r\n";
    byte[] b = info.getBytes();// 字符串转字节数组
    os.write(b, 0, b.length);// 写出
  }

运行结果:

验证字节输入流不用缓存区

通过以上的 2 段程序,可以看出,字符流是有缓存的,如果我们没有调用 flush 方法,并且没有调用 close 方法,是无法把内容写到文件中的。但是同样的没有调用 flush 方法和 close 方法,字节流确可以把内容写出到文件。

  • 验证字符流调用 flush方法,不调用 close 方法的结果
/**
   * 调用flush方法,不调用close方法,观察程序运行结果,用字符流输出内容到文件是可以的,说明字符输出流确实用到了缓冲区
   */
  @Test
  public void testWriter2() throws IOException {
    // 1、使用File类与文件建立联系
    File file = new File("D:/file/txt/output_char_writer.txt");
    // 2、选择对应的输入流或者输出流
    Writer writer = new FileWriter(file);
    String info = "调用flush方法,不调用close方法,观察程序运行结果,用字符流输出内容到文件是可以的,说明字符输出流确实用到了缓冲区!\r\n";
    // 3、进行写操作
    writer.write(info);
    // 4、强制刷出
    writer.flush();
  }

运行结果:

验证字符流调用 flush方法,不调用 close 方法的结果
  • 验证字符流调用 close 方法,不调用 flush 方法的结果
/**
   * 调用close方法,不调用flush方法,观察程序运行结果,用字符流输出内容到文件是可以的,说明字符输出流确实用到了缓冲区
   */
  @Test
  public void testWriter3() throws IOException {
    // 1、使用File类与文件建立联系
    File file = new File("D:/file/txt/output_char_writer.txt");
    // 2、选择对应的输入流或者输出流
    Writer writer = new FileWriter(file);
    String info = "调用close方法,不调用flush方法,观察程序运行结果,用字符流输出内容到文件是可以的,说明字符输出流确实用到了缓冲区!\r\n";
    // 3、进行写操作
    writer.write(info);
    // 4、关闭资源
    writer.close();
  }

运行结果:

验证字符流调用 close 方法,不调用 flush 方法的结果

通过以上的 2 段程序,可以看出,字符流是有缓存的,通过显示调用 flush 方法可以把缓存内容输出到文件,如果没有调用 flush 方法,在调用 close 方法时,默认也是会把缓存内容输出到文件。

切记字符输出流在flush方法和close方法都没有调用的时候,是无法输出内容到文件的。为了避免出现此类问题,我们在使用输出流的时候,不管是字节流还是字符流最好都显示的调用一下 flush 方法。

讲了这么多,大家觉得我们在操作文件的时候是用字节流好呢还是用字符流好呢,答案是使用字节流更好,因为所有的文件在磁盘中以及网络传输都是以二进制的字节传输的,所以在实际开发中,字节流用的比较广泛

我们再来明确一下,文件操作的套路只有4步:

1、使用File类与文件建立联系
2、选择对应的输入流或者输出流
3、进行读或写操作
4、关闭资源

另外读写操作也是有固定套路的:

 byte[] b = new byte[1024];
    int len = 0;
    while ((len = is.read(b)) != -1) {
      os.write(b, 0, len);
 }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容