使用java NIO FileChannel读取文件并解决中文乱码问题

      FileChannel 是java.nio下的一个连接文件的通道。通过此通道能够方便的实现对文件的读写操作。FileChannel 操作是ByteBuffer,能够读取文件字节到ByteBuffer或将ByteBuffer中的字节写入文件。

     由于读取的文件内容涉及到中文(假设文件编码是utf8),FileChannel读取的是byte,而一个中文字符可能占2个或3byte,这样很容易导致一次读取到的byte不足以全部转换为中文,如果按照常规的方式new String(byte[],"utf-8"),根本无法解决乱码。

     本文通过一个完整的示例,展示了如果用FileChannel读取中文文件并解决乱码问题。

  1. 打开文件通道
    File file = new File(getClass().getResource("").getPath(), "中文测试.txt");
    RandomAccessFile raFile = new RandomAccessFile(file, "rw");
    FileChannel fChannel = raFile.getChannel();

     

  2. 从文件通道读取字节到buffer
    ByteBuffer bBuf = ByteBuffer.allocate(32);
    
    int bytesRead = fChannel.read(bBuf);

     

  3. 转码。以utf编码转换ByteBuffer到CharBuffer
    CharBuffer cBuf = CharBuffer.allocate(32);
    decoder.decode(bBuf, cBuf, true);

     

  4. 判断decode操作后是否有未处理完的字节,如果有,则缓存等待下次处理
    byte[] remainByte = null;
    int leftNum = 0;
    
    leftNum = bBuf.limit() - bBuf.position();
    if (leftNum > 0) { // 记录未转换完的字节
    		remainByte = new byte[leftNum];
    		bBuf.get(remainByte, 0, leftNum);
    }

     

  5. 将上步缓存的字节写回ByteBuffer,然后进行下一次读取
    if (remainByte != null) {
    	 bBuf.put(remainByte);
    }
    bytesRead = fChannel.read(bBuf);

    转码采用了CharsetDecoder这个类。因为文件是utf8编码的,所以Charset用的是utf8。CharsetDecoder对一次读取的byte做转码操作时,仅仅转码尽可能多的字节,此次转码不了的字节需要缓存,等待下次读取再转换,上述步骤4、步骤5很关键。如果没有这两步,直接导致转码后的字符与原文件相比,不全,会少很多。

         完整示例代码如下:

public class FileChannelTest {

	public static void main(String[] args) throws IOException {
		FileChannelTest fcChannelTest = new FileChannelTest();
		fcChannelTest.readFile();
	}

	public void readFile() throws IOException {
		// 文件编码是utf8,需要用utf8解码
		Charset charset = Charset.forName("utf-8");
		CharsetDecoder decoder = charset.newDecoder();

		File file = new File(getClass().getResource("").getPath(), "中文测试.txt");
		RandomAccessFile raFile = new RandomAccessFile(file, "rw");
		FileChannel fChannel = raFile.getChannel();

		ByteBuffer bBuf = ByteBuffer.allocate(32); // 缓存大小设置为32个字节。仅仅是测试用。
		CharBuffer cBuf = CharBuffer.allocate(32);

		int bytesRead = fChannel.read(bBuf); // 从文件通道读取字节到buffer.
		char[] tmp = null; // 临时存放转码后的字符
		byte[] remainByte = null;// 存放decode操作后未处理完的字节。decode仅仅转码尽可能多的字节,此次转码不了的字节需要缓存,下次再转
		int leftNum = 0; // 未转码的字节数
		while (bytesRead != -1) {

			bBuf.flip(); // 切换buffer从写模式到读模式
			decoder.decode(bBuf, cBuf, true); // 以utf8编码转换ByteBuffer到CharBuffer
			cBuf.flip(); // 切换buffer从写模式到读模式
			remainByte = null;
			leftNum = bBuf.limit() - bBuf.position();
			if (leftNum > 0) { // 记录未转换完的字节
				remainByte = new byte[leftNum];
				bBuf.get(remainByte, 0, leftNum);
			}

			// 输出已转换的字符
			tmp = new char[cBuf.length()];
			while (cBuf.hasRemaining()) {
				cBuf.get(tmp);
				System.out.print(new String(tmp));
			}

			bBuf.clear(); // 切换buffer从读模式到写模式
			cBuf.clear(); // 切换buffer从读模式到写模式
			if (remainByte != null) {
				bBuf.put(remainByte); // 将未转换完的字节写入bBuf,与下次读取的byte一起转换
			}
			bytesRead = fChannel.read(bBuf);
		}
		raFile.close();
	}
}

   

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