用Java自动检查文件的编码方式

大多数文本编辑器在打开文件时都能够自动检测文件的编码,那它是怎样做到的呢?我虽然没有实现过一个文本编辑器,但是可以猜测的是,它有一个默认的编码集合,然后尝试用每一个编码去解码打开的文件,如果能够解码则表示这就是文件的正确编码。有一些特殊情况,有些编码在文件开头有特殊的标记字节,因而可以很快检测,这里不考虑。现在的核心问题就是如何决定一个编码是否能够解码一个文件,在Java1.4中可以利用nio中的Charset来解决这个问题。

 

	/**
	 * 测试输入字节流是否能够使用指定的字符集解码。
	 */
	public static boolean canDecode(InputStream input, Charset charset) throws IOException {
		ReadableByteChannel channel = Channels.newChannel(input);
		CharsetDecoder decoder = charset.newDecoder();

		ByteBuffer byteBuffer = ByteBuffer.allocate(2048);
		CharBuffer charBuffer = CharBuffer.allocate(1024);
	
		boolean endOfInput = false;
		while (!endOfInput) {
			int n = channel.read(byteBuffer);
			byteBuffer.flip(); // flip so it can be drained
			
			endOfInput = (n == -1);
			CoderResult coderResult = decoder.decode(byteBuffer, charBuffer, endOfInput);
			charBuffer.clear();
			if (coderResult == CoderResult.OVERFLOW) {
				while (coderResult == CoderResult.OVERFLOW) {
					coderResult = decoder.decode(byteBuffer, charBuffer, endOfInput);
					charBuffer.clear();
				}
			}
			if (coderResult.isError()) {
				return false;
			}
			byteBuffer.compact(); // compact so it can be refilled
		}
		CoderResult coderResult;
		while ((coderResult = decoder.flush(charBuffer)) == CoderResult.OVERFLOW) {
			charBuffer.clear();
		}
		if (coderResult.isError()) {
			return false;
		}
		
		return true;
	}

 要理解上面的代码必须熟悉对Buffer和Channel的操作以及解码的过程。上面的代码只是决定能不能解码,下面代码能够解码出的内容写到字符输出流中(也就是Writer),它要更复杂一些。

 

	
	/**
	 * 使用指定的字符集解码字节输入流,并将它写入到字符输出流中,如果发生解码错误则返回false,否则返回true,
	 * 输入中的无效字节序列将被忽略。
	 */
	public static boolean decode(InputStream input, Writer output, Charset charset) throws IOException {
		ReadableByteChannel channel = Channels.newChannel(input);
		CharsetDecoder decoder = charset.newDecoder();

		ByteBuffer byteBuffer = ByteBuffer.allocate(2048);
		CharBuffer charBuffer = CharBuffer.allocate(1024);
	
		boolean endOfInput = false;
		boolean error = false;
		while (!endOfInput) {
			int n = channel.read(byteBuffer);
			byteBuffer.flip(); // flip so it can be drained
			
			endOfInput = (n == -1);
			CoderResult coderResult = decoder.decode(byteBuffer, charBuffer, endOfInput);
			error = drainCharBuffer(error, byteBuffer, charBuffer, coderResult, output);
			if (coderResult != CoderResult.UNDERFLOW) {
				while (coderResult != CoderResult.UNDERFLOW) {
					coderResult = decoder.decode(byteBuffer, charBuffer, endOfInput);
					error = drainCharBuffer(error, byteBuffer, charBuffer, coderResult, output);
				}
			}
			byteBuffer.compact(); // compact so it can be refilled
		}
		CoderResult coderResult;
		while ((coderResult = decoder.flush(charBuffer)) != CoderResult.UNDERFLOW) {
			error = drainCharBuffer(error, byteBuffer, charBuffer, coderResult, output);
		}
		error = drainCharBuffer(error, byteBuffer, charBuffer, coderResult, output);
		
		output.flush();
		return !error;
	}
	
	private static boolean drainCharBuffer(boolean error, ByteBuffer byteBuffer, 
			CharBuffer charBuffer, CoderResult coderResult, Writer output) throws IOException {
		// write charBuffer to output
		charBuffer.flip();
		if (charBuffer.hasRemaining())
			output.write(charBuffer.toString());
		charBuffer.clear();
		
		if (coderResult.isError()) {
			error = true;
			byteBuffer.position(byteBuffer.position() + coderResult.length()); // ignore invalid byte sequence
		}
		return error;
	}

 

要注意byteBuffer的大小不能太小以至于比一个字符的最大字节数还要小,比如说utf-8的每个字符最多可能占用4个字节,如果设置byteBuffer的大小为3,解码结果可能总是CoderResult.UNDERFLOW,但是又无法再往byteBuffer填充数据,因而会出现死循环。

 

另外要注意的是,程序可能得到错误的结果,如:

		String s = "abc中国";
		byte[] utf8Bytes = s.getBytes(Charset.forName("utf-8"));
		byte[] gbkBytes = s.getBytes(Charset.forName("gbk"));
		CharArrayWriter writer = new CharArrayWriter();
		System.out.println(decode(new ByteArrayInputStream(utf8Bytes), writer, Charset.forName("utf-8")));
		System.out.println(writer.toString());
		writer = new CharArrayWriter();
		System.out.println(decode(new ByteArrayInputStream(utf8Bytes), writer, Charset.forName("gbk")));
		System.out.println(writer.toString());
 

输出结果:

true
abc中国
true
abc涓浗

 可以看到用utf-8编码的字节流仍然可以用gbk进行解码,但是解码的结果却不对。这是偶然情况,将字符串换成"中国人",则用gbk就不能解码了。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值