編集:以下の私のソリューションを参照してください...
次の問題を解決する必要がありました: ファイル (主にアドレス情報) をさまざまなソースから受け取ります。これらは Windows 標準で CR/LF ('\r''\n') を改行として使用するか、UNIX で LF (' \n')。
StreamReader.ReadLine() メソッドを使用してテキストを読み取る場合、どちらの場合も同等に処理されるため、これは問題ではありません。
この問題は、CR または LF がファイル内のどこかにあるはずのない場所にある場合に発生します。これは、たとえば、セル内に改行を含むセルを含む EXCEL ファイルを .CSV または他のフラット ファイルにエクスポートする場合に発生します。
これで、たとえば次の構造を持つファイルができました。
FirstName;LastName;Street;HouseNumber;PostalCode;City;Country'\r''\n'
Jane;Doe;co James Doe'\n'TestStreet;5;TestCity;TestCountry'\r''\n'
John;Hancock;Teststreet;1;4586;TestCity;TestCounty'\r''\n'
StreamReader.ReadLine() メソッドは最初の行を次のように読み取ります。
FirstName;LastName;Street;HouseNumber;PostalCode;City;Country
これは問題ありませんが、2 行目は次のようになります。
Jane;Doe;co James Doe
これにより、コードが破損するか、次の行のように誤った結果が得られます。
TestStreet;5;TestCity;TestCountry
そのため、通常はツールを使用してファイルを実行し、周りにゆるい '\n' または '\r' があるかどうかを確認して削除します。
しかし、この手順は忘れがちなので、独自の ReadLine() メソッドを実装しようとしました。要件は、1 つまたは 2 つの LineBreak 文字を使用でき、それらの文字は消費ロジックによって自由に定義できることでした。
これは私が思いついたクラスです:
public class ReadFile
{
private FileStream file;
private StreamReader reader;
private string fileLocation;
private Encoding fileEncoding;
private char lineBreak1;
private char lineBreak2;
private bool useSeccondLineBreak;
private bool streamCreated = false;
private bool endOfStream;
public bool EndOfStream
{
get { return endOfStream; }
set { endOfStream = value; }
}
public ReadFile(string FileLocation, Encoding FileEncoding, char LineBreak1, char LineBreak2, bool UseSeccondLineBreak)
{
fileLocation = FileLocation;
fileEncoding = FileEncoding;
lineBreak1 = LineBreak1;
lineBreak2 = LineBreak2;
useSeccondLineBreak = UseSeccondLineBreak;
}
public string ReadLine()
{
if (streamCreated == false)
{
file = new FileStream(fileLocation, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
reader = new StreamReader(file, fileEncoding);
streamCreated = true;
}
StringBuilder builder = new StringBuilder();
char[] buffer = new char[1];
char lastChar = new char();
char currentChar = new char();
bool first = true;
while (reader.EndOfStream != true)
{
if (useSeccondLineBreak == true)
{
reader.Read(buffer, 0, 1);
lastChar = currentChar;
if (currentChar == lineBreak1 && buffer[0] == lineBreak2)
{
break;
}
else
{
currentChar = buffer[0];
}
if (first == false)
{
builder.Append(lastChar);
}
else
{
first = false;
}
}
else
{
reader.Read(buffer, 0, 1);
if (buffer[0] == lineBreak1)
{
break;
}
else
{
currentChar = buffer[0];
}
builder.Append(currentChar);
}
}
if (reader.EndOfStream == true)
{
EndOfStream = true;
}
return builder.ToString();
}
public void Close()
{
if (streamCreated == true)
{
reader.Close();
file.Close();
}
}
}
このコードは正常に動作し、本来の機能を果たしますが、元の StreamReader.ReadLine() メソッドと比較すると、約 3 倍遅くなります。大きな行数を扱うと、違いが混乱するだけでなく、実際のパフォーマンスにも反映されます。(700'000行の場合、すべての行を読み取り、チャンクを抽出して新しいファイルに書き込むのに約5秒かかります。私の方法では、システムで約15秒かかります)
バッファを大きくしてさまざまなアプローチを試みましたが、これまでのところパフォーマンスを向上させることはできませんでした。
私が興味を持っていること: StreamReader.ReadLine() の元のパフォーマンスに近づけるために、このコードのパフォーマンスを改善する方法について何か提案はありますか?
解決:
上記のコードと同じことを行うのに、700'000 行で最大 6 秒かかります (既定の 'StreamReader.ReadLine()' を使用した場合は最大 5 秒)。
正しい方向に向けてくれた Jim Mischel に感謝します。
public class ReadFile
{
private FileStream file;
private StreamReader reader;
private string fileLocation;
private Encoding fileEncoding;
private char lineBreak1;
private char lineBreak2;
private bool useSeccondLineBreak;
const int BufferSize = 8192;
int bufferedCount;
char[] rest = new char[BufferSize];
int position = 0;
char lastChar;
bool useLastChar;
private bool streamCreated = false;
private bool endOfStream;
public bool EndOfStream
{
get { return endOfStream; }
set { endOfStream = value; }
}
public ReadFile(string FileLocation, Encoding FileEncoding, char LineBreak1, char LineBreak2, bool UseSeccondLineBreak)
{
fileLocation = FileLocation;
fileEncoding = FileEncoding;
lineBreak1 = LineBreak1;
lineBreak2 = LineBreak2;
useSeccondLineBreak = UseSeccondLineBreak;
}
private int readInBuffer()
{
return reader.Read(rest, 0, BufferSize);
}
public string ReadLine()
{
StringBuilder builder = new StringBuilder();
bool lineFound = false;
if (streamCreated == false)
{
file = new FileStream(fileLocation, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 8192);
reader = new StreamReader(file, fileEncoding);
streamCreated = true;
bufferedCount = readInBuffer();
}
while (lineFound == false && EndOfStream != true)
{
if (position < bufferedCount)
{
for (int i = position; i < BufferSize; i++)
{
if (useLastChar == true)
{
useLastChar = false;
if (rest[i] == lineBreak2)
{
count++;
position = i + 1;
lineFound = true;
break;
}
else
{
builder.Append(lastChar);
}
}
if (rest[i] == lineBreak1)
{
if (useSeccondLineBreak == true)
{
if (i + 1 <= BufferSize - 1)
{
if (rest[i + 1] == lineBreak2)
{
position = i + 2;
lineFound = true;
break;
}
else
{
builder.Append(rest[i]);
}
}
else
{
useLastChar = true;
lastChar = rest[i];
}
}
else
{
position = i + 1;
lineFound = true;
break;
}
}
else
{
builder.Append(rest[i]);
}
position = i + 1;
}
}
else
{
bufferedCount = readInBuffer();
position = 0;
}
}
if (reader.EndOfStream == true && position == bufferedCount)
{
EndOfStream = true;
}
return builder.ToString();
}
public void Close()
{
if (streamCreated == true)
{
reader.Close();
file.Close();
}
}
}