在 C# 中向后读取大文件(从头到尾)
Reading large files backwards (from end to start) in C#
我有一个包含一系列价格数据的文本文件。
该问题可能与任何长期的历史数据相匹配,例如温度、空气湿度、价格、日志文件...
我的历史文件的头部如下所示:
如果我想读取和处理一个太大而无法占用内存的文件,我通常会选择以下代码:
using (FileStream fs = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
using (BufferedStream bs = new BufferedStream(fs))
using (StreamReader sr = new StreamReader(bs))
{
string line;
while ((line = sr.ReadLine()) != null)
{
// Process Data
}
}
在我的例子中,每 1000 毫秒创建一条记录。最新数据位于文件末尾。
尝试处理最新数据时会出现问题。
示例:
我想生成过去 30 天的平均值。
从文件末尾开始并向开头移动直到达到 X 天阈值将是最有效的。
上面的示例代码将读取整个文件,这在这种情况下几乎不可用。 A worst-case 每次我需要更新最近的数据指标。
这个问题当然适用于任何你想处理最后 x 个元素的操作。
有没有从头到尾读取文件的功能?
您可以使用 Seek 到达文件末尾,但是您需要 "guess" 或计算距离末尾的距离...例如读取最后 1024 个字节:
stream.Seek(-1024, SeekOrigin.End);
只需算出最后 30 行最多可以有多少字节,并在文件末尾之前查找到那么远,然后只读取文件的那部分
试试下面的代码。最后一行可以是空白。不确定处理最后一行空白的最佳方式。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
namespace GetFileReverse
{
class Program
{
const string FILENAME = @"c:\temp\test.txt";
static void Main(string[] args)
{
GetFileReverse getFileReverse = new GetFileReverse(FILENAME);
string line = "";
while ((line = getFileReverse.ReadLine()) != null)
{
Console.WriteLine(line);
}
}
}
public class GetFileReverse : IDisposable
{
const int BUFFER_SIZE = 1024;
private FileStream stream { get; set; }
private string data { get; set; }
public Boolean SOF { get; set; }
private long position { get; set; }
public GetFileReverse(string filename)
{
stream = File.OpenRead(filename);
if (stream != null)
{
position = stream.Seek(0, SeekOrigin.End);
SOF = false;
data = string.Empty;
}
else
{
SOF = true;
}
}
private byte[] ReadStream()
{
byte[] bytes = null;
int size = BUFFER_SIZE;
if (position != 0)
{
bytes = new byte[BUFFER_SIZE];
long oldPosition = position;
if (position >= BUFFER_SIZE)
{
position = stream.Seek(-1 * BUFFER_SIZE, SeekOrigin.Current);
}
else
{
position = stream.Seek(-1 * position, SeekOrigin.Current);
size = (int)(oldPosition - position);
bytes = new byte[size];
}
stream.Read(bytes, 0, size);
stream.Seek(-1 * size, SeekOrigin.Current);
}
return bytes;
}
public string ReadLine()
{
string line = "";
while (!SOF && (!data.Contains("\r\n")))
{
byte[] bytes = ReadStream();
if (bytes != null)
{
string temp = Encoding.UTF8.GetString(bytes);
data = data.Insert(0, temp);
}
SOF = position == 0;
}
int lastReturn = data.LastIndexOf("\r\n");
if (lastReturn == -1)
{
if (data.Length > 0)
{
line = data;
data = string.Empty;
}
else
{
line = null;
}
}
else
{
line = data.Substring(lastReturn + 2);
data = data.Remove(lastReturn);
}
return line;
}
public void Close()
{
stream.Close();
}
public void Dispose()
{
stream.Dispose();
data = string.Empty;
position = -1;
}
}
}
我有一个包含一系列价格数据的文本文件。 该问题可能与任何长期的历史数据相匹配,例如温度、空气湿度、价格、日志文件...
我的历史文件的头部如下所示:
如果我想读取和处理一个太大而无法占用内存的文件,我通常会选择以下代码:
using (FileStream fs = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
using (BufferedStream bs = new BufferedStream(fs))
using (StreamReader sr = new StreamReader(bs))
{
string line;
while ((line = sr.ReadLine()) != null)
{
// Process Data
}
}
在我的例子中,每 1000 毫秒创建一条记录。最新数据位于文件末尾。 尝试处理最新数据时会出现问题。
示例:
我想生成过去 30 天的平均值。
从文件末尾开始并向开头移动直到达到 X 天阈值将是最有效的。
上面的示例代码将读取整个文件,这在这种情况下几乎不可用。 A worst-case 每次我需要更新最近的数据指标。
这个问题当然适用于任何你想处理最后 x 个元素的操作。
有没有从头到尾读取文件的功能?
您可以使用 Seek 到达文件末尾,但是您需要 "guess" 或计算距离末尾的距离...例如读取最后 1024 个字节:
stream.Seek(-1024, SeekOrigin.End);
只需算出最后 30 行最多可以有多少字节,并在文件末尾之前查找到那么远,然后只读取文件的那部分
试试下面的代码。最后一行可以是空白。不确定处理最后一行空白的最佳方式。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
namespace GetFileReverse
{
class Program
{
const string FILENAME = @"c:\temp\test.txt";
static void Main(string[] args)
{
GetFileReverse getFileReverse = new GetFileReverse(FILENAME);
string line = "";
while ((line = getFileReverse.ReadLine()) != null)
{
Console.WriteLine(line);
}
}
}
public class GetFileReverse : IDisposable
{
const int BUFFER_SIZE = 1024;
private FileStream stream { get; set; }
private string data { get; set; }
public Boolean SOF { get; set; }
private long position { get; set; }
public GetFileReverse(string filename)
{
stream = File.OpenRead(filename);
if (stream != null)
{
position = stream.Seek(0, SeekOrigin.End);
SOF = false;
data = string.Empty;
}
else
{
SOF = true;
}
}
private byte[] ReadStream()
{
byte[] bytes = null;
int size = BUFFER_SIZE;
if (position != 0)
{
bytes = new byte[BUFFER_SIZE];
long oldPosition = position;
if (position >= BUFFER_SIZE)
{
position = stream.Seek(-1 * BUFFER_SIZE, SeekOrigin.Current);
}
else
{
position = stream.Seek(-1 * position, SeekOrigin.Current);
size = (int)(oldPosition - position);
bytes = new byte[size];
}
stream.Read(bytes, 0, size);
stream.Seek(-1 * size, SeekOrigin.Current);
}
return bytes;
}
public string ReadLine()
{
string line = "";
while (!SOF && (!data.Contains("\r\n")))
{
byte[] bytes = ReadStream();
if (bytes != null)
{
string temp = Encoding.UTF8.GetString(bytes);
data = data.Insert(0, temp);
}
SOF = position == 0;
}
int lastReturn = data.LastIndexOf("\r\n");
if (lastReturn == -1)
{
if (data.Length > 0)
{
line = data;
data = string.Empty;
}
else
{
line = null;
}
}
else
{
line = data.Substring(lastReturn + 2);
data = data.Remove(lastReturn);
}
return line;
}
public void Close()
{
stream.Close();
}
public void Dispose()
{
stream.Dispose();
data = string.Empty;
position = -1;
}
}
}