第7章 IO流 - IO流之字符流

优质
小牛编辑
128浏览
2023-12-01

InputStream类和OutputStream类在读写文件时操作的都是字节,如果希望在程序中操作字符,使用这两个类就不太方便,为此JDK提供了字符流。同字节流一样,字符流也有两个抽象的顶级父类,分别是Reader和Writer。其中Reader是字符输入流,用于从某个源设备读取字符,Writer是字符输出流,用于向某个目标设备写入字符。Reader和Writer作为字符流的顶级父类,也有许多子类,接下来通过继承关系图来列出Reader和Writer的一些常用子类,如图所示。

字符流

字符流

1.1 转换流出现的原因及思想

由于字节流操作中文不是特别方便,所以,java就提供了转换流。字符流=字节流+编码表。

编码表:由字符及其对应的数值组成的一张表

常见编码表

计算机只能识别二进制数据,早期由来是电信号。为了方便应用计算机,让它可以识别各个国家的文字。就将各个国家的文字用数字来表示,并一一对应,形成一张表。

字符串中的编码问题

编码:把看得懂的变成看不懂的
解码:把看不懂的变成看得懂

  1. package cn.itcast_01;
  2. import java.io.UnsupportedEncodingException;
  3. import java.util.Arrays;
  4. /*
  5. * String(byte[] bytes, String charsetName):通过指定的字符集解码字节数组
  6. * byte[] getBytes(String charsetName):使用指定的字符集合把字符串编码为字节数组
  7. *
  8. * 编码:把看得懂的变成看不懂的
  9. * String -- byte[]
  10. *
  11. * 解码:把看不懂的变成看得懂的
  12. * byte[] -- String
  13. *
  14. * 举例:谍战片(发电报,接电报)
  15. *
  16. * 码表:小本子
  17. * 字符 数值
  18. *
  19. * 要发送一段文字:
  20. * 今天晚上在老地方见
  21. *
  22. * 发送端:今 -- 数值 -- 二进制 -- 发出去
  23. * 接收端:接收 -- 二进制 -- 十进制 -- 数值 -- 字符 -- 今
  24. *
  25. * 今天晚上在老地方见
  26. *
  27. * 编码问题简单,只要编码解码的格式是一致的。
  28. */
  29. public class StringDemo {
  30. public static void main(String[] args) throws UnsupportedEncodingException {
  31. String s = "你好";
  32. // String -- byte[]
  33. byte[] bys = s.getBytes(); // [-60, -29, -70, -61]
  34. // byte[] bys = s.getBytes("GBK");// [-60, -29, -70, -61]
  35. // byte[] bys = s.getBytes("UTF-8");// [-28, -67, -96, -27, -91, -67]
  36. System.out.println(Arrays.toString(bys));
  37. // byte[] -- String
  38. String ss = new String(bys); // 你好
  39. // String ss = new String(bys, "GBK"); // 你好
  40. // String ss = new String(bys, "UTF-8"); // ???
  41. System.out.println(ss);
  42. }
  43. }

1.2 转换流概述

IO流可分为字节流和字符流,有时字节流和字符流之间也需要进行转换。在JDK中提供了两个类可以将字节流转换为字符流,它们分别是InputStreamReader和OutputStreamWriter。

OutputStreamWriter是Writer的子类,它可以将一个字节输出流转换成字符输出流,方便直接写入字符,而InputStreamReader是Reader的子类,它可以将一个字节输入流转换成字符输入流,方便直接读取字符。通过转换流进行数据读写的过程如图所示。

字符流

OutputStreamWriter 字符输出流

InputStreamReader 字符输入流

OutputStreamWriter写数据

方法功能描述
write(int c)写入一个字符
write(char[] cbuf)写入一个字符数组
write(char[] cbuf,int off,int len)写入一个字符数组的一部分
write(String str)写入一个字符串
write(String str,int off,int len)写入一个字符串的一部分

字符流操作要注意的问题:

字符流数据没有直接进文件而是到缓冲区,所以要刷新缓冲区。

