C# 具有自定义换行符的Streamreader-性能优化 编辑:请参阅下面的我的解决方案。。。
我有以下问题需要解决: 我们从不同的来源接收文件(主要是地址信息),这些文件可以在Windows标准中使用CR/LF('\r'\n')作为换行符,也可以在UNIX中使用LF('\n') 当使用StreamReader.ReadLine()方法在中读取文本时,这没有问题,因为它会平等地处理这两种情况 当文件中的某个地方有CR或LF而不应该存在时,就会出现问题。 例如,如果将包含单元格内换行符的EXCEL文件导出为.CSV或其他平面文件,则会发生这种情况 现在您有了一个具有以下结构的文件:C# 具有自定义换行符的Streamreader-性能优化 编辑:请参阅下面的我的解决方案。。。,c#,streamreader,C#,Streamreader,我有以下问题需要解决: 我们从不同的来源接收文件(主要是地址信息),这些文件可以在Windows标准中使用CR/LF('\r'\n')作为换行符,也可以在UNIX中使用LF('\n') 当使用StreamReader.ReadLine()方法在中读取文本时,这没有问题,因为它会平等地处理这两种情况 当文件中的某个地方有CR或LF而不应该存在时,就会出现问题。 例如,如果将包含单元格内换行符的EXCEL文件导出为.CSV或其他平面文件,则会发生这种情况 现在您有了一个具有以下结构的文件: Firs
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
这很好,但第二行是:
Jane;Doe;co James Doe
TestStreet;5;TestCity;TestCountry
这将破坏您的代码,或者您将得到错误的结果,如下所示:
Jane;Doe;co James Doe
TestStreet;5;TestCity;TestCountry
因此,我们通常通过一个工具来运行该文件,该工具检查周围是否有松散的“\n”或“\r”,并将其删除
但这一步很容易忘记,因此我尝试实现自己的ReadLine()方法。要求是它能够使用一个或两个换行符,并且这些字符可以由消费逻辑自由定义
这是我想到的课程:
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倍。当我们处理大型行计数时,这种差异不仅会被测量,而且会反映在现实世界的性能中。
(对于700000行,读取所有行、提取块并将其写入新文件需要约5秒,使用我的方法,在我的系统上需要约15秒)
我尝试了使用更大缓冲区的不同方法,但到目前为止,我无法提高性能
我感兴趣的是:
有什么建议可以改进这段代码的性能,使其更接近StreamReader.ReadLine()的原始性能吗
解决方案:
现在需要约6秒(相比之下,使用默认的“StreamReader.ReadLine()”需要约5秒)才能完成700000行与上述代码相同的操作
谢谢吉姆·米谢尔为我指明了正确的方向
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();
}
}
}
公共类读取文件
{
私有文件流文件;
私有流阅读器;
私有字符串文件位置;
私有编码文件编码;
私有字符行break1;
私有字符行break2;
私人住宅使用条件中断;
const int BufferSize=8192;
int缓冲计数;
char[]rest=新字符[BufferSize];
int位置=0;
char lastChar;
布尔·乌斯拉斯卡尔;
private bool streamCreated=false;
私有bool内流;
公共布尔内流
{
获取{return endOfStream;}
设置{endOfStream=value;}
}
public ReadFile(字符串文件位置、编码文件编码、char LineBreak1、char LineBreak2、bool usecondlinebreak)
{
fileLocation=fileLocation;
fileEncoding=fileEncoding;
lineBreak1=lineBreak1;
lineBreak2=lineBreak2;
UseSeCondlineBreak=UseSeCondlineBreak;
}
私有int readInBuffer()
{
返回reader.Read(rest,0,BufferSize);
}
公共字符串读取行()
{
StringBuilder=新的StringBuilder();
bool lineFound=false;
if(streamCreated==false)
{
file=newfilestream(fileLocation,FileMode.Open,FileAccess.Read,FileShare.ReadWrite,8192);
reader=新的StreamReader(文件、文件编码);
streamCreated=true;
bufferedCount=readInBuffer();
}
while(lineFound==false&&EndOfStream!=true)
{
如果(位置<缓冲计数)
{
for(int i=位置;i 如果(i+1,加速的方法是让它一次读取多个字符。例如,创建一个4 KB的缓冲区,将数据读入该缓冲区,然后逐个字符进行。如果您将一个字符一个字符复制到StringBuilder
,这非常简单
下面的代码显示了如何解析循环中的行。您必须将其拆分,以便它可以维护调用之间的状态,但这应该会给您一个想法
const int BufferSize = 4096;
const string newline = "\r\n";
using (var strm = new StreamReader(....))
{
int newlineIndex = 0;
var buffer = new char[BufferSize];
StringBuilder sb = new StringBuilder();
int charsInBuffer = 0;
int bufferIndex = 0;
char lastChar = (char)-1;
while (!(strm.EndOfStream && bufferIndex >= charsInBuffer))
{
if (bufferIndex > charsInBuffer)
{
charsInBuffer = strm.Read(buffer, 0, buffer.Length);
if (charsInBuffer == 0)
{
// nothing read. Must be at end of stream.
break;
}
bufferIndex = 0;
}
if (buffer[bufferIndex] == newline[newlineIndex])
{
++newlineIndex;
if (newlineIndex == newline.Length)
{
// found a line
Console.WriteLine(sb.ToString());
newlineIndex = 0;
sb = new StringBuilder();
}
}
else
{
if (newlineIndex > 0)
{
// copy matched newline characters
sb.Append(newline.Substring(0, newlineIndex));
newlineIndex = 0;
}
sb.Append(buffer[bufferIndex]);
}
++bufferIndex;
}
// Might be a line left, without a newline
if (newlineIndex > 0)
{
sb.Append(newline.Substring(0, newlineIndex));
}
if (sb.Length > 0)
{
Console.WriteLine(sb.ToString());
}
}
您可以通过跟踪起始位置来对此进行优化,这样当您找到一行时,就可以创建一个从buffer[start]
到buffer[current]的字符串
,而不创建StringBuilder
。而是调用构造函数。当您跨越缓冲区边界时,这有点棘手。在这种情况下,可能希望将跨越缓冲区边界作为特例处理,并使用StringBuilder
作为临时存储
不过,在第一个版本开始运行之前,我不会为这种优化而烦恼。这不是我想要的实现