大多數(shù)文本編輯器在打開(kāi)文件時(shí)都能夠自動(dòng)檢測(cè)文件的編碼,那它是怎樣做到的?我雖然沒(méi)有實(shí)現(xiàn)過(guò)一個(gè)文本編輯器,但是可以猜測(cè)的是,它有一個(gè)默認(rèn)的編碼集合,然后嘗試用每一個(gè)編碼去解碼打開(kāi)的文件,如果能夠解碼則表示這就是文件的正確編碼。有一些特殊情況,有些編碼在文件開(kāi)頭有特殊的標(biāo)記字節(jié),因而可以很快檢測(cè),這里不考慮。現(xiàn)在的核心問(wèn)題就是如何決定一個(gè)編碼是否能夠解碼一個(gè)文件,在Java1.4中可以利用nio中的Charset來(lái)解決這個(gè)問(wèn)題。
/**
* 測(cè)試輸入字節(jié)流是否能夠使用指定的字符集解碼。
*/
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;
}
要理解上面的代碼必須熟悉對(duì)Buffer和Channel的操作以及解碼的過(guò)程。上面的代碼只是決定能不能解碼,下面代碼能夠解碼出的內(nèi)容寫(xiě)到字符輸出流中(也就是Writer),它要更復(fù)雜一些。
Java代碼
/**
* 使用指定的字符集解碼字節(jié)輸入流,并將它寫(xiě)入到字符輸出流中,如果發(fā)生解碼錯(cuò)誤則返回false,否則返回true,
* 輸入中的無(wú)效字節(jié)序列將被忽略。
*/
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的大小不能太小以至于比一個(gè)字符的最大字節(jié)數(shù)還要小,比如說(shuō)utf-8的每個(gè)字符最多可能占用4個(gè)字節(jié),如果設(shè)置byteBuffer的大小為3,解碼結(jié)果可能總是CoderResult.UNDERFLOW,但是又無(wú)法再往byteBuffer填充數(shù)據(jù),因而會(huì)出現(xiàn)死循環(huán)。
另外要注意的是,程序可能得到錯(cuò)誤的結(jié)果,如:
String s = "abc中國(guó)";
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());
輸出結(jié)果:
Java代碼
true
abc中國(guó)
true
abc涓 浗
可以看到用utf-8編碼的字節(jié)流仍然可以用gbk進(jìn)行解碼,但是解碼的結(jié)果卻不對(duì)。這是偶然情況,將字符串換成"中國(guó)人",則用gbk就不能解碼了。