Java:Java中的输入输出流和内外编码转换

文章目录

  • Java中的编码转换
  • 编码和`java.io.Reader`类
  • 输入流中的编码转换和缓冲区

Java中的编码转换

有一个文本文件text.txt,其编码为UTF-8,内容为:

1中A

这个文件在计算机中是以二进制形式存在的,其二进制内容用16进制表示为:

31 e4 b8 ad 41

这很好理解,看以下UTF-8编码对应关系:

字符 UTF-8编码 UTF-16编码 Unicode代码点
‘1’ 0x31 0x0031 0x31
‘中’ 0xE4B8AD 0x4E2D 0x4E2D
‘A’ 0x41 0x0041 0x41

如果我们用以下方式读取文件:

public static void main(String[] args) throws Exception {
     
  File file = new File("text.txt");
  FileInputStream fis = new FileInputStream(file);
  byte[] barr = new byte[5];
  fis.read(barr, 0 , 5);
  fis.close();
  
  for(byte b: barr) {
     
    System.out.print(Integer.toHexString((int)b & 0xff) + " ");
    // 31 e4 b8 ad 41 
  }
}

以上是进行的二进制读取,和编码无关。关键是看下面:

public static void main(String[] args) throws Exception {
     
  File file = new File("text.txt");
  FileReader fr = new FileReader(file);
  char[] carr = new char[3];
  fr.read(carr, 0, 3);
  fr.close();

  for (char c : carr) {
     
    System.out.print(c + " ");
    // 1 中 A
  }
  System.out.println();

  String str = new String(carr);
  System.out.println(str);
  // 1中A

  byte[] barr = str.getBytes("UTF-8");
  for (byte b : barr) {
     
    System.out.print(Integer.toHexString((int) b & 0xff) + " ");
    // 31 e4 b8 ad 41
  }
}

这段代码很有意思,里面涉及两次编码转换——

  • 一次是由FileReader进行的;
  • 一次是调用String类的getBytes方法进行的。

首先要明白,Java中的char类型是怎么在内存存储的:Java中,一个char类型,占用2个字节。

这就有个问题:'中'的UTF-8编码为3个字节,而我们用FileReader读取text.txt文件,只读取了3个char
其中'中'只占用一个char。这说明,'中'在内存中不是以UTF-8存储的。

这有个重要事实就是:

The Java platform uses the UTF-16 representation in char arrays and in the String and StringBuffer classes.

也就是说:Java平台对字符实际上是以UTF-16编码的方式存储的。

所以text.txt被FileReader读入char[] carr后,实际上被进行了如下编码转换:

由UTF-8编码的:

31 e4 b8 ad 41

转为了UTF-16编码的(共3个char,每个2字节):

00 31 4e 2d 00 41

使用String的构造函数String(char[] value),不需要进行编码转换,因为内部都是采用的UTF-16,所以char[]String对象字符在内存里存储的二进制内容是一样的。

然后调用String对象的getBytes("UTF-8")方法时,又会发生一次编码转换:

由UTF-16编码的:

00 31 4e 2d 00 41

转为了UTF-8编码的:

31 e4 b8 ad 41

编码和java.io.Reader

凡是类名为java.io.***Reader的类都是继承自java.io.Reader父类。Reader顾名思义就是读取,只有字符才是人可以直接读取的,所以这些类都是读取字符的。

同理,那些java.io.***Write的类也一样,是写字符的。

当且仅当处理字符时需要涉及编码转换,所以这些类的对象都是包含了编码信息的。

输入流中的编码转换和缓冲区

我们知道读取文本文件的时候,经常会像下面这样,一层套一层的:

BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream("file.in"), "GBK"));

下面解释下每一层是干嘛的。

  • FileInputStream:读取文件的二进制内容,不会进行任何转换。
  • InputStreamReader:把文件的二进制内容按照参数指定的编码方式进行解码转换,最后得到的是Java平台使用的UTF-16编码后的内容。
  • BufferedReader:这些类提供的方法都是数据只能按顺序读取一次。而此类加了一个缓冲区,并且实现了markreset方法,可以使用mark方法标记数据流的当前位置,然后用reset方法将该流重新定位到此点。

InputStreamFileInputStreamInputStreamReaderFileReader都不支持markresetBufferedReader支持(利用内部的缓冲区),可以用boolean markSupported()方法查看是否支持。

如果没有BufferedReader层,每次使用InputStreamReader.read()方法读取一次,都会导致从底层输入流(此处是文件中)读取字节,然后再进行编码转换后返回,效率极低。而BufferedReader层提供的缓冲区可以实现高效读取。

另外:FileReader("file.in")完全等效于InputStreamReader(new FileInputStream("file.in")),是采用默认字符编码读取文件的简便方式。

你可能感兴趣的:(Java,编程语言/Java,Java,编码转换,IO,输入输出流,内外码)