BOMの有無を識別し、UTFを識別して Java の Files で読み書きするサンプルコード

2020年10月3日土曜日

技術的備忘録 汎用ソースコード&ツール

t f B! P L


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


BOMの有無を識別し、UTFを識別してFilesで読み書きするサンプルコード

 

以前、Java の InputStreamReader と OutputStreamWriter によるBOMの読み書きサンプルを書いたが、

今回は Files によるサンプルコードを掲載する。

UTF以外に shift-jis も読み書きできる。

ただし、BOM無しUTFには対応していない。

以前と同様に、「BOM有りはUTF」「BOMなしは shift-jis」という前提でコードを作成している。

 

<2020年10月29日追記>ゼロサイズテキストファイルに対応しました。

 

まずサンプルコードの全てを載せる。

 

[ReadWrite.java]ファイル

package FilesBOM;

import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

public class ReadWrite {

	private static final String PATH = "C:\\Users\\username\\OneDrive\\eclipse-workspace\\FilesBOM\\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";

	private static final byte[] bomUTF8 = { (byte) 0xEF, (byte) 0xBB, (byte) 0xBF };
	private static final byte[] bomUTF16Little = { (byte) 0xFF, (byte) 0xFE };
	private static final byte[] bomUTF16Big = { (byte) 0xFE, (byte) 0xFF };
	private static final byte[] bomUTF32Little = { (byte) 0xFF, (byte) 0xFE, (byte) 0x00, (byte) 0x00 };
	private static final byte[] bomUTF32Big = { (byte) 0x00, (byte) 0x00, (byte) 0xFE, (byte) 0xFF };
	
	public static void main(String[] args) {
		// TODO 自動生成されたメソッド・スタブ
		if(args.length < 2) {
			System.out.println("Enter the input file name and output file name.");
			return;
		}
		
		String inputFilename = PATH + "\\" + args[0];
		String outputFilename = PATH + "\\" + args[1];
		
		try
		{
			// read sample.
			Path file = Paths.get(inputFilename);
			byte[] textbyte = Files.readAllBytes(file);
			
			StringRef charsetName = new StringRef();
			
			// check BOM.
			int BOMLen = 0;
			byte[] bomUTF = null;

			if(IsBOM(textbyte,charsetName)) {
				System.out.println(charsetName.value.toString());
				
				if(charsetName.value.equals(UTF8)) {
					BOMLen = 3;
					bomUTF = bomUTF8;
				}
				else if(charsetName.value.equals(UTF16LE)) {
					BOMLen = 2;
					bomUTF = bomUTF16Little;
				}
				else if(charsetName.value.equals(UTF16BE)) {
					BOMLen = 2;
					bomUTF = bomUTF16Big;
				}
				else if(charsetName.value.equals(UTF32LE)) {
					BOMLen = 4;
					bomUTF = bomUTF32Little;
				}
				else if(charsetName.value.equals(UTF32BE)) {
					BOMLen = 4;
					bomUTF = bomUTF32Big;
				}
				else {
					BOMLen = 0;
					bomUTF = null;
				}
			}
			
			// edit text.
			byte[] textbyteExcludingBOM = new byte[textbyte.length - BOMLen]; 
			
			System.arraycopy(textbyte, BOMLen, 
					textbyteExcludingBOM, 0, 
					textbyte.length - BOMLen);
			
			String text = new String(textbyteExcludingBOM, 
					Charset.forName(charsetName.value));
			
			text += "\n:COPY.";
			
			// print text.
			System.out.println(text);
			
			// write sample.
			byte[] wTextbyteExcludingBOM = text.getBytes(charsetName.value);
			byte[] wtextbyte = new byte[wTextbyteExcludingBOM.length + BOMLen];
			System.arraycopy(wTextbyteExcludingBOM, 0, 
					wtextbyte, BOMLen, 
					wTextbyteExcludingBOM.length);
			
			if(bomUTF != null) {
				// copy BOM.
				System.arraycopy(bomUTF,0,
						wtextbyte,0,
						bomUTF.length);
			}

			Path wfile = Paths.get(outputFilename);
			Files.deleteIfExists(wfile);
			Files.write(wfile, wtextbyte, StandardOpenOption.CREATE_NEW);
			
			// end.
		}
		catch(Exception e) {
			System.out.println(e.toString());
		}

	}

