失乐园

技术之路

BlogJava 联系 聚合 管理
  19 Posts :: 44 Stories :: 40 Comments :: 0 Trackbacks
       大多数文本编辑器在打开文件时都能够自动检测文件的编码,那它是怎样做到的?我虽然没有实现过一个文本编辑器,但是可以猜测的是,它有一个默认的编码集合,然后尝试用每一个编码去解码打开的文件,如果能够解码则表示这就是文件的正确编码。有一些特殊情况,有些编码在文件开头有特殊的标记字节,因而可以很快检测,这里不考虑。现在的核心问题就是如何决定一个编码是否能够解码一个文件,在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),它要更复杂一些。
Java代码
/**
* 使用指定的字符集解码字节输入流,并将它写入到字符输出流中,如果发生解码错误则返回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());  

输出结果:
Java代码
true
abc中国  
true
abc涓 浗

可以看到用utf-8编码的字节流仍然可以用gbk进行解码,但是解码的结果却不对。这是偶然情况,将字符串换成"中国人",则用gbk就不能解码了。
posted on 2010-08-17 09:41 狄浩 阅读(17382) 评论(0)  编辑  收藏

只有注册用户登录后才能发表评论。


网站导航: