欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-04-15 12:05:33
...

在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. 字符由多个字节组成,如果不能按照指定的顺序与数量读取所需的字节,必然出现乱码。