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

【Java】I/O 流篇 —— 字符 I/O 流

目录

  • 字符集
    • ASCII(美国信息交换标准代码)
    • GBK(汉字内码扩展规范)
    • Unicode(UTF-8 作为常用实现方式)
  • 字符 I/O 流
    • 字符流概述
    • FileReader 类
      • 构造方法
      • 基本操作
      • 读数据的 2 种方法
    • FileWriter 类
      • 构造方法
      • 写数据的 5 种方法
      • 基本操作
    • 原理
      • FileReader 原理
      • FileWriter 原理
        • 基本概念
        • 工作原理
        • 使用场景
        • 代码示例
        • 注意事项

字符集

ASCII(美国信息交换标准代码)

  1. 诞生背景:20 世纪 60 年代,计算机技术开始兴起,当时计算机主要在美国使用。为了实现不同计算机系统之间信息的交换和处理,美国制定了 ASCII 编码标准。它是基于拉丁字母的一套电脑编码系统,也是计算机领域中最早且应用广泛的字符集。
  2. 编码方式:ASCII 采用 7 位二进制数进行编码,由于计算机存储的基本单位是字节(8 位),所以在实际存储时,最高位补 0,这样就占用 1 个字节。7 位二进制数可以表示的字符数量为 个。
  3. 字符范围:
    • 控制字符:包括一些不可显示的字符,用于控制设备的操作,如换行符(LF,ASCII 码值为 10)、回车符(CR,ASCII 码值为 13)、制表符(TAB,ASCII 码值为 9)等,它们的 ASCII 码值范围是 0 - 31 和 127。
    • 可显示字符:包含英文字母(大写 A - Z,ASCII 码值为 65 - 90;小写 a - z,ASCII 码值为 97 - 122)、数字 0 - 9(ASCII 码值为 48 - 57)以及常见的标点符号和特殊字符(如!、@、# 等),其 ASCII 码值范围是 32 - 126。
  4. 应用场景:ASCII 字符集在处理纯英文文本时非常高效,几乎所有的计算机系统和软件都支持 ASCII 编码。例如,在早期的英文程序、简单的文本文件、网络通信协议(如 HTTP 协议中部分头部信息)等场景中广泛应用。
  5. 局限性:ASCII 只能表示英文字符和一些基本的控制字符,无法满足世界上其他众多语言文字(如中文、日文、阿拉伯文等)的编码需求。随着计算机在全球的普及,这种局限性越来越明显。

GBK(汉字内码扩展规范)

  1. 诞生背景:随着计算机在中国的普及,ASCII 字符集无法满足中文信息处理的需求。我国首先制定了 GB2312 字符集,收录了 6000 多个常用汉字和一些符号。但 GB2312 不能涵盖所有的中文汉字,为了更全面地满足中文信息处理的需要,1995 年发布了 GBK 字符集,它是对 GB2312 的扩展和增强。
  2. 编码方式:GBK 采用双字节编码方式。对于 ASCII 字符,GBK 字符集保持了与 ASCII 编码的一致性,使用单字节表示(第一个字节的最高位为 0),这保证了对英文等 ASCII 字符的兼容性。而对于汉字和其他非 ASCII 字符,则使用两个字节表示。一般来说,第一个字节(高字节)的取值范围是 0x81 - 0xFE,第二个字节(低字节)的取值范围是 0x40 - 0xFE(除去 0x7F)。
  3. 字符范围:GBK 收录了 21003 个汉字,涵盖了大部分常用字、次常用字以及一些生僻字,同时还包括 ASCII 字符和大量的图形符号,如数学运算符号、货币符号、制表符等。这使得 GBK 能够满足中文文本处理中对各种字符的需求。
  4. 应用场景:GBK 字符集在中文操作系统(如早期的中文 Windows 系统)、中文软件(如 WPS、早期的办公软件等)、中文数据库存储(如早期的 MySQL 数据库在处理中文数据时,GBK 是一种常见的字符集配置选项)等领域得到了广泛应用。
  5. 局限性:GBK 字符集主要是为了满足中文信息处理的需求而设计的,对于其他语言文字的支持有限。在国际通用性方面存在不足,不同国家和地区有各自的字符集标准,在进行跨国数据交换和多语言系统开发时,GBK 字符集可能会遇到兼容性问题,需要进行字符集转换等额外操作。

Unicode(UTF-8 作为常用实现方式)

  1. 诞生背景:为了解决全球不同语言文字的编码问题,实现所有字符的统一编码,Unicode 应运而生。Unicode 旨在为世界上每一种语言的每一个字符都提供一个唯一的数字编码,无论该字符在何种平台、何种程序以及何种语言中使用,其编码都是一致的。UTF-8 是 Unicode 的一种可变长字符编码实现方式,因其良好的兼容性和空间效率,成为了 Unicode 应用中最常用的编码方式。
  2. 编码方式(UTF-8):UTF-8 是一种可变长的编码方式,它使用 1 - 4 个字节来表示一个字符:
    • 对于 ASCII 字符(码点范围 U+0000 - U+007F),UTF - 8 使用 1 个字节表示,且编码与 ASCII 码完全相同,这保证了对 ASCII 字符集的兼容性。
    • 对于常用的汉字等字符(一般码点范围在 U+4E00 - U+9FFF 等),UTF - 8 使用 3 个字节表示。
    • 对于一些生僻字符或特殊符号(码点范围在辅助平面等),UTF - 8 可能会用 4 个字节表示。
  3. 字符范围:Unicode 涵盖了世界上几乎所有已知的语言文字、符号、象形文字以及表情符号等。包括但不限于中文、英文、日文、阿拉伯文、古文字等各种语言的字符,以及数学符号、音乐符号、旗帜符号等特殊符号。
  4. 应用场景:Unicode(UTF-8)在互联网、操作系统、编程语言、数据库等领域都得到了广泛的支持。在互联网上,网页通常使用 UTF - 8 编码来确保不同语言的内容都能正确显示;在操作系统中,如现代的 Windows、Linux、macOS 等都支持 Unicode 字符集;在编程语言中,像 Python 3 默认使用 Unicode 字符串,Java 也对 Unicode 有良好的支持;在数据库中,如 MySQL、Oracle 等也都支持 Unicode 编码,方便存储和处理多语言数据。
  5. 优势:Unicode 的优势在于其广泛的字符覆盖范围和良好的兼容性,能够满足全球各种语言文字的编码需求,使得不同语言之间的信息交流和处理更加便捷。UTF - 8 作为其实现方式,在存储和传输方面具有较高的效率,尤其是对于以 ASCII 字符为主的文本,能够节省存储空间。

字符 I/O 流

字符流概述

  1. 字符流的底层其实就是字节流:字符流 = 字节流 + 字符集
  2. 特点
    • 输入流:一次读一个字节,遇到中文时,一次读多个字节
      • GBK 一次读两个字节
      • UTF - 8 一个读三个字节
    • 输出流:底层会把数据按照指定的编码方式进行编码,变成字节再写到文件中
  3. 使用场景:对于纯文本文件进行读写操作

FileReader 类

构造方法

  • FileReader(File file)

    • 功能描述:创建一个新的 FileReader 对象,该对象会从指定的 File 对象所代表的文件中读取字符数据。同样,如果文件不存在、是目录或无法打开,会抛出 FileNotFoundException 异常。
  • FileReader(String fileName)

    • 功能描述:创建一个新的 FileReader 对象,该对象会根据指定的文件名称来读取文件。文件名称可以是相对路径或绝对路径。如果指定的文件不存在、是一个目录而不是文件,或者由于其他原因无法打开该文件,会抛出 FileNotFoundException 异常。
  • FileReader(FileDescriptor fd)

    • 功能描述:创建一个新的 FileReader 对象,该对象会通过指定的文件描述符 fd 来读取字符数据。文件描述符是一个底层系统用于标识打开文件的整数,这种构造方法通常用于与底层系统进行交互或处理已经打开的文件。

基本操作

操作步骤

  1. 创建字符输入流对象

    FileReader fr = new FileReader("aaa.txt");
    
  2. 读取数据

    fr.read();
    
  3. 释放资源

    fr.close();
    

代码示例

import java.io.FileReader;
import java.io.IOException;

public class Demo {
	public static void main(String[] args) throws IOException {
		// 创建对象
		FileReader fr = new FileReader("aaa.txt");
		
		// 读取数据
		int ch;
		while((ch = fr.read()) != -1) {
			System.out.println(ch);
		}
		
		// 释放资源
		fr.close();
	}
}

注意事项

  1. read() 默认一个字节一个字节的读取,如果遇到中文就会一次读取多个
  2. 在读取之后,read() 的底层还会进行解码并转成十进制,并将其作为返回值,对应字符集字符

读数据的 2 种方法

  • int read() 读取数据,末尾返回 -1
  • int read(char[] buffer) 读取多个数据,末尾返回 -1

代码示例:

import java.io.FileReader;
import java.io.IOException;

public class Demo {
	public static void main(String[] args) throws IOException {
		// 创建对象
		FileReader fr = new FileReader("aaa.txt");
		/*以下是文件中的内容
		 你好
		 今天天气不错
		 */
        
		// 读取数据
		char[] chars = new char[2];
		int len;
		while((len = fr.read(chars)) != -1) {
			System.out.print(new String(chars,0,len));
		}
		
		// 释放资源
		fr.close();
	}
}

程序运行结果如下:

你好
今天天气不错

如果把代码中的输出语句改为以下情形:

System.out.println(new String(chars,0,len));

则程序运行结果变为:

你好


今天
天气
不错

造成这样的原因是在 “你好” 的后面其实是有 “\r\n”,由于数组长度为 2,每次只能读取两个字符,读取到 “\r\n” 就会回车换行,然后 println() 又会换行,所以导致输出结果变为这样,究其原因就是数组长度不够。

注意事项

  1. int read() 返回的是十进制整数,如果要想得到字符,可以进行强制类型转换
  2. int read(char[] buffer) 是将空参 read() 与强制类型转换结合起来,所以得到的直接是字符

FileWriter 类

构造方法

  • FileWriter(String fileName)

    • 功能描述:创建一个 FileWriter 对象,用于向指定名称的文件写入字符数据。如果指定的文件不存在,会尝试创建该文件;如果文件已经存在,会覆盖原有内容。若在创建或打开文件过程中出现问题,比如没有权限创建文件、指定的路径无效等,会抛出 IOException 异常。
  • FileWriter(String fileName, boolean append)

    • 功能描述:创建一个 FileWriter 对象,用于向指定名称的文件写入字符数据。append 参数决定了写入操作的模式,如果 appendtrue,则会以追加模式打开文件,新写入的数据会添加到文件末尾;如果 appendfalse,则会覆盖原有内容。同样,若在操作文件时出现问题,会抛出 IOException 异常。
  • FileWriter(File file)

    • 功能描述:创建一个 FileWriter 对象,用于向指定 File 对象所代表的文件写入字符数据。若文件不存在,会尝试创建;若存在,会覆盖原有内容。若文件操作出现问题,会抛出 IOException 异常。
  • FileWriter(File file, boolean append)

    • 功能描述:创建一个 FileWriter 对象,用于向指定 File 对象所代表的文件写入字符数据。append 参数决定写入模式,true 为追加模式,false 为覆盖模式。若操作文件时出现问题,会抛出 IOException 异常。

写数据的 5 种方法

  • void write(int c) 写出一个字符
  • void write(String str) 写出一个字符串
  • void write(String str,int off,int len) 写出一个字符串的一部分
  • void write(char[] cbuf) 写出一个字符数组
  • void write(char[] cbuf,int off,int len) 写出字符数组的一部分

基本操作

操作步骤

  1. 创建字符输出流对象

    FileWriter fw = new FileWriter("aaa.txt");
    
  2. 写入数据

    fw.write();
    
  3. 释放资源

    fw.close();
    

代码示例

import java.io.FileWriter;
import java.io.IOException;

public class Demo {
	public static void main(String[] args) throws IOException {
		// 创建对象
		FileWriter fw = new FileWriter("aaa.txt");
		
		// 写入数据
		fw.write(25105); // 写入'我'
		
		//释放资源
		fw.close();
	}
}

注意事项

  1. 创建字符输出流对象
    • 参数是字符串表示的路径或者 File 对象都是可以的
    • 如果文件不存在会创建一个新的文件,但是要保证父级路径是存在的
    • 如果文件已经存在,则会清空文件,如果不想清空可以打开续写开关
  2. 写数据
    • 如果 write 方法的参数是整数,但是实际上写到本地文件中的是整数在字符集上对应的字符
  3. 释放资源
    • 每次使用完流之后都要释放资源

原理

FileReader 原理

  1. 创建字符输入流对象
    • 关联文件,并创建缓冲区(长度为 8192 的字节数组)
  2. 读取数据
    • 判断缓冲区中是否有数据可以读取
    • 缓冲区没有数据:就从文件中获取数据,装到缓冲区中,每次尽可能装满缓冲区
      • 如果文件中也没有数据了,返回 - 1
    • 缓冲区有数据:就从缓冲区中读取。
      • 空参的 read 方法:一次读取一个字节,遇到中文一次读多个字节,把字节解码并转成十进制返回
      • 有参的 read 方法:把读取字节,解码,强转三步合并了,强转之后的字符放到数组中

来看下面这段代码:

import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class Demo {
	public static void main(String[] args) throws IOException {
		
		FileReader fr = new FileReader("aaa.txt");
		fr.read();
		
		FileWriter fw = new FileWriter("aaa.txt");
		
		int ch;
		while((ch = fr.read()) != -1) {
			System.out.println((char)ch);
		}
		
		fw.close();
		fr.close();
	}
}

我们知道创建字符输出流对象是会清空文件中原本的内容,那么问题来了,在这段代码中,创建了字符输出流对象后还会输出文件中的内容吗?

答案是会的,因为在创建字符输出流对象之前,调用了一次字符输入流的 read 方法,这会使文件中的内容被读取进入到缓冲区,即使创建字符输出流对象是会清空文件中原本的内容,但是只要缓冲区中有数据,就会被读取

FileWriter 原理

FileWriter 是 Java 中用于将字符数据写入文件的字符输出流类,其 flush 方法在处理文件写入操作时发挥着重要作用,下面将从基本概念、工作原理、使用场景、代码示例、注意事项等方面详细介绍:

基本概念

FileWriter 继承自 OutputStreamWriter,而 OutputStreamWriter 实现了 Flushable 接口,因此 FileWriter 具备 flush 方法。该方法的主要作用是将缓冲区中暂存的数据强制写入到目标文件中,从而清空缓冲区。

工作原理

FileWriter 在进行字符写入操作时,并非直接将字符写入文件,而是先将字符数据存储在内部的缓冲区中。这是为了减少与文件系统的频繁交互,提高写入效率。当缓冲区被填满或者调用 flush 方法时,FileWriter 会将缓冲区中的字符数据按照指定的字符编码转换为字节序列,然后将这些字节数据写入到目标文件中。

使用场景
  • 实时数据写入:当需要确保某些重要数据及时写入文件,而不是等待缓冲区填满时,可以调用 flush 方法。例如,在记录日志的场景中,每记录一条重要日志信息后,就调用 flush 方法,保证日志信息能立即写入文件,避免因程序崩溃或异常导致数据丢失。
  • 多线程写入:在多线程环境下,多个线程可能同时向同一个文件写入数据。为了保证数据的完整性和顺序性,每个线程在完成一部分数据写入后,可以调用 flush 方法,确保数据及时写入文件,避免不同线程的数据相互干扰。
代码示例
import java.io.FileWriter;
import java.io.IOException;

public class FileWriterFlushExample {
    public static void main(String[] args) {
        try (FileWriter fw = new FileWriter("test.txt")) {
            // 写入一部分数据
            fw.write("这是第一段写入的数据。");
            // 调用 flush 方法,将缓冲区数据写入文件
            fw.flush();
            System.out.println("第一段数据已写入文件。");

            // 继续写入另一部分数据
            fw.write("这是第二段写入的数据。");
            // 再次调用 flush 方法
            fw.flush();
            System.out.println("第二段数据已写入文件。");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在上述代码中,每次调用 write 方法写入数据后,都调用了 flush 方法,确保数据及时写入文件。

注意事项
  • close 方法的关系:当调用 FileWriterclose 方法时,会自动调用 flush 方法,将缓冲区中的数据刷新到文件中,然后关闭流。但在某些情况下,可能需要在关闭流之前多次刷新缓冲区,此时就需要手动调用 flush 方法。
  • 性能影响:虽然 flush 方法可以确保数据及时写入文件,但频繁调用 flush 方法会增加与文件系统的交互次数,从而降低写入性能。因此,需要根据实际需求合理使用 flush 方法,在保证数据安全的前提下,尽量减少不必要的刷新操作。

相关文章:

  • 理解 AI IDE 中的代码库索引:深入探讨 Cursor 的实现
  • C++ | 高级教程 | 信号处理
  • 48.日常算法
  • Python入门 — 类
  • 在ubuntu如何安装samba软件?
  • lowagie(itext)老版本手绘PDF,包含页码、水印、图片、复选框、复杂行列合并等。
  • day7作业
  • GB 44497-2024《智能网联汽车 自动驾驶数据记录系统》标准解读
  • 最新前端框架选型对比与建议(React/Vue/Svelte/Angular)
  • C++ | 高级教程 | 文件和流
  • Redis核心数据结构与底层实现
  • jmeter 如何做移动端的测试 特别是兼容性测试
  • 探索超声波的奥秘——定时器与PCA
  • 阳光高考瑞数6vmp算法还原
  • DeepSeek赋能大模型内容安全,网易易盾AIGC内容风控解决方案三大升级
  • Axios 取消请求
  • 微软推出Office免费版,限制诸多,只能编辑不能保存到本地
  • Ubuntu搭建esp32环境 配置打开AT指令集 websocket功能
  • C++引用
  • 【Deepseek+Browser-Use搭建 Web UI自动化】
  • 松江新城做网站/女生学电子商务好吗
  • 电商免费网站入口/外贸网站推广方法之一
  • Wordpress 反馈表单/trinseo公司
  • jsp网站开发公司/最优化方法
  • 南宁有本地租房做网站吗/外包公司和劳务派遣的区别
  • 网站后台栏目发布/个人接外包的网站