当前位置: 首页 > article >正文

Java 字符流详解

在 Java 的 I/O 体系中,字符流(ReaderWriter)是专门用于处理文本数据的输入输出流。与字节流不同,字符流以字符为单位进行读取和写入,能够更好地处理文本信息,尤其是包含多字节字符(如中文)的文本文件。本文将从字符流的类关系图开始,详细介绍字符流的原理、使用方法以及常见问题。
在这里插入图片描述

1. 字符流与字节流的区别

字节流(InputStreamOutputStream)以字节为单位进行读写,适用于处理二进制数据(如图片、音频、视频等)。而字符流(ReaderWriter)以字符为单位进行读写,适用于处理文本数据。

示例:字节流读取中文乱码问题

/**
* 中文乱码问题
* @throws IOException
*/
private static void testChineseCharacterEncode() throws IOException {
   //FileInputStream为操作文件的字符输入流
   FileInputStream inputStream = new FileInputStream("JavaSE/resourses/a.txt");//内容为“沉默王二是傻 X”

   int len;
   while ((len=inputStream.read())!=-1){
       System.out.print((char)len);
   }
}

运行结果:

沉默王二是傻 X

出现乱码的原因是字节流无法正确处理多字节字符的编码问题。为了解决这个问题,可以使用字符流或者在字节流的基础上进行编码转换。

示例:字节流正确读取中文

/**
* 字节流正确读取中文
* @throws IOException
*/
private static void testCharacterCharacterEncode2() throws IOException {
   try (FileInputStream inputStream = new FileInputStream("JavaSE/resourses/a.txt")) {
       byte[] bytes = new byte[1024];
       int len;
       while ((len = inputStream.read(bytes)) != -1) {
           System.out.print(new String(bytes, 0, len));
       }
   }
}

通过 new String(byte bytes[], int offset, int length) 构造方法,Java 会根据默认的 UTF-8 编码将字节流转换为字符串,从而正确解码中文字符。

public String(byte bytes[], int offset, int length) {
    checkBounds(bytes, offset, length);
    this.value = StringCoding.decode(bytes, offset, length);
}

继续追看 StringCoding.decode() 方法调用的 defaultCharset() 方法,会发现默认编码是UTF-8,代码如下

public static Charset defaultCharset() {
    if (defaultCharset == null) {
        synchronized (Charset.class) {
            if (cs != null)
                defaultCharset = cs;
            else
                defaultCharset = forName("UTF-8");
        }
    }
    return defaultCharset;
}
static char[] decode(byte[] ba, int off, int len) {
    String csn = Charset.defaultCharset().name();
    try {
        // use charset name decode() variant which provides caching.
        return decode(csn, ba, off, len);
    } catch (UnsupportedEncodingException x) {
        warnUnsupportedCharset(csn);
    }
}

2. 字符流的基本概念

字符流 = 字节流 + 编码表

字符流的核心思想是将字节流与字符编码表结合起来,通过编码表将字节数据转换为字符数据。常见的字符编码包括 ASCII、ISO-8859-1、UTF-8、UTF-16 等。

3. 字符输入流(Reader)

java.io.Reader 是字符输入流的超类,定义了字符输入流的一些共性方法:

  • close():关闭流并释放相关资源。
  • read():读取一个字符。
  • read(char[] cbuf):读取多个字符并存储到字符数组中。

FileReader 是 Reader 的子类,用于从文件中读取字符数据。它的主要特点如下:

  • 可以通过构造方法指定要读取的文件路径。
  • 每次可以读取一个或多个字符。
  • 可以读取 Unicode 字符集中的字符,通过指定字符编码来实现字符集的转换。

3.1 FileReader构造方法

  • FileReader(File file):创建一个新的 FileReader,参数为File对象。
  • FileReader(String fileName):创建一个新的 FileReader,参数为文件名。
// 使用File对象创建流对象
File file = new File("a.txt");
FileReader fr = new FileReader(file);

// 使用文件名称创建流对象
FileReader fr = new FileReader("b.txt");

3.2 FileReader读取字符数据

  • 读取字符read方法,每次可以读取一个字符,返回读取的字符(转为 int 类型),当读取到文件末尾时,返回-1
