JAVA NIO(三):缓冲区的相互转换及中文乱码的解决方案

在Java IO中,Channel(通道)只能直接与ByteBuffer进行通信,这样我们可能会用ByteBuffer的视图来解决数据的转换问题,如将字符串转换为二进制缓冲区,整数缓冲区转换为二进制缓冲区,示例如下:

ByteBuffer buffer = ByteBuffer.allocate(100);
//  获取缓冲区的视图,但与ByteBuffer的mark、position、limit互相独立
CharBuffer charBuff = buffer.asCharBuffer();
//  更容易进行字符操作
charBuff.put("Hello, World!");
//  创建输出通道
WritableByteChannel outChannel = Channels.newChannel(System.out);
//  写入缓冲区数据
outChannel.write(byteBuf);
outChannel.close();

采用上述的方法,会导致以下几个问题:
1. 难以控制字符与字节的转换过程;
2. 难以获取缓冲区的当前位置与剩余空间;
3. 难以控制缓冲区的阶段读取,如20-50,50-70;

在上面的例子中,我们将CharBuffer与ByteBuffer进行了转换,极大地简化了字符的操作行为,但可惜的是,字符依赖于系统编码(这里是UTF-8,每个字符占两个字节),写入的时候每个字符占两个字节,但读取的时候却不知道编码,最后导致出现乱码。

要解决上述的问题,只有自己控制字符与字节转换的过程,才能保证字符的输入与输出保持一致,如下:

//  创建输出通道
WritableByteChannel outChannel = Channels.newChannel(System.out);
String text = "你好,JAVA!";
byte[] arr = text.getBytes("UTF-8");
ByteBuffer byteBuf = ByteBuffer.wrap(arr);
outChannel.write(byteBuf);
outChannel.close();

使用上述的方法,优点是能保证字符的正确转换,但是很显然,效率太低了,还有更好的办法吗?

经过实测,通过字节转换的效率并不低,并且比CharBuffer放入字符串的速度高4-5倍,对比数据如下:

字节数量 ByteBuffer直接放入数据 通过CharBuffer放入数据
1200 PT0.000087808S PT0.000437138S
2400 PT0.000099591S PT0.000669771S
4800 PT0.000140644S PT0.000880357S

这里最有意思的地方在于,CharBuffer统一以两个字节存储一个字符,而ByteBuffer则依赖于编码,可能是两个字节,也可能是三个字节,最后导致512个字符,CharBuffer的limit为512,换算为ByteBuffer后为1024个字节,而直接用ByteBuffer存储字节则为1200(依赖于具体的字符,不定),如下:

字符数量 ByteBuffer直接放入数据 通过CharBuffer放入数据
512 1200 512
1024 2400 1024
2048 4800 1024

关于乱码,如果是以CharBuffer放入的数据,因为没有对应的字符集解码器,必定是乱码,只有通过同样的CharBuufer方式读出,才能正确解决,所以在这里,char是一种数据存储格式,就如同long数据、int数据一样,并不是操作系统上对应的字符集,所以无法解析。尤其是文本编辑器提示“此文档包含当前文本编码无法处理的字符”,则可以断定是以字符数据格式进行存储,处理方式如下:

FileInputStream fis = new FileInputStream(file);
FileChannel fc = fis.getChannel();
ByteBuffer bbuf = ByteBuffer.allocateDirect(1024);
long offset = 0;
long nums = 0;
while((nums = fc.read(bbuf, offset)) > 0) {
    //  一定要反转回到正确位置,CharBuffer视图是基于当前位置创建的
    bbuf.flip();
    //  以字符数据方式进行读取
    CharBuffer cbuf = bbuf.asCharBuffer();
    bbuf.clear();
    offset = offset + nums;
}
fc.close();
fis.close();

结论

  1. 一定要区分字符与字符数据,如果是操作系统上的字符,则可对应到文本编码,并有相应的字符集编码,而字符数据必定是二进制编码;
  2. 缓冲区存储的是字节(不是字符);
  3. 字符由多个字节组成,如果不能按照指定的顺序与数量读取所需的字节,必然出现乱码。

你可能感兴趣的:(java)