我有一个6GB的文件,最后20行是坏的。我想使用带有.NET 4的内存映射文件来读取最后几行并在console.writelines中显示它们,然后转到最后20行并用String.Empty替换它们。使用带有C#示例的内存映射文件/流,这是一种很酷的方法吗?
感谢。
答案 0 :(得分:3)
内存映射文件可能是大文件(通常是大小等于或大于RAM的文件)的问题,以防您最终映射整个文件。如果只映射结尾,那不应该是一个真正的问题。
无论如何,这是一个不使用内存映射文件的C#实现,而是一个常规的FileStream。它基于ReverseStreamReader
实现(还包括代码)。我很想看到它在性能和内存消耗方面与其他MMF解决方案相比。
public static void OverwriteEndLines(string filePath, int linesToStrip)
{
if (filePath == null)
throw new ArgumentNullException("filePath");
if (linesToStrip <= 0)
return;
using (FileStream file = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite))
{
using (ReverseStreamReader reader = new ReverseStreamReader(file))
{
int count = 0;
do
{
string line = reader.ReadLine();
if (line == null) // end of file
break;
count++;
if (count == linesToStrip)
{
// write CR LF
for (int i = 0; i < linesToStrip; i++)
{
file.WriteByte((byte)'\r');
file.WriteByte((byte)'\n');
}
// truncate file to current stream position
file.SetLength(file.Position);
break;
}
}
while (true);
}
}
}
// NOTE: we have not implemented all ReadXXX methods
public class ReverseStreamReader : StreamReader
{
private bool _returnEmptyLine;
public ReverseStreamReader(Stream stream)
: base(stream)
{
BaseStream.Seek(0, SeekOrigin.End);
}
public override int Read()
{
if (BaseStream.Position == 0)
return -1;
BaseStream.Seek(-1, SeekOrigin.Current);
int i = BaseStream.ReadByte();
BaseStream.Seek(-1, SeekOrigin.Current);
return i;
}
public override string ReadLine()
{
if (BaseStream.Position == 0)
{
if (_returnEmptyLine)
{
_returnEmptyLine = false;
return string.Empty;
}
return null;
}
int read;
StringBuilder sb = new StringBuilder();
while((read = Read()) >= 0)
{
if (read == '\n')
{
read = Read();
// supports windows & unix format
if ((read > 0) && (read != '\r'))
{
BaseStream.Position++;
}
else if (BaseStream.Position == 0)
{
// handle the special empty first line case
_returnEmptyLine = true;
}
break;
}
sb.Append((char)read);
}
// reverse string. Note this is optional if we don't really need string content
if (sb.Length > 1)
{
char[] array = new char[sb.Length];
sb.CopyTo(0, array, 0, array.Length);
Array.Reverse(array);
return new string(array);
}
return sb.ToString();
}
}
答案 1 :(得分:1)
从问题来看,你需要有一个Memory Mapped文件。但是,有一种方法可以在不使用内存映射文件的情况下执行此操作。
正常打开文件,然后将文件指针移动到文件末尾。一旦你到最后,反向读取文件(每次读取后减少文件指针),直到你得到所需的字符数。
很酷的方式......将字符反向加载到一个数组中,然后一旦完成阅读就不必反转它们。
对数组进行修复,然后将其写回。关闭,冲洗,完成!
答案 2 :(得分:0)
解决方案分为两部分。对于第一部分,您需要向后读取内存映射以获取行,直到您已读取所需的行数(在本例中为20)。
对于第二部分,您希望通过最后20行截断文件(通过将它们设置为string.Empty)。我不确定你是否可以用内存映射来做到这一点。 您可能必须在某处复制文件并用源数据覆盖原始数据,但最后的xxx字节除外(代表最后20行)
下面的代码将提取最后20行并显示它。
您还将获得该职位( lastBytePos 变量) 最后二十行开始的地方。您可以使用该信息来了解截断文件的位置。
更新:截断文件调用FileStream.SetLength(lastBytePos)
我不确定你最后20行的意思是不好的。如果磁盘物理损坏且数据无法读取,我添加了一个 badPositions 列表,其中包含memorymap在读取数据时遇到问题的位置。
我没有要测试的+ 2GB文件,但它应该可以工作(手指交叉)。
using System;
using System.Collections.Generic;
using System.Text;
using System.IO.MemoryMappedFiles;
using System.IO;
namespace ConsoleApplication
{
class Program
{
static void Main(string[] args)
{
string filename = "textfile1.txt";
long fileLen = new FileInfo(filename).Length;
List<long> badPositions = new List<long>();
List<byte> currentLine = new List<byte>();
List<string> lines = new List<string>();
bool lastReadByteWasLF = false;
int linesToRead = 20;
int linesRead = 0;
long lastBytePos = fileLen;
MemoryMappedFile mapFile = MemoryMappedFile.CreateFromFile(filename, FileMode.Open);
using (mapFile)
{
var view = mapFile.CreateViewAccessor();
for (long i = fileLen - 1; i >= 0; i--) //iterate backwards
{
try
{
byte b = view.ReadByte(i);
lastBytePos = i;
switch (b)
{
case 13: //CR
if (lastReadByteWasLF)
{
{
//A line has been read
var bArray = currentLine.ToArray();
if (bArray.LongLength > 1)
{
//Add line string to lines collection
lines.Insert(0, Encoding.UTF8.GetString(bArray, 1, bArray.Length - 1));
//Clear current line list
currentLine.Clear();
//Add CRLF to currentLine -- comment this out if you don't want CRLFs in lines
currentLine.Add(13);
currentLine.Add(10);
linesRead++;
}
}
}
lastReadByteWasLF = false;
break;
case 10: //LF
lastReadByteWasLF = true;
currentLine.Insert(0, b);
break;
default:
lastReadByteWasLF = false;
currentLine.Insert(0, b);
break;
}
if (linesToRead == linesRead)
{
break;
}
}
catch
{
lastReadByteWasLF = false;
currentLine.Insert(0, (byte) '?');
badPositions.Insert(0, i);
}
}
}
if (linesToRead > linesRead)
{
//Read last line
{
var bArray = currentLine.ToArray();
if (bArray.LongLength > 1)
{
//Add line string to lines collection
lines.Insert(0, Encoding.UTF8.GetString(bArray));
linesRead++;
}
}
}
//Print results
lines.ForEach( o => Console.WriteLine(o));
Console.ReadKey();
}
}
}
答案 3 :(得分:0)
我对ReverseStreamReaders一无所知。解决方案[基本上]很简单:
但是,关于“反向读取线条”,细节仍然存在。有一些复杂的因素可能会让你陷入困境:
我不确定是否有一个好的,简单的解决方案在显而易见的范围之外:按顺序读取文件,不要写最后20行。
答案 4 :(得分:0)
首先,我将用F#编写代码,但是由于我的C#编码生锈,应该可以将其转换为C#代码。
第二,据我了解,您需要一种有效的方法来访问某些文件的内容并对其进行更改,然后将其写回。
要使用memorymappedfile,您需要先将其全部读取到临时的映射文件tmp中。这只会产生一些过热,因为您将在一次读取中完成所有操作。然后,使用tmp更改内容,然后在完成后首先写回新文件内容。这将比使用普通文件流更快,并且您不应该非常关注堆栈/堆溢出。
open System.IO
open Sytem.IO.MemoryMappedFiles
// Create a memorymapped image of the file content i.e. copy content
// return the memorymappedfile
// use is the same as using in C#
let createMappedImage path =
let mmf = MemorymappedFile.create("tmp", (fileInfo(path)).Length)
use writer = new StreamWriter(mmf.CreaViewStream())
writer.write(File.ReadAllText(path))
mmf // return memorymappedfile to be used
// Some manipulation function to apply to the image
// type : char[] -> StreamReader -> unit
let fillBuffer (buffer : byte[]) (reader : StreamReader) =
let mutable entry = 0
let mutable ret = reader.Read() // return -1 as EOF
while ret >= 0 && entry < buffer.Length do
buffer.[entry] <- ret
entry <- entry + 1
entry // return count of byte read
// type : int -> byte[] -> StreamWriter -> unit
let flushBuffer count (buffer : byte[]) (writer : StreamWriter) =
let stop = count + 1
let mutable entry = 0
while entry < stop do
writer.Write(buffer.[entry])
entry <- entry + 1
// return unit e.i. void
// read then write the buffer one time
// writeThrough call fillBuffer which return the count of byte read,
// and input it to the flushBuffer that then write it to the destination.
let writeThrough buffer source dest =
flushBuffer (fillBuffer buffer source) buffer dest
// return unit
// write back the altered content of the image without overflow
let writeBackMappedImage bufsize dest image =
// buffer for read/write
let buf = Array.Create bsize (byte 0)// normal page is 4096 byte
// delete old content on write
use writer = new StreamWriter(File.Open(dest,FileMode.Truncate,FileAccess.Write))
use reader = new StreamReader(image.CreateViewStream())
while not reader.EndOfStream do
writeThrough buf reader writer
let image = createMappedImage "some path"
let alteredImage = alteration image // some undefined function to correct the content.
writeBackMappedImage image
image.dispose()
image.close()
这还没有运行,所以可能会有一些错误,但是我认为这个想法很明确。如上所述,createMappedImage创建文件的内存映射图像文件。
fillbuffer接收一个字节数组和一个流读取器,然后填充并返回 flushBuffer计算应该刷新多少缓冲区,源流读取器和目标流写入器。
您需要对文件执行的所有操作都可以对图像进行处理,而无需无意间对文件进行危险的操作。如果确定转换正确无误,则可以将图像内容写回。