flush()和close()的区别:

  • A:close()关闭流对象,但是先刷新一次缓冲区。关闭之后,流对象不可以继续再使用了。
  • B:flush()仅仅刷新缓冲区,刷新之后,流对象还可以继续使用。
  1. package cn.itcast_03;
  2. import java.io.FileOutputStream;
  3. import java.io.IOException;
  4. import java.io.OutputStreamWriter;
  5. /*
  6. * OutputStreamWriter的方法:
  7. * public void write(int c):写一个字符
  8. * public void write(char[] cbuf):写一个字符数组
  9. * public void write(char[] cbuf,int off,int len):写一个字符数组的一部分
  10. * public void write(String str):写一个字符串
  11. * public void write(String str,int off,int len):写一个字符串的一部分
  12. */
  13. public class OutputStreamWriterDemo {
  14. public static void main(String[] args) throws IOException {
  15. // 创建对象
  16. OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(
  17. "osw2.txt"));
  18. // 写数据
  19. // public void write(int c):写一个字符
  20. // osw.write('a');
  21. // osw.write(97);
  22. // 为什么数据没有进去呢?
  23. // 原因是:字符 = 2字节
  24. // 文件中数据存储的基本单位是字节。
  25. // void flush()
  26. // public void write(char[] cbuf):写一个字符数组
  27. // char[] chs = {'a','b','c','d','e'};
  28. // osw.write(chs);
  29. // public void write(char[] cbuf,int off,int len):写一个字符数组的一部分
  30. // osw.write(chs,1,3);
  31. // public void write(String str):写一个字符串
  32. // osw.write("我爱林青霞");
  33. // public void write(String str,int off,int len):写一个字符串的一部分
  34. osw.write("我爱林青霞", 2, 3);
  35. // 刷新缓冲区
  36. osw.flush();
  37. // osw.write("我爱林青霞", 2, 3);
  38. // 释放资源
  39. osw.close();
  40. // java.io.IOException: Stream closed
  41. // osw.write("我爱林青霞", 2, 3);
  42. }
  43. }

1.3 InputStreamReader读数据

  • public int read():一次读一个字符
  • public int read(char[] cbuf):一次读一个字符数组
  1. package cn.itcast_03;
  2. import java.io.FileInputStream;
  3. import java.io.IOException;
  4. import java.io.InputStreamReader;
  5. /*
  6. * InputStreamReader的方法:
  7. * int read():一次读取一个字符
  8. * int read(char[] chs):一次读取一个字符数组
  9. */
  10. public class InputStreamReaderDemo {
  11. public static void main(String[] args) throws IOException {
  12. // 创建对象
  13. InputStreamReader isr = new InputStreamReader(new FileInputStream(
  14. "StringDemo.java"));
  15. // 一次读取一个字符
  16. // int ch = 0;
  17. // while ((ch = isr.read()) != -1) {
  18. // System.out.print((char) ch);
  19. // }
  20. // 一次读取一个字符数组
  21. char[] chs = new char[1024];
  22. int len = 0;
  23. while ((len = isr.read(chs)) != -1) {
  24. System.out.print(new String(chs, 0, len));
  25. }
  26. // 释放资源
  27. isr.close();
  28. }
  29. }

1.4 字符流复制文本文件

  1. package cn.itcast_04;
  2. import java.io.FileInputStream;
  3. import java.io.FileOutputStream;
  4. import java.io.IOException;
  5. import java.io.InputStreamReader;
  6. import java.io.OutputStreamWriter;
  7. /*
  8. * 需求:把当前项目目录下的a.txt内容复制到当前项目目录下的b.txt中
  9. *
  10. * 数据源:
  11. * a.txt -- 读取数据 -- 字符转换流 -- InputStreamReader
  12. * 目的地:
  13. * b.txt -- 写出数据 -- 字符转换流 -- OutputStreamWriter
  14. */
  15. public class CopyFileDemo {
  16. public static void main(String[] args) throws IOException {
  17. // 封装数据源
  18. InputStreamReader isr = new InputStreamReader(new FileInputStream(
  19. "a.txt"));
  20. // 封装目的地
  21. OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(
  22. "b.txt"));
  23. // 读写数据
  24. // 方式1
  25. // int ch = 0;
  26. // while ((ch = isr.read()) != -1) {
  27. // osw.write(ch);
  28. // }
  29. // 方式2
  30. char[] chs = new char[1024];
  31. int len = 0;
  32. while ((len = isr.read(chs)) != -1) {
  33. osw.write(chs, 0, len);
  34. // osw.flush();
  35. }
  36. // 释放资源
  37. osw.close();
  38. isr.close();
  39. }
  40. }

1.5 转换流的简化写法

转换流的名字比较长,而我们常见的操作都是按照本地默认编码实现的,所以,为了简化我们的书写,转换流提供了对应的子类。

FileWriter