FileReader fr = new FileReader("abc.txt");
int b;
while ((b = fr.read()) != -1) {
    System.out.println((char) b);
}
fr.close();
  • 读取指定长度的字符read(char[] cbuf, int off, int len),并将其存储到字符数组中。其中,cbuf 表示存储读取结果的字符数组,off 表示存储结果的起始位置,len 表示要读取的字符数。
File textFile = new File("docs/约定.md");
try (FileReader reader = new FileReader(textFile)) {
    char[] buffer = new char[1024];
    int len;
    while ((len = reader.read(buffer, 0, buffer.length)) != -1) {
        System.out.print(new String(buffer, 0, len));
    }
}

4. 字符输出流(Writer)

java.io.Writer 是字符输出流的超类,定义了字符输出流的一些共性方法:

  • 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():关闭流并刷新缓冲区。

FileWriter 是 Writer 的子类,用于将字符写入文件。

4.1 FileWriter构造方法

  • FileWriter(File file): 创建一个新的 FileWriter,参数为要读取的File对象。
  • FileWriter(String fileName): 创建一个新的 FileWriter,参数为要读取的文件的名称。

4.2 FileWriter写入数据

  • 写入字符write(int b) 方法,每次可以写出一个字符
FileWriter fw = new FileWriter("output.txt");
fw.write(72); // 写入字符'H'的ASCII码
fw.write(101); // 写入字符'e'的ASCII码
fw.write(108); // 写入字符'l'的ASCII码
fw.write(108); // 写入字符'l'的ASCII码
fw.write(111); // 写入字符'o'的ASCII码
fw.close();
  • 写入字符数组write(char[] cbuf) 方法,将指定字符数组写入输出流。
FileWriter fw = new FileWriter("output.txt");
char[] chars = {'H', 'e', 'l', 'l', 'o'};
fw.write(chars); // 将字符数组写入文件
fw.close();
  • 写入指定字符数组write(char[] cbuf, int off, int len) 方法,将指定字符数组的一部分写入输出流。
fw = new FileWriter("output.txt");
    char[] chars = {'H', 'e', 'l', 'l', 'o', ',', ' ', 'W', 'o', 'r', 'l', 'd', '!'};
fw.write(chars, 0, 5); // 将字符数组的前 5 个字符写入文件
  • 写入字符串write(String str) 方法,将指定字符串写入输出流。
FileWriter fw = new FileWriter("output.txt");
String str = "沉默王二";
fw.write(str); // 将字符串写入文件
fw.close();
  • 写入指定字符串write(String str, int off, int len) 方法,将指定字符串的一部分写入输出流。
String str = "沉默王二真的帅啊!";
try (FileWriter fw = new FileWriter("output.txt")) {
    fw.write(str, 0, 5); // 将字符串的前 5 个字符写入文件
} catch (IOException e) {
    e.printStackTrace();
}

5. 关闭与刷新

  • flush():刷新缓冲区,流对象可以继续使用。
  • close():先刷新缓冲区,然后通知系统释放资源,流对象不可以再被使用。
FileWriter fw = new FileWriter("fw.txt");
fw.write('刷'); // 写出第1个字符
fw.flush();
fw.write('新'); // 继续写出第2个字符,写出成功
fw.close();

6. 续写与换行

续写和换行:操作类似于FileOutputStream操作,直接上代码:

// 使用文件名称创建流对象,可以续写数据
FileWriter fw = new FileWriter("JavaSE/resourses/fw.txt",true);
// 写出字符串
fw.write("沉默王二");
// 写出换行
fw.write("\r\n");
// 写出字符串
fw.write("是傻 X");
// 关闭资源
fw.close();

7. 文本文件复制

示例:使用 FileReader 和 FileWriter 复制文本文件

public class CopyFile {
    public static void main(String[] args) throws IOException {
        //创建输入流对象
        FileReader fr = new FileReader("JavaSE/resourses/a.txt");
        //创建输出流对象
        FileWriter fw = new FileWriter("JavaSE/resourses/copyaa.txt");

        /*创建输出流做的工作:
         *      1、调用系统资源创建了一个文件
         *      2、创建输出流对象
         *      3、把输出流对象指向文件
         * */
        //文本文件复制,一次读一个字符
        // copyMethod1(fr,fw);
        //文本文件复制,一次读一个字符数组
        copyMethod2(fr,fw);
        fr.close();
        fw.close();
    }


