1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2019-03-30

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

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

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 ;

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

    /**
     * 取込ファイル2
     */
    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    取込ファイル1
     * @param inputFile2    取込ファイル2
     * @param outputDir     差分ファイル出力フォルダ
     */
    public CSVDiff(String delimiterType, String args, String inputDir, String inputFile1, String inputFile2,String outputDir) {

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

        //================================================================================
        // 2.レコード比較時のキー項目インデックスを配列に変換する
        //================================================================================
        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);
            }
        }

        //================================================================================
        // 3.各種フォルダ名、ファイル名を設定
        //================================================================================
        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) {
            //================================================================================
            // レコード1=レコード2
            // → レコード1、レコード2を出力
            //================================================================================

            //--------------------------------------------------------------------------------
            // レコード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);

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

        } else if (outputType == 1) {
            //================================================================================
            // レコード1<レコード2
            // → レコード1のみ出力
            //================================================================================

            //--------------------------------------------------------------------------------
            // レコード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);

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

        }  else if (outputType == 2) {
            //================================================================================
            // レコード1>レコード2
            // → レコード2のみ出力
            //================================================================================

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

            //--------------------------------------------------------------------------------
            // レコード2出力
            //--------------------------------------------------------------------------------
            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 ;

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

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

        // --------------------------------------------------------------------------------
        // (2)出力用リスト生成
        // --------------------------------------------------------------------------------
        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("ファイル1とファイル2でレコード数に相違があります");
                isError = true ;
                break ;
            }

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

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

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

            } else if (diffType == 1) {
                // ファイル1レコード<ファイル2レコード
                outputRecord = this.makeRecord(diffType, record1, null);
                System.out.println(outputRecord);
                this.outputList.add(outputRecord);
                i_file1 ++;
            } else {
                // ファイル1レコード>ファイル2レコード
                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 ++;
        }

        // --------------------------------------------------------------------------------
        // (3)ファイル出力
        // --------------------------------------------------------------------------------
        this.writeFile();


         return 0 ;
    }

    private void writeFile () {
        FileOutputStream fos  = null;
        OutputStreamWriter osw = null;
        // --------------------------------------------------------------------------------
        // (1)出力ファイルタイムスタンプ生成
        // --------------------------------------------------------------------------------
        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 () {

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

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

        // --------------------------------------------------------------------------------
        // (3)出力ファイル配置フォルダチェック
        // --------------------------------------------------------------------------------
        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 ;

    }

以下は実行クラス

CSVDiffExecute.java

/**
 * CSV差分出力実行クラス
 *
 */
public class CSVDiffExecute {
    /**
     * メインメソッド
     * @param args [0] 区切り文字種別        (1:カンマ 1以外:タブ)
     *              [1] キー項目のインデックス(半角カンマ区切りで複数指定可能)
     *              [2] 取込ファイル配置フォルダ
     *              [3] 取込ファイル1
     *              [4] 取込ファイル2
     *              [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();
    }
}


1
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?