Java中常用编码格式及I/O操作中的编码

一、常见的字符编码格式

(1)ASCII码。学过计算机的人都知道ASCII码,总共有128个,用一个字节的低7位表示,0~31是控制字符,如换行、回车、删除等;32~126是可打印字符,可以通过键盘输入并且能够显示出来。

(2)ISO-8859-1。ASCII码的128个字符显然是不够用的,于是ISO组织在ASCII码的基础上又制定了一些标准用来扩展ASCII编码,它们是ISO-8859-1~ISO-8859-15,其中ISO-8859-1涵盖了大多数西欧语言字符,应用最广泛。ISO-8859-1仍然采用单字节编码,总共能表示256个字符。

(3)GBK。GBK的全称是《汉字内码扩展规范》,是全国信息技术标准化技术委员会于1995年为Windows 95制定的新的汉字内码规范,它的出现是为了扩展GB2312,加入更多的汉字,其编码范围是8140~FEFE(去掉XX7F),总共有23940个码位,能表示21003个汉字。GBK的编码是和GB2312兼容的,也就是说,采用GB2312编码的汉字可以用GBK来解码,并且不会有乱码。

(4)UTF-16。说到UTF必须要提到Unicode(Universal Code,统一码),ISO试图创建一个全新的超语言字典,让世界上所有的语言都可以通过这本字典来相互翻译。可想而知这个字典该是多么得复杂,关于Unicode的详细规范可以参考相应文档。Unicode是Java和XML的基础,UTF-16具体定义了Unicode字符在计算机中的存取方法。UTF-16用两个字节来表示Unicode转化格式,这个是定长的表示方法,不论什么字符都可以采用两个字节表示,两个字节是16个bit,所以叫UTF-16。UTF-16表示字符非常方便,每两个字节表示一个字符,在字符串操作时可以大大简化操作,这也是Java以UTF-16作为内存的字符存储格式的一个很重要的原因。

(5)UTF-8。UTF-16统一采用两个字节表示一个字符,虽然在表示上非常简单方便,但是也有其缺点,因为很大一部分字符用一个字节就可以表示,现在却要用两个字节表示,存储空间放大了一倍,在网络带宽还非常有限的今天,势必会增大网络传输的流量,而且也没必要。UTF-8则采用了一种变长技术,每个编码区域都有不同的字码长度。不同类型的字符可以由1~6个字节组成。如果一个字节的最高位(第8位)为0,表示这是一个ASCII字符(00 -7F)。可见,所有ASCII编码已经是UTF-8了。如果一个字节以11开头,连续的1的个数即暗示这个字符的字节数,例如:110xxxxx代表它是双字节UTF-8字符的首字节。如果一个字节以10开始,表示它不是首字节,需要向前查找才能得到当前字符的首字节。

二、I/O操作中的编码

我们知道涉及编码的地方一般都在字符到字节或者字节到字符的转换上,而需要这种转换的场景主要是在I/O操作中,I/O包括磁盘I/O和网络I/O。附图1是Java中处理I/O读操作的接口。

Java中常用编码格式及I/O操作中的编码_第1张图片

Reader类是Java的I/O中读字符的父类,而InputStream类是读字节的父类。InputStreamReader类是关联字节到字符的桥梁,它负责在I/O过程中处理读取字节到字符的转换,而具体字节到字符的解码由StreamDecoder去实现,在StreamDecoder的解码过程中必须由用户指定Charset编码格式。值得注意的是,如果你没有指定Charset,将使用本地环境中的默认字符集,例如在中文环境中将使用GBK编码。

写操作的情况类似,写字符的父类是Writer,写字节的父类是OutputStream,通过OutputStreamWriter类完成字符到字节的转换。附图2是Java中处理I/O写操作的接口。 

Java中常用编码格式及I/O操作中的编码_第2张图片 同样由StreamEncoder类负责将字符编码成字节,编码格式和默认编码规则与解码是一致的。

 如下面一段代码,实现了文件的读写功能。

public class Test {
    public static void main(String[] args) {
        String file = "c:/write.txt";
        String charset = "GB2312";
        OutputStreamWriter writer = null;
        FileOutputStream outputStream = null;
         FileInputStream inputStream = null;
         InputStreamReader reader = null;
         try {
                  //  写字符转换成字节流
                  outputStream = new FileOutputStream(file);
                  writer = new OutputStreamWriter(outputStream, charset);
                  writer.write("这是要保存的中文字符");
                  //  读取字节转换成字符
                  inputStream = new FileInputStream(file);
                  reader = new InputStreamReader(inputStream, charset);
                  StringBuffer buffer = new StringBuffer();
                  char[] buf = new char[64];
                  int count = 0;
                  while ((count = reader.read(buf)) != -1) {
                          buffer.append(buf, 0, count);
                  }
                  System.out.println(buffer);
         } catch (FileNotFoundException e) {
                  // TODO Auto-generated catch block
                  e.printStackTrace();
         } catch (UnsupportedEncodingException e) {
                  // TODO Auto-generated catch block
                  e.printStackTrace();
         } catch (IOException e) {
                  // TODO Auto-generated catch block
                  e.printStackTrace();
         } finally {
                  try {
                          writer.close();
                          // reader.close();
                  } catch (IOException e) {
                          // TODO Auto-generated catch block
                          e.printStackTrace();
                  }
                  try {
                          reader.close();
                  } catch (IOException e) {
                          // TODO Auto-generated catch block
                          e.printStackTrace();
                  }
         }
 }
}

在应用程序中涉及到I/O操作时只要注意指定统一的编解码字符集Charset,一般不会出现乱码问题。如果未指定字符编码且编解码都在中文环境中,取操作系统默认编码,通常也没问题。但还是强烈建议不要使用操作系统的默认编码,因为这样,你的应用程序的编码格式就和运行环境绑定起来了,在跨环境下很可能会出现乱码问题。

参考:Java高级特性编程及实战/肖睿,龙浩,孙琳主编.--北京:人民邮电出版社,2018.1

你可能感兴趣的:(java,java)