Тук действат няколко фактора:
- Текстовите файлове нямат присъщи метаданни за описание на тяхното кодиране (въпреки всички приказки за данъци в ъглови скоби, има причини XML да е популярен)
- Кодирането по подразбиране за Windows все още е 8bit (или doublebyte) "ANSI" набор от знаци с ограничен диапазон от стойности - текстовите файлове, написани в този формат, не са преносими
- За да различат Unicode файл от ANSI файл, приложенията на Windows разчитат на наличието на маркировка за ред на байтове в началото на файла (не е абсолютно вярно - Реймънд Чен обяснява). На теория BOM е там, за да ви каже endiance (ред на байтовете) на данните. За UTF-8, въпреки че има само един ред на байтовете, приложенията на Windows разчитат на маркерните байтове, за да разберат автоматично, че е Unicode (въпреки че ще забележите, че Notepad има опция за кодиране в своите диалогови прозорци за отваряне/запазване).
- Погрешно е да се каже, че Java е повредена, защото не записва UTF-8 BOM автоматично. В Unix системи би било грешка да се напише BOM в скрипт файл, например, и много Unix системи използват UTF-8 като свое кодиране по подразбиране. Има моменти, когато не го искате и в Windows, като например когато добавяте данни към съществуващ файл:
fos = new FileOutputStream(FileName,Append);
Ето метод за надеждно добавяне на UTF-8 данни към файл:
private static void writeUtf8ToFile(File file, boolean append, String data)
throws IOException {
boolean skipBOM = append && file.isFile() && (file.length() > 0);
Closer res = new Closer();
try {
OutputStream out = res.using(new FileOutputStream(file, append));
Writer writer = res.using(new OutputStreamWriter(out, Charset
.forName("UTF-8")));
if (!skipBOM) {
writer.write('\uFEFF');
}
writer.write(data);
} finally {
res.close();
}
}
Употреба:
public static void main(String[] args) throws IOException {
String chinese = "\u4E0A\u6D77";
boolean append = true;
writeUtf8ToFile(new File("chinese.txt"), append, chinese);
}
Забележка: ако файлът вече е съществувал и сте избрали да добавите и съществуващите данни не са UTF-8 кодирани, единственото нещо, което кодът ще създаде, е бъркотия.
Ето типа Closer
, използван в този код:
public class Closer implements Closeable {
private Closeable closeable;
public <T extends Closeable> T using(T t) {
closeable = t;
return t;
}
@Override public void close() throws IOException {
if (closeable != null) {
closeable.close();
}
}
}
Този код прави най-добро предположение в стил Windows за това как да се чете файлът въз основа на маркировките за ред на байтове:
private static final Charset[] UTF_ENCODINGS = { Charset.forName("UTF-8"),
Charset.forName("UTF-16LE"), Charset.forName("UTF-16BE") };
private static Charset getEncoding(InputStream in) throws IOException {
charsetLoop: for (Charset encodings : UTF_ENCODINGS) {
byte[] bom = "\uFEFF".getBytes(encodings);
in.mark(bom.length);
for (byte b : bom) {
if ((0xFF & b) != in.read()) {
in.reset();
continue charsetLoop;
}
}
return encodings;
}
return Charset.defaultCharset();
}
private static String readText(File file) throws IOException {
Closer res = new Closer();
try {
InputStream in = res.using(new FileInputStream(file));
InputStream bin = res.using(new BufferedInputStream(in));
Reader reader = res.using(new InputStreamReader(bin, getEncoding(bin)));
StringBuilder out = new StringBuilder();
for (int ch = reader.read(); ch != -1; ch = reader.read())
out.append((char) ch);
return out.toString();
} finally {
res.close();
}
}
Употреба:
public static void main(String[] args) throws IOException {
System.out.println(readText(new File("chinese.txt")));
}
(System.out използва кодирането по подразбиране, така че дали ще отпечата нещо разумно зависи от вашата платформа и конфигурация.)
person
McDowell
schedule
20.04.2009