代码示例:把当前项目目录下的a.txt内容复制到当前项目目录下的b.txt中

  1. package cn.itcast_04;
  2. import java.io.FileReader;
  3. import java.io.FileWriter;
  4. import java.io.IOException;
  5. /*
  6. * 由于我们常见的操作都是使用本地默认编码,所以,不用指定编码。
  7. * 而转换流的名称有点长,所以,Java就提供了其子类供我们使用。
  8. * OutputStreamWriter = FileOutputStream + 编码表(GBK)
  9. * FileWriter = FileOutputStream + 编码表(GBK)
  10. *
  11. * InputStreamReader = FileInputStream + 编码表(GBK)
  12. * FileReader = FileInputStream + 编码表(GBK)
  13. *
  14. /*
  15. * 需求:把当前项目目录下的a.txt内容复制到当前项目目录下的b.txt中
  16. *
  17. * 数据源:
  18. * a.txt -- 读取数据 -- 字符转换流 -- InputStreamReader -- FileReader
  19. * 目的地:
  20. * b.txt -- 写出数据 -- 字符转换流 -- OutputStreamWriter -- FileWriter
  21. */
  22. public class CopyFileDemo2 {
  23. public static void main(String[] args) throws IOException {
  24. // 封装数据源
  25. FileReader fr = new FileReader("a.txt");
  26. // 封装目的地
  27. FileWriter fw = new FileWriter("b.txt");
  28. // 一次一个字符
  29. // int ch = 0;
  30. // while ((ch = fr.read()) != -1) {
  31. // fw.write(ch);
  32. // }
  33. // 一次一个字符数组
  34. char[] chs = new char[1024];
  35. int len = 0;
  36. while ((len = fr.read(chs)) != -1) {
  37. fw.write(chs, 0, len);
  38. fw.flush();
  39. }
  40. // 释放资源
  41. fw.close();
  42. fr.close();
  43. }
  44. }

1.6 FileReader

代码示例:把c:a.txt内容复制到d:b.txt中

  1. package cn.itcast_04;
  2. import java.io.FileReader;
  3. import java.io.FileWriter;
  4. import java.io.IOException;
  5. /*
  6. * 需求:把c:\a.txt内容复制到d:\b.txt中
  7. *
  8. * 数据源:
  9. * c:\a.txt -- FileReader
  10. * 目的地:
  11. * d:\b.txt -- FileWriter
  12. */
  13. public class CopyFileDemo3 {
  14. public static void main(String[] args) throws IOException {
  15. // 封装数据源
  16. FileReader fr = new FileReader("c:\a.txt");
  17. // 封装目的地
  18. FileWriter fw = new FileWriter("d:\b.txt");
  19. // 读写数据
  20. // int ch = 0;
  21. int ch;
  22. while ((ch = fr.read()) != -1) {
  23. fw.write(ch);
  24. }
  25. //释放资源
  26. fw.close();
  27. fr.close();
  28. }
  29. }

2. 字符缓冲流

字符流为了高效读写,也提供了对应的字符缓冲流。BufferedWriter:字符缓冲输出流,BufferedReader:字符缓冲输入流。

2.1 BufferedWriter基本用法

将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。 可以指定缓冲区的大小,或者接受默认的大小。在大多数情况下,默认值就足够大了。

代码示例:BufferedWriter基本用法

  1. package cn.itcast_05;
  2. import java.io.BufferedWriter;
  3. import java.io.FileWriter;
  4. import java.io.IOException;
  5. /**
  6. * 字符流为了高效读写,也提供了对应的字符缓冲流。
  7. * BufferedWriter:字符缓冲输出流
  8. * BufferedReader:字符缓冲输入流
  9. *
  10. * BufferedWriter:字符缓冲输出流
  11. * 将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。
  12. * 可以指定缓冲区的大小,或者接受默认的大小。在大多数情况下,默认值就足够大了。
  13. */
  14. public class BufferedWriterDemo {
  15. public static void main(String[] args) throws IOException {
  16. // BufferedWriter(Writer out)
  17. // BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
  18. // new FileOutputStream("bw.txt")));
  19. BufferedWriter bw = new BufferedWriter(new FileWriter("bw.txt"));
  20. bw.write("hello");
  21. bw.write("world");
  22. bw.write("java");
  23. bw.flush();
  24. bw.close();
  25. }
  26. }

2.2 BufferedReader基本用法

从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。 可以指定缓冲区的大小,或者可使用默认的大小。大多数情况下,默认值就足够大了。