    static boolean IsBOM(byte[] bomByte, StringRef charsetName)
    {
        boolean result;

        if (bomByte.length == 0)
        {
            result = false;
            charsetName.value = SHIFTJIS; //shift_jis,Japanese (Shift-JIS)
        }
        
        else if (bomByte.length >= 3 && IsMatched(bomByte, bomUTF8))
        {
            result = true;
            charsetName.value = UTF8; //utf-8,Unicode (UTF-8)
        }

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

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

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

        else if (bomByte.length >= 2 && 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]ファイル

package FilesBOM;

public class StringRef {
	String	value;
}

実行する時はCLIで第一パラメータに入力ファイル名を、第二パラメータに出力ファイル名を入れる。

実行すると入力ファイルを読み込み、テキストの末尾に「:COPY.」を追記して、出力ファイルへ書き込む。

予め入力ファイルは用意しておく。

出力ファイルは新規作成し、既存ならば削除後再作成する。

入力ファイルのエンコーディングと同じ出力ファイルを作成する。

 

コード解説

このサンプルはUTFとshift-jisのテキストファイルが混在した環境で使用することを想定して、作成している。

テキストファイルを読み込んでみるまでエンコーディング名は分からない。

従ってテキストモードで読み込む事ができない。

入力ファイルの読み込みはバイナリモードで読み込む。

// read sample.
Path file = Paths.get(inputFilename);
byte[] textbyte = Files.readAllBytes(file);

Files.readAllBytes は対象ファイルをバイナリで全て読み込み byte型配列で出力する。

 

その後、取得した byte型配列 の先頭を IsBOM でBOMの有無の確認を行う。

IsBOM のロジックは以前のInputStreamReaderのサンプルと同じ。

ここで、後の書き込み処理のために「BOMの長さ」と「BOM値」の配列を設定しておく。

// check BOM.
int BOMLen = 0;
byte[] bomUTF = null;

if(IsBOM(textbyte,charsetName)) {
	System.out.println(charsetName.value.toString());
	
	if(charsetName.value.equals(UTF8)) {
		BOMLen = 3;
		bomUTF = bomUTF8;
	}
	else if(charsetName.value.equals(UTF16LE)) {
		BOMLen = 2;
		bomUTF = bomUTF16Little;
	}
	else if(charsetName.value.equals(UTF16BE)) {
		BOMLen = 2;
		bomUTF = bomUTF16Big;
	}
	else if(charsetName.value.equals(UTF32LE)) {
		BOMLen = 4;
		bomUTF = bomUTF32Little;
	}
	else if(charsetName.value.equals(UTF32BE)) {
		BOMLen = 4;
		bomUTF = bomUTF32Big;
	}
	else {
		BOMLen = 0;
		bomUTF = null;
	}
}

読み込んだbyte型配列を、BOM部分を除いて別のbyte型配列へ arraycopy でコピーする。

読み込んだbyte型配列を String型に変換する。

このとき、エンコーディング名を指定して変換する。

取得した String の末尾に「:COPY.」を追記して、コンソールに表示する。

// edit text.
byte[] textbyteExcludingBOM = new byte[textbyte.length - BOMLen]; 

System.arraycopy(textbyte, BOMLen, 
		textbyteExcludingBOM, 0, 
		textbyte.length - BOMLen);

String text = new String(textbyteExcludingBOM, 
		Charset.forName(charsetName.value));

text += "\n:COPY.";

// print text.
System.out.println(text);

編集した String を byte型配列に変換して、先頭にBOMを書き加える。

そのbyte型配列を Files.write でバイナリモードで出力ファイルへ書き込む。

// write sample.
byte[] wTextbyteExcludingBOM = text.getBytes(charsetName.value);
byte[] wtextbyte = new byte[wTextbyteExcludingBOM.length + BOMLen];
System.arraycopy(wTextbyteExcludingBOM, 0, 
		wtextbyte, BOMLen, 
		wTextbyteExcludingBOM.length);

if(bomUTF != null) {
	// copy BOM.
	System.arraycopy(bomUTF,0,
			wtextbyte,0,
			bomUTF.length);
}

Path wfile = Paths.get(outputFilename);
Files.deleteIfExists(wfile);
Files.write(wfile, wtextbyte, StandardOpenOption.CREATE_NEW);

処理解説は以上。

 

ネット上には Files.readAllLines を用いてBOMに対応するサンプルもあるが、テキストモードで読み込むと複数のエンコーディングに対応する事はできない。

shift-jis と UTF を複数全て扱えるようにするには、バイナリモードで読み書きする必要がある。

 

ソースコードはサンプルコードなので美しくないが、自分で書くときはクラスなどを用いて綺麗に書き直したら良いと思う。

 

このコードがお役に立てば幸いだ。

 


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


このブログを検索

Translate

人気の投稿

自己紹介

自分の写真
オッサンです。実務経験は Windows環境にて C#,VB.NET ,SQL Server T-SQL,Oracle PL/SQL,PostgreSQL,MariaDB。昔はDelphi,C,C++ など。 趣味はUbuntu,PHP,PostgreSQL,MariaDBかな ?基本無料のやつ。

QooQ