首页 > 编程语言 >Java 字符流详解

Java 字符流详解

时间:2024-11-01 15:46:05浏览次数:3  
标签:字符 Java fw int write 详解 new FileWriter

在 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的故事

标签:字符,Java,fw,int,write,详解,new,FileWriter
From: https://blog.csdn.net/gaosw0521/article/details/143428895

相关文章

  • Java - 手写识别; 如何用spring ai和大模型做手写识别教程
    识别后的文字利用大模型提升Java手写识别:更简单、更高效在Java场景中,我们经常需要处理手写识别的问题。过去,这类需求主要依赖于OCR技术,但其效果并不总是稳定。随着大模型的发展,使用大模型进行java手写识别成为了一种更优的选择。通过引入先进的大模型,不仅提高了识别的准......
  • 洛谷题单指南-字符串-P3369 【模板】普通平衡树
    原题链接:https://www.luogu.com.cn/problem/P3369题意解读:平衡树的基本操作,模版题。解题思路:1、二叉搜索树-BST二叉搜索树满足这样的性质:每一个节点的权值大于它的左儿子,小于它的右儿子。对BST进行中序遍历,将得到一个从小到大的有序序列,因此BST是为了维护一个有序序列的动态......
  • java.sql.SQLException: ORA-00971: 缺失 SET 关键字
    目录背景:过程:错误原因: 解决办法:总结:背景:正在运行某个项目程序,提交信息之后发现库中并没有刚刚的相关数据,后来查看后台信息,发现提示错误,java.sql.SQLException:ORA-00971:缺失SET关键字,下面一片红色,经过筛选,我看到(UserManage.java:194),显然是UserManage类里面的......
  • 2024最新IntelliJ IDEA常用的小技巧汇总,JAVA 新手上路必备
    目录一、IntelliJIDEA概述二、下载与安装2.1下载2.2安装三、快速创建并运行Java工程3.1创建Java工程3.2创建package和class四、详细设置4.1字体大小设置4.2字符编码设置4.3大小写不敏感设置4.4自动导包4.5启动退出设置4.6自动更新五、快速开发5.1代码模板......
  • WebSocket详解:从前端到后端的全栈理解
    文章目录前言一、WebSocket简介1.1WebSocket的特点二、WebSocket的工作原理2.1握手过程2.2数据传输三、WebSocket在前端的应用四、WebSocket在后端的应用五、WebSocket的局限与解决方案结语前言随着互联网技术的发展,传统的HTTP协议在某些场景下的局限性逐渐显......
  • java--标识符、常量、变量、类型 转换
    1、注释(增强代码可读性)java中的注释单行注释(“//”)多行注释(/**/)tips:多行注释不能嵌套,否则会报错文档注释(/***/)源代码文件(Xxxx.java)通过编译生成字节码文件(Xxxx.class)的过程中编译器会忽略掉源码中的注释部分2、关键字(赋予特定含义的单词)特点组成关键字的字母全部小......
  • 【日常记录-Java】应用引入Slf4J
    1.简介    SLF4J(SimpleLoggingFacadeforJava)是Java的一个简单日志门面,为Java日志访问提供了一套标准、规范的API框架。而具体日志的实现则可以根据这套接口去实现具体的日志框架,以便将来需要更换日志框架时,只替换实现框架即可。常见的具体实现有JUL、log4j、......
  • 【日常记录-Java】SLF4J扫描实现框架的过程
    1.简介    SLF4J(SimpleLoggingFacadeforJava)作为一种简单的门面或抽象,服务于其他各种日志框架,例如JUL、log4j、logback等,核心作用有两项:提供日志接口;提供获取具体日志对象的方法;2.扫描过程 2.1引入依赖    在使用SLF4J时,需要引入其API依赖以及......
  • JAVA 二叉树面试题
    @目录摘要代码Node节点main函数问题1:递归——求二叉树的最大深度问题2:求二叉树的最小深度问题3:求二叉树中节点的个数问题4:求二叉树中叶子节点的个数问题5:求二叉树中第k层节点的个数,不是求第k层叶子节点个数问题6:判断两棵树是否相同问题7:给定一个二叉树,检查它是否是镜像对称的。问......
  • 【JVM详解&JVM优化】JVM内存模型
    一、介绍:        JVM是java虚拟机,JVM(JavaVirtualMachine)。对于Java不需要管理垃圾,jvm会自动帮助我们回收垃圾,但更好的掌握jvm如何帮助回收垃圾的,能让我们的系统更加稳定。        所有的Java程序都需要在JVM中运行,JVM也是Java跨平台的原理所在,对于不同......