字符流

Java采用Unicode规范处理字符,字符流IO自动将Java内部字符格式与本地字符集进行转换,自动解
码编码,因此比起直接使用IO字节流,字符流操作相对更简单高效,所有的字符操作流都是从抽象类
Reader以及Writer继承而来。

字符编码

字符都有编码,读写字符时的编码不一致,则容易出现乱码,因此首先需要简单了解下常⻅的字符编
码规则。

ASCII码

ASCII是American Standard Code for Information Interchange缩写,称为美国信息交换标准代码。
ASCII一共定义了 128 个字符,其中 33 个字符是不可显示的控制字符, 95 个可显示的字符。

一个字节占 8 位,2^8=256,即一个字节可表示 256 个字符(0255),而ASCII字符只有 128 个,因此一个字节的低 7 位,2^7=128,足以表达全部的ASCII字符(0127),128~255则预留扩展其它字符,但 128 位根本不足以表示其它国家的字符。

GBK

  • GBK全称汉字内码扩展规范,GBK一共收集了^2 万多汉字与字符,一个中文字符编码成两个字节进
    行存储。
  • GBK兼容了ASCII字符集

GBK是从GB2312编码规范上扩展的,而中国的汉字与符号有数十万,显然GBK也是不够的,因此又出现了GB18030, 按照字符集表示范围GB18030 > GBK > GB2312。

Unicode

各个国家都有自己的编码,当计算机信息在国际上进行交换时,就会出现问题,如用GBK编码的字节
数据发送给A国家,A国家采用A国码解码肯定就出现了乱码,此时国际标准组织就制定了一套通用的字
符集Unicode,即统一码,也叫万国码。

  • Unicode字符集收纳了世界上所有文字、符号,统一进行编号

UTF-8

Unicode只是一种字符集,并不是编码方案,没有编码方案则无法存储。Unicode字符集出现最早的编
码方案是UTF-32,它规定所有的字符都采用固定的 4 个字节来表示, 4 个字节3 2位,可以表示 42 亿字
符,足以支撑Unicode字符集。即使只需一个字节的ASCII字符a(二进制0110 0001 ),也必须用 4 个字
节表示,前三字节直接补 0 。

UTF-32采用固定字节编码,程序处理简单,但是占用空间太大,基本很少使用,此时国际标准组织推出了Unicode编码方案UTF-8。

  • UTF-8针对Unicode字符集采取可变⻓编码方案,共分为四个⻓度区,1~4个字节
  • 英文、数字等只占用一个字节(兼容标准的ASCII编码),汉字字符占用^3 个字节

Reader

Reader读取字符流,子类必须实现read、close方法,大部分子类会覆盖Reader中方法,提供更高效
的操作或者一些额外的功能。

Reader提供的方法

1.4.3 Writer

Writer用于将字符写入到字符流中,子类必须实现write、flush、close方法,大部分子类会覆盖Writer
中方法,提供更高效的操作或者一些额外的功能。Note: 下图中少了一个常用的PrintWriter

操作使用

一个字符流通常包装一个字节流,通过字节流去实现底层物理IO操作,字符流处理字符与字节之间的数据转换。在Java中有两个通用的字节到字符的桥接流,InputStreamReader与 OutputStreamReader。

public class CopyCharacters {
public static void main(String[] args) {
try (InputStreamReader isr = new InputStreamReader(
new FileInputStream("D://test/source.txt"));
OutputStreamWriter osw = new OutputStreamWriter(
new FileOutputStream("D://test/target.txt"))) {
int c;
while((c=isr.read()) != -1) {
System.out.println(c);
osw.write(c);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
字符流编码

上述main方法在构造字符输入输出流时,未明确指定读写字符编码,字符流最终采用的是系统平台默
认编码。

  • Windows中文系统默认是GBK
  • Linux系统默认是UTF-8

Java在安装时,根据系统编码自动设置Java系统属性,通过如下命令查看Java系统属性

1 java -XshowSettings:property -version

Java系统属性值中有 2 个与编码相关的属性

  • file.encoding :这个非常重要,在Java中读取文件、URLEncode、字符串数据编码等都与此属性
    有关。
  • sun.jnu.encoding:不用关注,用于JVM查找加载class的类名路径编码等

注意:在Windows下开发时,使用Java命令查看Java系统属性时,file.encoding=GBK,当使用IDEA
工具开发项目时,通过IDEA给项目又设置了UTF-8编码,最终IDEA运行Java程序时,会通过-Dfile.encoding=UTF-8去覆盖默认的编码。当开发过程中遇⻅字符乱码时,需要关注JVM实际运行时的系统属性,也可通过代码获取。

1 String fileEncoding = System.getProperty("file.encoding")

指定字符编码构造字符输入输出流

// 指定UTF-8编码读字符数据
new InputStreamReader(new FileInputStream("D://test/source.txt"), StandardCharse
// 指定UTF-8编码写字符数据
new OutputStreamWriter(new FileOutputStream("D://test/target.txt"), StandardChar

读写字符数据时编码必须保持一致,否则会出现字符乱码。
在中文Windows上使用记事本新建D://test/source.txt文件,写入 2023,星云训练营 ,然后以ANSI编码 保存,在Windows中文系统上ANSI处理中文时就是GBK。

文件读写字符流

Java中还提供了FileReader、FileWriter简化了从文件读写字符,其内部自动封装包裹了对应的文件字节流,但是采用FileReader、FileWriter时只能使用JVM默认编码,无法单独设置读取字符的编码,因 此统一编码非常重要。

// InputStreamReader从文件读取字符内容
new InputStreamReader(new FileInputStream("D://test/source.txt"), StandardCharse
// OutputStreamWriter将字符内容写入文件
new OutputStreamWriter(new FileOutputStream("D://test/target.txt"), StandardChar

使用FileReader、FileWriter读写字符内容

public class CopyFileCharacters {

public static void main(String[] args) {
try (FileReader fr = new FileReader("D://test/source.txt");
FileWriter fw = new FileWriter("D://test/target.txt")) {
// 查看FileReader读取字符编码
System.out.println(fr.getEncoding());
// 查看FileWriter写入字符编码
System.out.println(fw.getEncoding());
int c;
while ((c = fr.read()) != -1) {
System.out.println(c);
fw.write(c);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

如果需要追加内容到文件中,请使用FileWriter两个参数的构造方法,第二个参数设置为true

public FileWriter(File file, boolean append)
// 第二个参数为true,字节内容会从文件末尾开始写入
FileWriter fw = new FileWriter("D://test/target.txt", true);

字符流IO经常会以更大单位读取字符,最常用的就是按行读取字符。一行包括一系列字符组成的字符串以及末尾的行结束符,行结束符可以是回⻋换行符\r\n,也可以是单个回⻋键字符\r,或者单个换行符\n。不同的操作系统,其换行符可能有所不同。

  • Dos、Windows采用回⻋+换行符(CR+LF)表示下一行,即字符表现形式\r\n
  • Unix、Linux采用换行符(LF)表示下一行,字符表现形式为\n
  • Mac采用回⻋符(CR)表示下一行,字符表现形式为\r

CR回⻋符ascii码十进制为13, 换行符ascii码十进制为 10 。

支持按行读写的字符流有BufferedReader、BufferedWriter、PrintWriter等,根据操作系统自动处理行结束符。