代码示例: BufferedReader基本用法

  1. package cn.itcast_05;
  2. import java.io.BufferedReader;
  3. import java.io.FileReader;
  4. import java.io.IOException;
  5. /**
  6. * BufferedReader
  7. * 从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。
  8. * 可以指定缓冲区的大小,或者可使用默认的大小。大多数情况下,默认值就足够大了。
  9. *
  10. * BufferedReader(Reader in)
  11. */
  12. public class BufferedReaderDemo {
  13. public static void main(String[] args) throws IOException {
  14. // 创建字符缓冲输入流对象
  15. BufferedReader br = new BufferedReader(new FileReader("bw.txt"));
  16. // 方式1
  17. // int ch = 0;
  18. // while ((ch = br.read()) != -1) {
  19. // System.out.print((char) ch);
  20. // }
  21. // 方式2
  22. char[] chs = new char[1024];
  23. int len = 0;
  24. while ((len = br.read(chs)) != -1) {
  25. System.out.print(new String(chs, 0, len));
  26. }
  27. // 释放资源
  28. br.close();
  29. }
  30. }

2.3 特殊功能

  • BufferedWriter,newLine():根据系统来决定换行符
  • BufferedReader,String readLine():一次读取一行数据

代码示例:字符缓冲流的特殊方法

  1. package cn.itcast_05;
  2. import java.io.BufferedReader;
  3. import java.io.BufferedWriter;
  4. import java.io.FileReader;
  5. import java.io.FileWriter;
  6. import java.io.IOException;
  7. /*
  8. * 字符缓冲流的特殊方法:
  9. * BufferedWriter:
  10. * public void newLine():根据系统来决定换行符
  11. * BufferedReader:
  12. * public String readLine():一次读取一行数据
  13. * 包含该行内容的字符串,不包含任何行终止符,如果已到达流末尾,则返回 null
  14. */
  15. public class BufferedDemo {
  16. public static void main(String[] args) throws IOException {
  17. // write();
  18. read();
  19. }
  20. private static void read() throws IOException {
  21. // 创建字符缓冲输入流对象
  22. BufferedReader br = new BufferedReader(new FileReader("bw2.txt"));
  23. // public String readLine():一次读取一行数据
  24. // String line = br.readLine();
  25. // System.out.println(line);
  26. // line = br.readLine();
  27. // System.out.println(line);
  28. // 最终版代码
  29. String line = null;
  30. while ((line = br.readLine()) != null) {
  31. System.out.println(line);
  32. }
  33. //释放资源
  34. br.close();
  35. }
  36. private static void write() throws IOException {
  37. // 创建字符缓冲输出流对象
  38. BufferedWriter bw = new BufferedWriter(new FileWriter("bw2.txt"));
  39. for (int x = 0; x < 10; x++) {
  40. bw.write("hello" + x);
  41. // bw.write("rn");
  42. bw.newLine();
  43. bw.flush();
  44. }
  45. bw.close();
  46. }
  47. }

代码示例:字符缓冲流复制文本文件

  1. package cn.itcast_06;
  2. import java.io.BufferedReader;
  3. import java.io.BufferedWriter;
  4. import java.io.FileReader;
  5. import java.io.FileWriter;
  6. import java.io.IOException;
  7. /*
  8. * 需求:把当前项目目录下的a.txt内容复制到当前项目目录下的b.txt中
  9. *
  10. * 数据源:
  11. * a.txt -- 读取数据 -- 字符转换流 -- InputStreamReader -- FileReader -- BufferedReader
  12. * 目的地:
  13. * b.txt -- 写出数据 -- 字符转换流 -- OutputStreamWriter -- FileWriter -- BufferedWriter
  14. */
  15. public class CopyFileDemo2 {
  16. public static void main(String[] args) throws IOException {
  17. // 封装数据源
  18. BufferedReader br = new BufferedReader(new FileReader("a.txt"));
  19. // 封装目的地
  20. BufferedWriter bw = new BufferedWriter(new FileWriter("b.txt"));
  21. // 读写数据
  22. String line = null;
  23. while ((line = br.readLine()) != null) {
  24. bw.write(line);
  25. bw.newLine();
  26. bw.flush();
  27. }
  28. // 释放资源
  29. bw.close();
  30. br.close();
  31. }
  32. }