    /**
     * 文本文件复制,一次读一个字符
     * @param fr
     * @param fw
     * @throws IOException
     */
    private static void copyMethod1(FileReader fr, FileWriter fw) throws IOException {
        int ch;
        while ((ch = fr.read()) != -1) {//读数据
            fw.write(ch);//写数据
        }
        fw.flush();
    }

    /**
     * 文本文件复制,一次读一个字符数组
     * @param fr
     * @param fw
     * @throws IOException
     */
    private static void copyMethod2(FileReader fr, FileWriter fw) throws IOException {
        char[] chs = new char[1024];
        int len = 0;
        while ((len = fr.read(chs)) != -1) {//读数据
            fw.write(chs,0,len);//写数据
        }
        fw.flush();
    }
}

8. IO 异常处理

在实际开发中,建议使用 try...catch...finally 代码块处理异常,确保资源能够正确关闭。或者直接使用 try-with-resources 的方式。
示例:使用try…catch…finally 代码块处理异常

 // 声明变量
 FileWriter fw = null;
 try {
     //创建流对象
     fw = new FileWriter("JavaSE/resourses/fw.txt");
     // 写出数据
     fw.write("二哥真的帅");
 } catch (IOException e) {
     e.printStackTrace();
 } finally {
     try {
         if (fw != null) {
             fw.close();
         }
     } catch (IOException e) {
         e.printStackTrace();
     }
 }

示例:使用 try-with-resources 处理异常

try (FileWriter fw = new FileWriter("fw.txt")) {
    fw.write("二哥真的帅");
} catch (IOException e) {
    e.printStackTrace();
}

9. 小结

字符流(Reader 和 Writer)是 Java I/O 中用于处理文本数据的抽象类。通过字符流,可以更方便地读取和写入字符数据,避免了字节流在处理多字节字符时的编码问题。常见的字符流子类包括 FileReader 和 FileWriter,它们分别用于从文件中读取和写入字符数据。在使用字符流时,需要注意字符编码的问题,并确保在操作完成后正确关闭流对象。

10.思维导图

在这里插入图片描述

11.参考链接

Java 字符流:Reader和Writer的故事


http://www.kler.cn/a/374760.html

相关文章:

  • [A-14]ARMv8/ARMv9-Memory-内存模型的类型(Device Normal)
  • Levenberg-Marquardt算法原理
  • 动态威胁场景下赋能企业安全,F5推出BIG-IP Next Web应用防火墙
  • Highcharts 条形图:数据可视化的利器
  • 推荐一款功能强大的文字处理工具:Atlantis Word Processor
  • 【jvm】堆的默认最大值和默认最小值的计算
  • Zoho Desk系统解锁工单自动化 分配效率翻倍
  • ffmpeg拉流分段存储到文件-笔记
  • SC5120家庭总线收发器可pin to pin兼容MAX22088
  • WAF+AI结合,雷池社区版的强大防守能力
  • scp免密传输教程
  • QT 跨平台优势独特,效果实例设计精彩呈现
  • 【Redis】内存淘汰策略
  • sqoop Oracle to hive出现 Error Msg = ORA-00933: SQL 命令未正确结束
  • 3周岁孤独症儿童:治愈的希望还是幻想?
  • 一文读懂 HTTP Cookies
  • Python批量查找包含多个关键词的PDF文件
  • CSP/信奥赛C++刷题训练:经典差分例题(2):洛谷P9904 :Mieszanie kolorów
  • TS:如何推导函数类型
  • 探索Unity:从游戏引擎到元宇宙体验,聚焦内容创作
  • 双十一大促有哪些值得入手的产品?这五款产品实用又划算!
  • Three.js 粒子系统教程构建炫酷的 3D 粒子效果
  • 蘑菇书(EasyRL)学习笔记(2)
  • 如何在算家云搭建GFP-GAN(图像生成)
  • h510主板怎么装win7_h510装win7完美解决方案
  • VB.NET中如何利用WCF(Windows Communication Foundation)进行服务间通信