如果我在任何时候错了,请随时纠正我......
我正在尝试使用.NET文件I / O类读取CSV(逗号分隔值)文件。现在的问题是,这个CSV文件可能包含一些带有软回车的字段(即单独的\ r或\ n标记,而不是文本文件中用于结束一行的标准\ r \ n)以及某些字段和标准文本模式I / O类StreamReader不遵循标准约定,并将软回车视为硬回车,从而危及CSV文件的完整性。
现在使用BinaryReader类似乎是唯一剩下的选项,但是BinaryReader没有ReadLine()函数,因此我需要自己实现ReadLine()。
我当前的方法一次从流中读取一个字符并填充StringBuilder,直到获得\ r \ n(忽略所有其他字符,包括solitary \ r或\ n),然后返回StringBuilder的字符串表示形式(使用ToString())。
但我想知道:这是实现ReadLine()函数最有效的方法吗?请赐教。
答案 0 :(得分:7)
可能是。在顺序方面,它只通过每个char一次,因此它将是O(n)(其中n是流的长度),所以这不是问题。要阅读单个字符,BinaryReader是您最好的选择。
我要做的是上课
public class LineReader : IDisposable
{
private Stream stream;
private BinaryReader reader;
public LineReader(Stream stream) { reader = new BinaryReader(stream); }
public string ReadLine()
{
StringBuilder result = new StringBuilder();
char lastChar = reader.ReadChar();
// an EndOfStreamException here would propogate to the caller
try
{
char newChar = reader.ReadChar();
if (lastChar == '\r' && newChar == '\n')
return result.ToString();
result.Append(lastChar);
lastChar = newChar;
}
catch (EndOfStreamException)
{
result.Append(lastChar);
return result.ToString();
}
}
public void Dispose()
{
reader.Close();
}
}
或类似的东西。
<子> (警告:该代码未经过测试,按原样提供,没有任何明示或暗示的保证。如果该程序证明有缺陷或毁坏地球,则承担所有必要的维修,修理或纠正的费用。)子>
答案 1 :(得分:1)
您可能希望使用ODBC / OleDB连接来执行此操作。如果将oledb连接的数据源指向包含csv文件的目录,则可以查询它,就像每个CSV都是一个表一样。
检查http://www.connectionstrings.com/?carrier=textfile>connectionstrings.com以获取正确的连接字符串
答案 2 :(得分:1)
这是BinaryReader类的扩展方法:
using System.IO;
using System.Text;
public static class BinaryReaderExtension
{
public static string ReadLine(this BinaryReader reader)
{
if (reader.IsEndOfStream())
return null;
StringBuilder result = new StringBuilder();
char character;
while(!reader.IsEndOfStream() && (character = reader.ReadChar()) != '\n')
if (character != '\r' && character != '\n')
result.Append(character);
return result.ToString();
}
public static bool IsEndOfStream(this BinaryReader reader)
{
return reader.BaseStream.Position == reader.BaseStream.Length;
}
}
我没有在所有条件下进行测试,但这段代码对我有用。
答案 3 :(得分:0)
如何简单地预处理文件?
用一些独特的东西替换软回车。
对于记录,数据中包含换行符的CSV文件,这是糟糕的设计。
答案 4 :(得分:0)
你可以一次读取一个更大的块,使用Encoder.GetString将其解码为字符串,然后使用string.Split(“\ r \ n”)拆分成行,甚至使用字符串挑选字符串的头部.Substring(0,string.IndexOf(“\ r \ n”))并留下其余部分来处理下一行。请记住将下一个读取操作添加到上一次读取的最后一行。
答案 5 :(得分:0)
你的方法听起来不错。提高方法效率的一种方法可能是在用常规字符串(即不是StringBuilder)构建它时存储每一行,然后将整行字符串附加到StringBuilder。有关详细说明,请参阅this article - StringBuilder不会自动成为最佳选择。
但是,它可能不会那么重要。答案 6 :(得分:0)
这是一种具有编码支持的更快的替代方案。它扩展了BinaryReader,因此您可以使用它来执行这两个操作,读取二进制块并直接在二进制流上执行类似ReadLine的StreamReader。
public class LineReader : BinaryReader
{
private Encoding _encoding;
private Decoder _decoder;
const int bufferSize = 1024;
private char[] _LineBuffer = new char[bufferSize];
public LineReader(Stream stream, int bufferSize, Encoding encoding)
: base(stream, encoding)
{
this._encoding = encoding;
this._decoder = encoding.GetDecoder();
}
public string ReadLine()
{
int pos = 0;
char[] buf = new char[2];
StringBuilder stringBuffer = null;
bool lineEndFound = false;
while(base.Read(buf, 0, 2) > 0)
{
if (buf[1] == '\r')
{
// grab buf[0]
this._LineBuffer[pos++] = buf[0];
// get the '\n'
char ch = base.ReadChar();
Debug.Assert(ch == '\n');
lineEndFound = true;
}
else if (buf[0] == '\r')
{
lineEndFound = true;
}
else
{
this._LineBuffer[pos] = buf[0];
this._LineBuffer[pos+1] = buf[1];
pos += 2;
if (pos >= bufferSize)
{
stringBuffer = new StringBuilder(bufferSize + 80);
stringBuffer.Append(this._LineBuffer, 0, bufferSize);
pos = 0;
}
}
if (lineEndFound)
{
if (stringBuffer == null)
{
if (pos > 0)
return new string(this._LineBuffer, 0, pos);
else
return string.Empty;
}
else
{
if (pos > 0)
stringBuffer.Append(this._LineBuffer, 0, pos);
return stringBuffer.ToString();
}
}
}
if (stringBuffer != null)
{
if (pos > 0)
stringBuffer.Append(this._LineBuffer, 0, pos);
return stringBuffer.ToString();
}
else
{
if (pos > 0)
return new string(this._LineBuffer, 0, pos);
else
return null;
}
}
}