ここでは、いくつかの要因が働いています。
- テキスト ファイルには、エンコーディングを説明するための固有のメタデータがありません (山かっこ税の話ですが、XML が人気があるのには理由があります)。
- Windows のデフォルトのエンコーディングは、値の範囲が限定された 8 ビット (または 2 バイト) の " ANSI " 文字セットのままです。この形式で記述されたテキスト ファイルは移植性がありません。
- Unicode ファイルと ANSI ファイルを区別するために、Windows アプリはファイルの先頭にあるバイト オーダー マークの存在に依存しています (厳密にはそうではありません - Raymond Chen は説明しています)。理論的には、BOM はデータのエンディアン(バイト順) を示すために存在します。UTF-8 の場合、バイト オーダーは 1 つしかありませんが、Windows アプリはマーカー バイトに依存して、それが Unicode であることを自動的に判断します (ただし、メモ帳には、開く/保存するダイアログにエンコード オプションがあることに注意してください)。
- Java は UTF-8 BOM を自動的に書き込まないため、Java が壊れていると言うのは誤りです。たとえば、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 はデフォルトのエンコーディングを使用するため、意味のあるものを出力するかどうかは、プラットフォームと構成によって異なります。)