CSVファイルの差分を出力するツールをつくってみた


ファイルの差分を比較するツールは沢山出回っているものの
自分が欲しいと思えるものがなかったので
2つのCSVファイルの差分を出力するツールをつくってみた。

引数でキーとなる行番号を指定して、
キーの値が異なれば別行に出力するシンプルなツールです。

```Java:CSVDiff.java

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.nio.charset.Charset;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * CSV差分出力
 */
public class CSVDiff {

    private String split = null;

    /**
     * 区切り文字
     */
    private String delimiter = null ;

    /**
     * キー項目のインデックス
     * ※0始まり
     */
    private List<Integer> keyIndex = null;

    /**
     * 取込ファイル配置フォルダ
     */
    private String inputDir = null ;

    /**
     * 取込ファイル１
     */
    private String inputFile1 = null ;

    /**
     * 取込ファイル２
     */
    private String inputFile2 = null ;

    /**
     * 差分ファイル出力フォルダ
     */
    private String outputDir = null ;

    /**
     * */
    private List<String[]> recordList1 = null;

    /**
     * */
    private List<String[]> recordList2 = null;

    private List <String>  outputList  = null ;

    /**
     * コンストラクタ
     * @param delimiterType 区切り文字種別（1：カンマ 1以外：タブ）
     * @param args          キー項目のインデックス
     * @param inputDir      取込ファイル配置フォルダ
     * @param inputFile1    取込ファイル１
     * @param inputFile2    取込ファイル２
     * @param outputDir     差分ファイル出力フォルダ
     */
    public CSVDiff(String delimiterType, String args, String inputDir, String inputFile1, String inputFile2,String outputDir) {

        //================================================================================
        // １．区切り文字種別判定
        //================================================================================
        if ("1".equals(delimiterType)) {
            this.delimiter = ",";
        } else {
            this.delimiter = "\t";
        }
        this.split = "*" + this.delimiter + "*" + this.delimiter + "*";

        //================================================================================
        // ２．レコード比較時のキー項目インデックスを配列に変換する
        //================================================================================
        this.keyIndex = new ArrayList<Integer> ();
        String arr[] = args.split(",");
        for (String item : arr) {
            int index = Integer.parseInt(item);
            if (! this.keyIndex.contains(index) ) {
                this.keyIndex.add(index);
            }
        }

        //================================================================================
        // ３．各種フォルダ名、ファイル名を設定
        //================================================================================
        this.inputDir   = inputDir;
        this.outputDir  = outputDir;
        this.inputFile1 = inputFile1;
        this.inputFile2 = inputFile2;
    }

    /**
     *
     * @param outputType
     * @param array1
     * @param array2
     * @return
     */
    private String makeRecord (int outputType,String[] array1 ,String[] array2) {
        StringBuilder outputRecord = new StringBuilder() ;

        if (outputType == 0) {
            //================================================================================
            // レコード１＝レコード２
            // →　レコード１、レコード２を出力
            //================================================================================

            //--------------------------------------------------------------------------------
            // レコード１出力
            //--------------------------------------------------------------------------------
            for ( int i=0;i<array1.length;i++) {
                if (i == 0) {
                    outputRecord.append(array1[i]);
                } else {
                    outputRecord.append(this.delimiter + array1[i]);
                }
            }
            //--------------------------------------------------------------------------------
            // 区切り文字出力
            //--------------------------------------------------------------------------------
            outputRecord.append(this.delimiter);
            outputRecord.append(split);

            //--------------------------------------------------------------------------------
            // レコード２出力
            //--------------------------------------------------------------------------------
            for ( int i=0;i<array2.length;i++) {
                outputRecord.append(this.delimiter + array2[i]);
            }

        } else if (outputType == 1) {
            //================================================================================
            // レコード１＜レコード２
            // →　レコード１のみ出力
            //================================================================================

            //--------------------------------------------------------------------------------
            // レコード１出力
            //--------------------------------------------------------------------------------
            for ( int i=0;i<array1.length;i++) {
                if (i == 0) {
                    outputRecord.append(array1[i]);
                } else {
                    outputRecord.append(this.delimiter + array1[i]);
                }
            }
            //--------------------------------------------------------------------------------
            // 区切り文字出力
            //--------------------------------------------------------------------------------
            outputRecord.append(this.delimiter);
            outputRecord.append(split);

            //--------------------------------------------------------------------------------
            // レコード２出力（カンマorタブのみ）
            //--------------------------------------------------------------------------------
            for ( int i=0;i<array1.length;i++) {
                outputRecord.append(this.delimiter);
            }

        }  else if (outputType == 2) {
            //================================================================================
            // レコード１＞レコード２
            // →　レコード２のみ出力
            //================================================================================

            //--------------------------------------------------------------------------------
            // レコード１出力（カンマorタブのみ）
            //--------------------------------------------------------------------------------
            for ( int i=0;i<array2.length;i++) {
                outputRecord.append(this.delimiter);
            }
            //--------------------------------------------------------------------------------
            // 区切り文字出力
            //--------------------------------------------------------------------------------
//            outputRecord.append(this.delimiter);
            outputRecord.append(split);

            //--------------------------------------------------------------------------------
            // レコード２出力
            //--------------------------------------------------------------------------------
            for ( int i=0;i<array2.length;i++) {
                outputRecord.append(this.delimiter + array2[i]);
            }
       }

        return outputRecord.toString();

    }


    /**
     * CSV差分リスト出力
     * @return 0：正常終了 1：異常終了
     */
    protected int outputCSVDiff ()  {
        boolean isError = false ;

        // --------------------------------------------------------------------------------
        // （１）取込・出力ファイル、フォルダ存在チェック
        // --------------------------------------------------------------------------------
        if (this.checkFileExists() != 0) {
            System.err.println("取込・出力用のファイルまたはフォルダが存在しません");
            return 1;
        }

        // --------------------------------------------------------------------------------
        // （１）ファイル読込
        // --------------------------------------------------------------------------------
        if (this.readFile() != 0) {
            System.err.println("ファイル読込エラー");
            return 1;
        }

        // --------------------------------------------------------------------------------
        // （２）出力用リスト生成
        // --------------------------------------------------------------------------------
        this.outputList = new ArrayList<String> () ;
        int i_file1 = 0 ;
        int i_file2 = 0 ;

        while (i_file1 < this.recordList1.size() && i_file2 < this.recordList2.size()) {
            String[] record1 = this.recordList1.get(i_file1);
            String[] record2 = this.recordList2.get(i_file2);
            String   outputRecord = null ;


            if (record1.length != record2.length) {
                System.err.println("ファイル１とファイル２でレコード数に相違があります");
                isError = true ;
                break ;
            }

            //========================================================================
            // キー項目一致判定
            //========================================================================
            int diffType = 0 ;
            for (int keyIndex :this.keyIndex) {

                if (record1[keyIndex].compareTo(record2[keyIndex]) < 0 ) {
                    // ファイル１レコード ＜ ファイル２レコード
                    diffType = 1;
                    break;
                } else if (record1[keyIndex].compareTo(record2[keyIndex]) > 0 ) {
                    // ファイル１レコード ＞ ファイル２レコード
                    diffType = 2;
                    break;
                }
            }

            if (diffType == 0) {
                // ファイル１レコード＝ファイル２レコード
                outputRecord = this.makeRecord(diffType, record1, record2) ;
                System.out.println(outputRecord);
                this.outputList.add(outputRecord);
                i_file1 ++;
                i_file2 ++;

            } else if (diffType == 1) {
                // ファイル１レコード＜ファイル２レコード
                outputRecord = this.makeRecord(diffType, record1, null);
                System.out.println(outputRecord);
                this.outputList.add(outputRecord);
                i_file1 ++;
            } else {
                // ファイル１レコード＞ファイル２レコード
                outputRecord = this.makeRecord(diffType, null, record2);
                System.out.println(outputRecord);
                this.outputList.add(outputRecord);
                i_file2 ++;
            }
        } // end-of-while

        if (isError) { return 1; }

        while (i_file1 < this.recordList1.size() ) {
            String[] record1 = this.recordList1.get(i_file1);
            String outputRecord = this.makeRecord(1, record1, null);
            System.out.println(outputRecord);
            this.outputList.add(outputRecord);
            i_file1 ++;
        }

        while (i_file2 < this.recordList2.size()) {
            String[] record2 = this.recordList2.get(i_file2);
            String outputRecord = this.makeRecord(2, null, record2) ;
            System.out.println(outputRecord);
            this.outputList.add(outputRecord);
            i_file2 ++;
        }

        // --------------------------------------------------------------------------------
        // （３）ファイル出力
        // --------------------------------------------------------------------------------
        this.writeFile();


         return 0 ;
    }

    private void writeFile () {
        FileOutputStream fos  = null;
        OutputStreamWriter osw = null;
        // --------------------------------------------------------------------------------
        // （１）出力ファイルタイムスタンプ生成
        // --------------------------------------------------------------------------------
        Timestamp timestamp    = new Timestamp(System.currentTimeMillis());
        SimpleDateFormat sdf   = new SimpleDateFormat("yyyyMMddHHmmss");
        String fileName = "csvdiff_"+sdf.format(timestamp)+".txt" ;

        try {
            fos = new FileOutputStream(this.outputDir+"/"+fileName);
            osw = new OutputStreamWriter(fos,"Shift_JIS");

            for (String record : this.outputList) {
                osw.write(record + "\r\n") ;
            }
            osw.close();
            fos.close();
        } catch (IOException ex) {
            ex.printStackTrace();
        } finally {
            if (osw != null ) {
                try {
                    osw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fos != null ) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }


        }


    }

    /**
     * 取込・出力ファイル、フォルダの存在チェック
     * @return 0： 取込・出力ファイル、フォルダあり 1： 取込・出力ファイル、フォルダのいずれか存在しない
     */
    private int checkFileExists () {

        // --------------------------------------------------------------------------------
        // （１）取込ファイル１存在チェック
        // --------------------------------------------------------------------------------
        File file1 = new File(this.inputDir+"/"+this.inputFile1) ;
        if (!file1.exists()) {
            System.err.println("エラー -> 取込ファイル１『"+this.inputDir+"/"+this.inputFile1+"』が存在しません！！");
            return 1;
        }

        // --------------------------------------------------------------------------------
        // （２）取込ファイル２存在チェック
        // --------------------------------------------------------------------------------
        File file2 = new File(this.inputDir+"/"+this.inputFile2) ;
        if (!file2.exists()) {
            System.err.println("エラー -> 取込ファイル２『"+this.inputDir+"/"+this.inputFile2+"』が存在しません！！");
            return 1;
        }

        // --------------------------------------------------------------------------------
        // （３）出力ファイル配置フォルダチェック
        // --------------------------------------------------------------------------------
        File outputDir = new File(this.outputDir) ;
        if (!outputDir.exists()) {
            System.err.println("エラー -> 出力フォルダ『"+this.outputDir+"』が存在しません！！");
            return 1;
        }
        return 0 ;
    }


    /**
     * ファイルを読み込んで配列リストに設定する
     * @return 0：読込OK 1：読込NG
     */
    private int readFile() {
        int retVal = 0 ;
        File file1 = new File(this.inputDir+"/"+this.inputFile1) ;
        File file2 = new File(this.inputDir+"/"+this.inputFile2) ;

        try {
            this.recordList1 = this.makeRecordList(file1);
            this.recordList2 = this.makeRecordList(file2);
        } catch (Exception e) {
            e.printStackTrace();
            retVal = 1;
        }
        return retVal;
    }

    /**
     * CSVファイル内容を配列リストに詰め替える
     * @param file
     * @return
     * @throws Exception
     */
    private List<String[]> makeRecordList (File file) throws Exception {
        boolean isError = false;
        List<String[]> recordList  = new ArrayList<String[]>() ;

        FileInputStream   fis  = null;
        InputStreamReader isr  = null ;
        BufferedReader    br   = null ;

        try {
            fis   = new FileInputStream(file);
            isr   = new InputStreamReader(fis, Charset.forName("MS932"));
            br    = new BufferedReader(isr);

            int i = 1 ;
            String record = null ;
            while((record = br.readLine()) != null) {
                String[] recordArray = record.split(this.delimiter);

                System.out.println("["+i+"]行目："+Arrays.toString(recordArray));
                recordList.add(recordArray);
//                System.out.println("["+i_file1+"]行目："+record);
                i ++ ;
            }

        } catch(FileNotFoundException e ) {
            e.printStackTrace();
            isError = true;
        } catch(IOException e ) {
            e.printStackTrace();
            isError = true;
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                    isError = true;
                }
            }
            if (isr != null) {
                try {
                    isr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                    isError = true;
                }
            }
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                    isError = true;
                }
            }
        }
        if (isError) { throw new Exception(); }

        return recordList ;

    }
```
以下は実行クラス

```Java:CSVDiffExecute.java

/**
 * CSV差分出力実行クラス
 *
 */
public class CSVDiffExecute {
    /**
     * メインメソッド
     * @param args [0] 区切り文字種別        （1：カンマ 1以外：タブ）
     *              [1] キー項目のインデックス（半角カンマ区切りで複数指定可能）
     *              [2] 取込ファイル配置フォルダ
     *              [3] 取込ファイル１
     *              [4] 取込ファイル２
     *              [5] 差分ファイル出力フォルダ
     */
    public static void main(String[] args) {
        //================================================================================
        // 引数チェック
        //================================================================================
        if (args.length != 6) {
            System.err.println("エラー->引数の数が不足しています。");
            System.exit(1);
        }
        //================================================================================
        // CSV差分出力実行
        //================================================================================
        CSVDiff csvDiff = new CSVDiff(args[0], args[1], args[2], args[3], args[4], args[5]);
        csvDiff.outputCSVDiff();
    }
}


```