浅析Java中的IO流 - 转换流

145 阅读5分钟

文章目录


1. 字符编码和字符集

计算中的任何信息在底层都以二进制数进行表示和存储,而我们直观上看到的各种信息,如图像、文字、视频等都是二级制信息转换后的结果。我们将数据按字符存储到计算机的过程称为编码,而将计算机中的二进制信息按照某种规则解析显示出来的过程称为解码。所谓的字符编码就是一套自然语言的字符和二进制数之间的对应规则。

字符集也称编码表,它是一个系统支持的所有字符的集合,包括各国家的文字、标点符号、数字等。常用的字符集有

  • ASCII字符集:美国信息交换标准代码,它是基于拉丁字母的一套电脑编码系统,用于显示现代英语。主要包括控制字符和可显示字符。基本的ASCII字符集使用7位(bits)表示一个字符,共128个字符。ASCII的扩展字符集使用8位表示一个字符,共256个字符

  • GBK字符集:包括GBK2312、GBK和GBK18030,它是一套为显式中文而设计的一套字符集

    GB2312、GBK、GB18030 这几种字符集的主要区别是什么?

  • Unicode字符集:它是为表达任意语言而设计的一套业界标准的字符集,它最多使用4个字节的数字来表示每个字母、符号或文字,一共有三种编码方案:UTF-8、UTF-16和UTF-32,最为常用的是UTF-8

    百度百科 - UTF-8


2. 乱码问题

所谓乱码问题是指当使用系统读取和系统默认的字符集不同的字符集所编码的文件时,读取到的信息就无法正常显示,即呈现乱码的形式。例如,在之前的IO流中,如果使用IDEA进行数据的保存,由于IDEA默认的编码格式为utf-8,那么保存数据的文件的编码格式自然也是utf-8。而且我们使用IO流读取utf-8编码的文件时也可以正常读取,显示的读取数据不会出现乱码。但如果使用IO流读取GBK等其他字符集编码的信息时,就会出现乱码问题。

那么如何解决上述编码和解码所使用的字符集不同而引发的乱码问题呢?我们可以使用Java中的转换流InputStreamReaderOutputStreamWriter解决,它们是Reader和Writer最重要的子类。通过查看API文档中类之间的继承关系可知,之前所学的FileReader和FileWriter的直接父类就是InputStreamReader和OutputStreamWriter,它们是字符流通向字节流的桥梁,可使用指定的char set将读取和写入的字符编码指定字符集格式的字节,如果没有指定char set,则使用系统默认的字符集。

  • InputStreamReader类包含了一个底层输入流,可以从中读取原始字节。它根据指定的编码方式,将这些字节转换为Unicode字符
  • OutputStreamWriter从运行的程序中接收Unicode字符,然后使用指定的编码方式将这些字符转换为字节,再将这些字节写入底层输出流中

3. OutputStreamWriter

OutputStreamWriter的直接父类就是java.io.Writer,因此它可以使用父类中的成员方法:

  • void writer(int c): 写入单个字符
  • void write(char[] cbuf): 写入字符数组
  • abstract void write(char[] cbuf, int off, int len):写入字符数组的某一部分,off为开始索引,len为写入长度
  • void write(String str): 写入字符串
  • void write(String str, int off, int len): 写入字符串的某一部分
  • void flush():刷新该流中的缓冲
  • void close():关闭流对象

它的构造方法有:

  • OutputStreamWriter(OutputStream out):创建使用默认字符编码的OutputStreamWriter
  • OutputStreamWriter(OutputStream out, String charsetName):创建使用指定字符集编码的OutputStreamWriter,其中char set是指定的字符集的名称,不区分大小写,默认是utf-8
public class OutputStreamWriterTest {
    public static void main(String[] args) throws IOException {
        write_utf_8();
        write_gbk();
    }

    private static void write_gbk() throws IOException {
        OutputStreamWriter osw = new OutputStreamWriter(
                new FileOutputStream("test.txt", true), "gbk");
        osw.write("梦想照进现实");
        osw.close();
    }

    private static void write_utf_8() throws IOException {
        OutputStreamWriter osw = new OutputStreamWriter(
                new FileOutputStream("test.txt", true));
        osw.write("hello world");
        osw.close();
    }
}

4. InputStreamReader

InputStreamReader的直接父类是java.io.Reader,所以它也可以使用父类共性的成员方法:

  • public void close():关闭此流并释放于此六相关联的任何系统资源
  • public int read():从输入流中读取一个字符
  • public int read(char[] cbuf):从输入流中读取一些字符,并将它们存储到字符数组中

它个构造方法有:

  • InputStreamReader(InputStream in):创建一个使用默认字符集的InputStreamReader
  • InputStreamReader(InputStream in, String charsetName):创建一个使用指定字符集的InputStreamReader

InputStreamReader的使用步骤:

  • 创建InputStreamReader对象,构造方法中传递字节输入流和指定的字符集名称
  • 使用InputStreamReader对象的read()读取文件中的信息
  • 释放资源

构造方法中指定的字符集名称和文件所使用的字符集要一致,否则会出现乱码问题。

public class InputStreamReaderTest {
    public static void main(String[] args) throws IOException {
        InputStreamReader isr = new InputStreamReader(
                new FileInputStream("test.txt"), "gbk");
        int len = 0;
        while ((len = isr.read()) != -1){
            System.out.println((char)len);
        }
        isr.close();
    }
}

5. 编码方式

Java中支持的所有编码方式如下所示:

StandardCharsets.UTF-8
StandardCharsets.UTF-16
StandardCharsets.UTF-16BE
StandardCharsets.UTF-16LE
StandardCharsets.ISO_8859_1
StandardCharsets.US_ASCII

因此,除了使用上面直接通过字符串表示编码的方式外,更好的选择是通过StandardCharsets常量来指定,这样可以减少拼写错误的出现。