3. 模拟记事本

  1. package cn.itcast.chapter07.task02;
  2. import java.io.FileReader;
  3. import java.io.FileWriter;
  4. import java.io.IOException;
  5. import java.util.Scanner;
  6. /**
  7. * 模拟记事本程序
  8. */
  9. public class Notepad {
  10. private static String filePath;
  11. private static String message = "";
  12. public static void main(String[] args) throws Exception {
  13. Scanner sc = new Scanner(System.in);
  14. System.out.println("--1:新建文件 2:打开文件 3:修改文件 4:保存 5:退出--");
  15. while (true) {
  16. System.out.print("请输入操作指令:");
  17. int command = sc.nextInt();
  18. switch (command) {
  19. case 1:
  20. createFile();// 1:新建文件
  21. break;
  22. case 2:
  23. openFile();// 2:打开文件
  24. break;
  25. case 3:
  26. editFile();// 3:修改文件
  27. break;
  28. case 4:
  29. saveFile();// 4:保存
  30. break;
  31. case 5:
  32. exit();// 5:退出
  33. break;
  34. default:
  35. System.out.println("您输入的指令错误!");
  36. break;
  37. }
  38. }
  39. }
  40. /**
  41. * 新建文件 从控制台获取内容
  42. */
  43. private static void createFile() {
  44. message = "";// 新建文件时,暂存文件内容清空
  45. Scanner sc = new Scanner(System.in);
  46. System.out.println("请输入内容,停止编写请输入"stop":");// 提示
  47. StringBuffer stb = new StringBuffer();// 用于后期输入内容的拼接
  48. String inputMessage = "";
  49. while (!inputMessage.equals("stop")) {// 当输入“stop”时,停止输入
  50. if (stb.length() > 0) {
  51. stb.append("rn");// 追加换行符
  52. }
  53. stb.append(inputMessage);// 拼接输入信息
  54. inputMessage = sc.nextLine();// 获取输入信息
  55. }
  56. message = stb.toString();// 将输入内容暂存
  57. }
  58. /**
  59. * 打开文件
  60. */
  61. private static void openFile() throws Exception {
  62. message = "";// 打开文件时,将暂存内容清空
  63. Scanner sc = new Scanner(System.in);
  64. System.out.print("请输入打开文件的位置:");
  65. filePath = sc.next();// 获取打开文件的路径
  66. // 控制只能输入txt格式的文件路径
  67. if (filePath != null && !filePath.endsWith(".txt")) {
  68. System.out.print("请选择文本文件!");
  69. return;
  70. }
  71. FileReader in = new FileReader(filePath);// 实例化一个FileReader对象
  72. char[] charArray = new char[1024];// 缓冲数组
  73. int len = 0;
  74. StringBuffer sb = new StringBuffer();
  75. // 循环读取,一次读取一个字符数组
  76. while ((len = in.read(charArray)) != -1) {
  77. sb.append(charArray);
  78. }
  79. message = sb.toString();// 将打开文件内容暂存
  80. System.out.println("打开文件内容:" + "rn" + message);
  81. in.close();// 释放资源
  82. }
  83. /**
  84. * 修改文件内容 通过字符串替换的形式
  85. */
  86. private static void editFile() {
  87. if (message == "" && filePath == null) {
  88. System.out.println("请先新建文件或者打开文件");
  89. return;
  90. }
  91. Scanner sc = new Scanner(System.in);
  92. System.out.println("请输入要修改的内容(以 "修改的目标文字:修改之后的文字" 格式),"
  93. + "停止修改请输入"stop":");
  94. String inputMessage = "";
  95. while (!inputMessage.equals("stop")) {// 当输入stop时,停止修改
  96. inputMessage = sc.nextLine();
  97. if (inputMessage != null && inputMessage.length() > 0) {
  98. // 将输入的文字根据“:”拆分成数组
  99. String[] editMessage = inputMessage.split(":");
  100. if (editMessage != null && editMessage.length > 1) {
  101. // 根据输入的信息将文件中内容替换
  102. message = message.replace(editMessage[0], editMessage[1]);
  103. }
  104. }
  105. }
  106. System.out.println("修改后的内容:" + "rn" + message);
  107. }
  108. /**
  109. * 保存 新建文件存在用户输入的路径 打开的文件将原文件覆盖
  110. */
  111. private static void saveFile() throws IOException {
  112. Scanner sc = new Scanner(System.in);
  113. FileWriter out = null;
  114. if (filePath != null) {// 文件是由“打开”载入的
  115. out = new FileWriter(filePath);// 将原文件覆盖
  116. } else {// 新建的文件
  117. System.out.print("请输入文件保存的绝对路径:");
  118. String path = sc.next();// 获取文件保存的路径
  119. filePath = path;
  120. // 将输入路径中大写字母替换成小写字母后判断是不是文本格式
  121. if (!filePath.toLowerCase().endsWith(".txt")) {
  122. filePath += ".txt";
  123. }
  124. out = new FileWriter(filePath);// 构造输出流
  125. }
  126. out.write(message);// 写入暂存的内容
  127. out.close();// 关闭输出流
  128. message = "";// 修改文件前现将写入内容置空
  129. filePath = null;// 将文件路径至null
  130. }
  131. /**
  132. * 退出
  133. */
  134. private static void exit() {
  135. System.out.println("您已退出系统,谢谢使用!");
  136. System.exit(0);
  137. }
  138. }

4. 总结