我有一个大文件> 200MB。该文件是来自外部方的CSV文件,但遗憾的是我不能逐行读取文件,因为\r\n
用于定义新行。
目前,我正在使用这种方法阅读所有内容:
var file = File.ReadAllText(filePath, Encoding.Default);
var lines = Regex.Split(file, @"\r\n");
for (int i = 0; i < lines.Length; i++)
{
string line = lines[i];
...
}
如何优化此功能?在我的225MB文件上调用ReadAllText后,该过程使用的RAM超过1GB。在我的情况下是否可以使用流式处理方法,我需要使用\r\n
模式拆分文件?
EDIT1:
使用 File.ReadLines 和 StreamReader 的解决方案将无法正常工作,因为它将文件中的每一行视为一行。我需要使用\r\n
模式拆分文件。使用我的代码读取文件会产生758.371行(这是正确的),而正常的行数会产生超过150万行。
解
public static IEnumerable<string> ReadLines(string path)
{
const string delim = "\r\n";
using (StreamReader sr = new StreamReader(path))
{
StringBuilder sb = new StringBuilder();
while (!sr.EndOfStream)
{
for (int i = 0; i < delim.Length; i++)
{
Char c = (char)sr.Read();
sb.Append(c);
if (c != delim[i])
break;
if (i == delim.Length - 1)
{
sb.Remove(sb.Length - delim.Length, delim.Length);
yield return sb.ToString();
sb = new StringBuilder();
break;
}
}
}
if (sb.Length>0)
yield return sb.ToString();
}
}
答案 0 :(得分:6)
您可以使用返回IEnumerable<string>
的{{3}},而不是将整个文件加载到内存中。
foreach(var line in File.ReadLines(@filePath, Encoding.Default)
.Where(l => !String.IsNullOrEmpty(l)))
{
}
答案 1 :(得分:4)
使用StreamReader很容易。
using (StreamReader sr = new StreamReader(path))
{
foreach(string line = GetLine(sr))
{
//
}
}
IEnumerable<string> GetLine(StreamReader sr)
{
while (!sr.EndOfStream)
yield return new string(GetLineChars(sr).ToArray());
}
IEnumerable<char> GetLineChars(StreamReader sr)
{
if (sr.EndOfStream)
yield break;
var c1 = sr.Read();
if (c1 == '\\')
{
var c2 = sr.Read();
if (c2 == 'r')
{
var c3 = sr.Read();
if (c3 == '\\')
{
var c4 = sr.Read();
if (c4 == 'n')
{
yield break;
}
else
{
yield return (char)c1;
yield return (char)c2;
yield return (char)c3;
yield return (char)c4;
}
}
else
{
yield return (char)c1;
yield return (char)c2;
yield return (char)c3;
}
}
else
{
yield return (char)c1;
yield return (char)c2;
}
}
else
yield return (char)c1;
}
答案 2 :(得分:0)
使用StreamReader逐行读取文件:
using (StreamReader sr = new StreamReader(filePath))
{
while (true)
{
string line = sr.ReadLine();
if (line == null)
break;
}
}
答案 3 :(得分:0)
怎么样
StreamReader sr = new StreamReader(path);
while (!sr.EndOfStream)
{
string line = sr.ReadLine();
}
使用流式阅读器方法意味着整个文件不会被加载到内存中。
答案 4 :(得分:0)
这是我午休时间:)
将MAXREAD
设置为您在内存中所需的数据量,例如使用foreach
,因为我正在使用yield return
。使用代码需要您自担风险,我已经在较小的数据集上尝试过了:)
您的用法如下:
foreach (var row in StreamReader(FileName).SplitByChar(new char[] {'\r','\n'}))
{
// Do something awesome! :)
}
这样的扩展方法:
public static class FileStreamExtensions
{
public static IEnumerable<string> SplitByChar(this StreamReader stream, char[] splitter)
{
int MAXREAD = 1024 * 1024;
var chars = new List<char>(MAXREAD);
var bytes = new char[MAXREAD];
var lastStop = 0;
var read = 0;
while (!stream.EndOfStream)
{
read = stream.Read(bytes, 0, MAXREAD);
lastStop = 0;
for (int i = 0; i < read; i++)
{
if (bytes[i] == splitter[0])
{
var assume = true;
for (int p = 1; p < splitter.Length; p++)
{
assume &= splitter[p] == bytes[i + p];
}
if (assume)
{
chars.AddRange(bytes.Skip(lastStop).Take(i - lastStop));
var res = new String(chars.ToArray());
chars.Clear();
yield return res;
i += splitter.Length - 1;
lastStop = i + 1;
}
}
}
chars.AddRange(bytes.Skip(lastStop));
}
chars.AddRange(bytes.Skip(lastStop).Take(read - lastStop));
yield return new String(chars.ToArray());
}
}