BOMの有無を判別し、UTFを読み分ける InputStreamReader のサンプルコード

shift-jis と utf-8 の混在問題に関する記事(リンクリスト)に戻る

この記事は情技師(ITエンジニア)向けの記事です。

Java 8 の BufferedReader, InputStreamReader, FileInputStream を使用してBOM有り、またはBOMなしのテキストファイルを読み込むサンプルコードを載せておく。

このサンプルは検索すれば沢山あるが、Java は広く普及している為、検索にノイズが多い。

特に多いのが「UTF-8 テキストの先頭BOMを読み飛ばす」というサンプルが多く

「複数のBOMを読み分ける」

「BOMなしテキストも読み込める」

「BOM有りとBOMなしを識別する」

というサンプルに若干たどり着きにくい。

 

また、外部クラスライブラリを使用する方法も多い。

それが悪いとは思わないが、標準ライブラリを使用する方法を見せたかった。

 

そのため、自分のブログに掲載することにした。

文字コード混在問題の記事からこちらに来る人向けだ。

また、C# で同様のサンプルコードを載せたので、次いでにまったく同じロジックで、Java のサンプルコードを書いた為でもある。

本当なら綺麗にクラス化すべきだろうが、サンプルコードなのでわかりやすさが優先だ。

 

<2020年10月29日追記>ゼロサイズテキストファイルでも正常に動作する事を確認しました。

 

以下が全てのサンプルコードである。

 

コマンドプロンプトから使用し、実行時には、第一引数に「ファイル名」を使用する。

(Java 1.8)

[ReadBOM.java]ファイル

package ReadBOM;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;

public class ReadBOM {

private static final String PATH = "C:\Users\username\eclipse-workspace\ReadBOM\run";

private static final String UTF8 = "UTF-8";
private static final String UTF16LE = "UTF-16LE";
private static final String UTF16BE = "UTF-16BE";
private static final String UTF32LE = "UTF-32LE";
private static final String UTF32BE = "UTF-32BE";
private static final String SHIFTJIS = "MS932";

public static void main(String[] args) throws FileNotFoundException, IOException {
//
String filename = PATH + "\" + args[0];

System.out.println(filename);
File file = new File(filename);
try(FileInputStream fis = new FileInputStream(file))
{
//Check BOM
byte[] bt = {(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF};
int cnt = fis.read(bt, 0, 4);

StringRef charsetName = new StringRef();
if(IsBOM(bt,charsetName))
{
if(charsetName.value == UTF8)
{
fis.getChannel().position(3);
}
else if(charsetName.value == UTF16LE || charsetName.value == UTF16BE)
{
fis.getChannel().position(2);
}
else if(charsetName.value == UTF32LE || charsetName.value == UTF32BE)
{
fis.getChannel().position(4);
}
else
{
//impossible
fis.getChannel().position(4);
}
}
else
{
//SHIFTJIS
fis.getChannel().position(0);
}

System.out.println(charsetName.value);

//Read Text File
try(BufferedReader br = new BufferedReader(new InputStreamReader(fis, Charset.forName(charsetName.value))))
{
String text;
while((text = br.readLine()) != null)
{
System.out.println(text);
}
}
}

}

static boolean IsBOM(byte[] bomByte, StringRef charsetName)
{
boolean result;
byte[] bomUTF8 = { (byte) 0xEF, (byte) 0xBB, (byte) 0xBF };
byte[] bomUTF16Little = { (byte) 0xFF, (byte) 0xFE };
byte[] bomUTF16Big = { (byte) 0xFE, (byte) 0xFF };
byte[] bomUTF32Little = { (byte) 0xFF, (byte) 0xFE, (byte) 0x00, (byte) 0x00 };
byte[] bomUTF32Big = { (byte) 0x00, (byte) 0x00, (byte) 0xFE, (byte) 0xFF };

if (IsMatched(bomByte, bomUTF8))
{
result = true;
charsetName.value = UTF8; //utf-8,Unicode (UTF-8)
}

else if (IsMatched(bomByte, bomUTF32Little))
{
result = true;
charsetName.value = UTF32LE; //utf-32,Unicode (UTF-32)
}

else if (IsMatched(bomByte, bomUTF32Big))
{
result = true;
charsetName.value = UTF32BE; //utf-32BE,Unicode (UTF-32 Big-Endian)
}

else if (IsMatched(bomByte, bomUTF16Little))
{
result = true;
charsetName.value = UTF16LE; //utf-16,Unicode
}

else if (IsMatched(bomByte, bomUTF16Big))
{
result = true;
charsetName.value = UTF16BE; //utf-16BE,Unicode (Big-Endian)
}

else
{
result = false;
//charsetName.value = 0; //non BOM !
charsetName.value = SHIFTJIS; //shift_jis,Japanese (Shift-JIS)
}

return result;
}

static boolean IsMatched(byte[] data, byte[] bom)
{
boolean result = true;

for (int i = 0; i < bom.length; i++)
{
if (bom[i] != data[i])
result = false;
}

return result;
}

}

[StringRef.java]ファイル

package ReadBOM;

public class StringRef {
String value;
}

 

PATH の値には好きなフォルダパス名を入れる。

 

基本的なロジックは以前書いた C# の sample03 と同じロジックである。

単純に、C# から Java8 に書き換えただけだ。

IsBOM, IsMatched の処理も全く同じ。

コードページがなくなりキャラコード名になっただけ。

File file = new File(filename);
try(FileInputStream fis = new FileInputStream(file))
{

先に、FileInputStream で対象ファイルを開き、fis.read(bt, 0, 4); でBOMを読み出してから、BOMが無ければ fis.getChannel().position(0); でファイルポインタを先頭に戻す。

BOMがあれば、BOMのサイズの直後にファイルポインタを移動する。

この処理がそれだ。

//Check BOM
byte[] bt = {(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF};
int cnt = fis.read(bt, 0, 4);

StringRef charsetName = new StringRef();
if(IsBOM(bt,charsetName))
{
if(charsetName.value == UTF8)
{
fis.getChannel().position(3);
}
else if(charsetName.value == UTF16LE || charsetName.value == UTF16BE)
{
fis.getChannel().position(2);
}
else if(charsetName.value == UTF32LE || charsetName.value == UTF32BE)
{
fis.getChannel().position(4);
}
else
{
//impossible
fis.getChannel().position(4);
}
}
else
{
//SHIFTJIS
fis.getChannel().position(0);
}

その後、FileInputStream のインスタンスを InputStreamReader に渡してインスタンス化する。

この時、BOMで確認したエンコーディング名を指定する。

BufferedReader のインスタンスもこのとき作る。

try(BufferedReader br = new BufferedReader(new InputStreamReader(fis, Charset.forName(charsetName.value))))
{
String text;
while((text = br.readLine()) != null)
{
System.out.println(text);
}
}

charsetName.value は、IsBOM から判別したエンコーディング名を引数で返す為のクラスだ。

参照渡しで引数に渡し、値を返して貰う。

業務システムの世界ではまだ、Java と C# が多いと思うので、文字コード混在環境では、こんなサンプルもあると便利だと思う。

 

お役に立てば幸いだ。

shift-jis と utf-8 の混在問題に関する記事(リンクリスト)に戻る

タイトルとURLをコピーしました