膨張空間

開発日記を中心に、時々雑記を投稿します。よろしく

StreamReaderで指定したレコードに自動分割する

目的

手動でファイルを分割して名前を指定して保存するのが面倒なので、ファイルを自動で分割して保存するようにした。

環境

詳細

実際のコードはここを参考にしてほしい。実際に使用しているので最低限のチェック処理が書いてある。

ポイント1:必要ファイル数を計算する

読み込むファイルの行数が不明なので、ファイルをいくつ分けるか判断する必要がある。 「ReadAllLines」メソッドを使って読み込み元のファイルを読み込んでその件数を取得している。件数をdoubleで宣言しているのがキモ。次のファイル数を計算する箇所で明示的にdoubleで宣言していない場合、暗黙的にintで宣言される。そのため、小数点部は必ず0になり切り上げしたくても狙った数にはならない。

//読み込み元のファイルレコード数
var RecordCount = (double)File.ReadAllLines(FilePath).Count();

//作成するファイル数を取得する
var LoopCount = Math.Ceiling(RecordCount /  (double)MaxFileRecord);

ポイント2:途中ファイルの読み込みは指定行数まで

よくあるサンプルでは「while (sr.Peek() > -1)」で最終レコードまで読み込んで、1行ずつテキストに出力しているものが多い。そのままでは指定した行数毎にファイルを分割できないので、事前に計算していした1ファイルの最大行数(MaxFileRecord)までレコードを読み込んでからファイルに出力してループを抜けるようにしている

//読み込んだ回数が指定したレコード数の時は
if (ReadCount == MaxFileRecord)
{
    //新しいファイルに書き込んで
    sw.WriteLine(NewText);

    //while (sr.Peek() > -1)を抜ける
    break;
}

ポイント3:最後はReadToEndで一気に読み込む

ReadToEndを使うことでファイルの終端まで読み込むことができる。StreamReaderを使用したファイル読み込みのサンプルでよく見る。便利なので通常の読み込みであればこれで十分だ。ReadToEndの読み込み開始位置はPositionから読み込む。Positionは読み込みを行うことで1文字ずつ移動していく。今回はStreamReader.WriteLineを使用して行単位で読み込んでいるので1行づつ移動していくことになる。 そのため最後のファイル以外を読み込んだ後にReadToEndを使用すると残りの行数が一気に取れる。

    //最後のファイルの時は
    if (i == LoopCount)
    {
        //StreamReader.ReadToEndで最後の行まで読み込む
        sw.WriteLine(sr.ReadToEnd());
    }

ファイル作成の主要部分を抜粋

//読み込み元のファイルレコード数
var RecordCount = (double)File.ReadAllLines(FilePath).Count();

//作成するファイル数を取得する
var LoopCount = Math.Ceiling(RecordCount /  (double)MaxFileRecord);

//引数のファイルパスからフォルダパスを取得する
var DirPath = Path.GetDirectoryName(FilePath);

//引数のファイルパスからファイル名を取得する
var BaseFileName = Path.GetFileNameWithoutExtension(FilePath);

//読み込み元のファイルをStreamReaderで開く
using (var fs = new FileStream(FilePaFileMode.Open, FileAccess.Read, FileShare.R)
using (var sr = new StreamReader(Encoding.UTF8))
{
    //作成するファイル数分繰り返す
    for (int i = 1; i <= LoopCount; i++)
    {
        //1ファイルに書き込むテキストを保持する変数を宣言
        var NewText = string.Empty;

        //新しいファイルのパスを生成する
        var NewFilePath = Path.Combine(DirP string.Format("{0}_{1}.csBaseFileName, i));

        //処理した行数をカウントする変数を宣言する
        var ReadCount = 0;

        //新しいファイルをStreamWriterで開く
        using (var newfs = new FileSt(NewFilePath, FileMode.OpenOrCreaFileAccess.ReadWriFileShare.ReadWrite))
        using (var sw = new StreamWriter(ne Encoding.UTF8))
        {
            //最後のファイルの時は
            if (i == LoopCount)
            {
                //StreamReader.ReadToEndで最後の行まで読み込む
                sw.WriteLine(sr.ReadToEnd());
            }
            else
            {
                //レコードが読み込めなくなるまで繰り返す
                while (sr.Peek() > -1)
                {
                    //読み込んだレコードを保存用の変数に追加する
                    NewText = NewText + sr.ReadLine() + Environment.NewLine;

                    //読み込んだ回数をカウントする
                    ReadCount++;

                    //読み込んだ回数が指定したレコード数の時は
                    if (ReadCount == MaxFileRecord)
                    {
                        //新しいファイルに書き込んで
                        sw.WriteLine(NewText);

                        //while (sr.Peek() > -1)を抜ける
                        break;
                    }
                }
            }
        }
    }
}

余談

ここは余談なので興味がない人は読み飛ばしてもらってかまわない。なぜこの機能が必要になったのかというと、業務で数万行のCSVファイルを取り込む処理を定期的に実行していた。取込に問題はなかったのだが取込後のチェックSQLタイムアウトが頻発していた。原因は特定していたが業務にクリティカルな修正になるので修正できなかった。そのため、一時的な逃げとして数万行のファイルを1万行に分割して取り込んでいた。当初は別の人間が対応していたのだが、なんやかんやで自分にお鉢が回ってきた。素直に手作業でやるのが面倒だったのでドラッグ&ドロップでファイルが分割できる機能を開発した。調べて見ると開始からN行までとか。N行目から最後までとかは割と出てきた。しかし、指定した行で自動分割する記事は見つからなかった。なので、もし同じ処理を行いたい人が出てきた時の参考になればいいなと思い記事にした。正直、コードのプラッシュアップはしていないので冗長な書き方になっているかもしれないがご容赦願いたい。

まとめ

  • 必要ファイル数を計算する
  • 途中ファイルの読み込みは指定行数まで
  • 最後はReadToEndで一気に読み込む

参考サイト