不知道内存映射文件或内存映射视图访问器的大小,如何读取其所有内容?

9
我需要类似于ReadToEnd或ReadAllBytes的东西,使用MappedViewAccessor读取MemoryMappedFile的所有内容,如果我不知道它的大小,我该怎么办?
我已经搜索过了,看到了这个问题,但那不是我要找的: 如何在.NET中快速从内存映射文件中读取字节? 编辑:
有一个问题,(int)stream.Length没有给我正确的长度,它实际上给出了内部缓冲区的大小!我需要更新这个问题,因为它非常紧迫。

1
我不认为这是一个有效的问题... 固件的大小如何适应内存区域,您的数据必须适合指定的区域。您已经创建了文件的大小,如果您没有该大小,则需要通过抽象或API提供给您或在使用文件之前知道该大小。您还可以在内存映射文件中创建标题以指示长度和当前偏移量(如果需要)。例如,如果文件大小小于页面大小,并且我在您的大小之后写入该区域,那会怎样? - Jay
7个回答

17

最好使用Stream:

public static Byte[] ReadMMFAllBytes(string fileName)
{
    using (var mmf = MemoryMappedFile.OpenExisting(fileName))
    {
        using (var stream = mmf.CreateViewStream())
        {
            using (BinaryReader binReader = new BinaryReader(stream))
            {
                return binReader.ReadBytes((int)stream.Length);
            }
        }
    }
}

答案有些问题,请提供更好的答案。 - Saw
太棒了,我用这种方式读写字符串没有发现任何问题,当获取字符串字节时,那些四舍五入的4096字节就会消失。真的是非常有用的解决方案。 - ElektroStudios

8
这很难回答,因为您的应用程序仍有许多细节没有具体说明,但我认为Guffa和Amer的回答部分正确:
  • MemoryMappedFile比文件更像内存;它是内存中4Kb页面的序列。 因此,stream.Length实际上将给出所有字节(没有“内部缓冲区大小”),但它可能会给出比您预期更多的字节,因为大小总是舍入到4Kb边界。
  • “文件”语义来自将MemoryMappedFile关联到实际的文件系统文件。 假设创建文件的进程总是调整文件大小,则可以通过fileSystem获取文件的精确大小。

如果以上所有内容都适用于您的应用程序,则以下代码应该有效:

    static byte[] ReadMemoryMappedFile(string fileName)
    {
        long length = new FileInfo(fileName).Length;
        using (var stream = File.Open(fileName, FileMode.OpenOrCreate, FileAccess.Read, FileShare.ReadWrite))
        {
            using (var mmf = MemoryMappedFile.CreateFromFile(stream, null, length, MemoryMappedFileAccess.Read, null, HandleInheritability.Inheritable, false))
            {
                using (var viewStream = mmf.CreateViewStream(0, length, MemoryMappedFileAccess.Read))
                {
                    using (BinaryReader binReader = new BinaryReader(viewStream))
                    {
                        var result = binReader.ReadBytes((int)length);
                        return result;
                    }
                }
            }
        }
    }

你可以使用以下内容来写入数据:

    private static void WriteData(string fileName, byte[] data)
    {
        using (var stream = File.Open(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite))
        {
            using (var mmf = MemoryMappedFile.CreateFromFile(stream, null, data.Length, MemoryMappedFileAccess.ReadWrite, null, HandleInheritability.Inheritable, true))
            {
                using (var view = mmf.CreateViewAccessor())
                {
                    view.WriteArray(0, data, 0, data.Length);
                }
            }

            stream.SetLength(data.Length);  // Make sure the file is the correct length, in case the data got smaller.
        }
    }

但是,如果您按照上述步骤进行操作,您可能会直接使用文件并避免内存映射。 如果将其映射到文件系统不可接受,则Guffa的答案是在数据本身中编码长度(或结束标记)。


不需要使用 FileInfo(fileName).Length,CreateFromFile 内部会使内存映射文件的容量与文件大小匹配。只需将大小参数传递为 0 即可。 - Brans Ds
即使您将0作为大小传递,容量也将等于或大于实际文件大小(它会舍入到下一个4096字节边界)。如果您需要实际文件大小,则无法避免使用new FileInfo(path).Length。 - stmax

7

你不能这样做。

视图访问器创建时会设置一个系统页面的最小大小,这意味着它可能比实际文件要大。视图流只是访问器的流形式,因此它也会产生相同的行为。

"视图以系统页面为单位提供,并且视图的大小将向上舍入到下一个系统页面大小"

http://msdn.microsoft.com/en-us/library/dd267577.aspx

访问器会在不抛出异常的情况下愉快地读取和写入实际文件之外的内容。在读取时,文件之外的任何字节都将被视为零。在写入时,写入文件之外的字节将被忽略。

要从具有与原始文件完全相同大小的内存映射文件中读取文件,您必须已经知道该大小。


你对 IPC 有什么建议?是将一些尾部添加到文件中吗?还是别的什么建议? - Saw
我在考虑将文件大小放在开头。 - Saw
@MohamedSakherSawan:是的,任何文件结构,其中文件本身中的数据可以用于确定大小的都可以。 - Guffa
你可能也可以在头部包含当前的偏移量/长度... - Jay
@Guffa 可能吧..抱歉,我不知道。只是为了那些找到并使用它的人。最近因为这个问题我遇到了一些麻烦。 - Brans Ds
显示剩余3条评论

2

使用MemoryMappedFile创建的流长度与文件系统页面大小对齐(通常为4096),您需要从其他地方获取文件大小。如果是内存映射文件,则可以使用以下代码:

byte[] ReadAllMemoryMappedFileBytes(string filePath)
{
    var fileInfo = new FileInfo(filePath);
    using (var file = MemoryMappedFile.CreateFromFile(filePath, FileMode.Open))
    using (var stream = file.CreateViewAccessor())
    {
        byte[] bytes = new byte[fileInfo.Length];
        stream.ReadArray(0, bytes, 0, bytes.Length);
        return bytes;
    }
}

1
使用FileInfo类获取长度,如下所示。
using System.Data;
using System.IO;
using System.IO.Compression;
using System.IO.MemoryMappedFiles;

// ...

public void WriteToMemoryMap(DataSet ds, string key, string fileName)
{
    var bytes = CompressData(ds);
    using (MemoryMappedFile objMf = MemoryMappedFile.CreateFromFile(fileName, FileMode.OpenOrCreate, key, bytes.Length))
    {
        using (MemoryMappedViewAccessor accessor = objMf.CreateViewAccessor())
        {
            accessor.WriteArray(0, bytes, 0, bytes.Length);
        }
    }
}
public DataSet ReadFromMemoryMap(string fileName)
{
    var fi = new FileInfo(fileName);
    var length = (int)fi.Length;
    var newBytes = new byte[length];
    using (MemoryMappedFile objMf = MemoryMappedFile.CreateFromFile(fileName, FileMode.Open))
    {
        using (MemoryMappedViewAccessor accessor = objMf.CreateViewAccessor())
        {
            accessor.ReadArray(0, newBytes, 0, length);
        }
    }
    return DecompressData(newBytes);
}
public byte[] CompressData(DataSet ds)
{
    try
    {
        byte[] data = null;
        var memStream = new MemoryStream();
        var zipStream = new GZipStream(memStream, CompressionMode.Compress);
        ds.WriteXml(zipStream, XmlWriteMode.WriteSchema);
        zipStream.Close();
        data = memStream.ToArray();
        memStream.Close();
        return data;
    }
    catch (Exception)
    {
        return null;
    }
}
public DataSet DecompressData(byte[] data)
{
    try
    {
        var memStream = new MemoryStream(data);
        var unzipStream = new GZipStream(memStream, CompressionMode.Decompress);
        var objDataSet = new DataSet();
        objDataSet.ReadXml(unzipStream, XmlReadMode.ReadSchema);
        unzipStream.Close();
        memStream.Close();
        return objDataSet;
    }
    catch (Exception)
    {
        return null;
    }
}

如果可能的话,请进一步解释一下这段代码是如何工作的,以便提问者理解为什么它可以解决他们的问题。 - SuperBiasedMan
这一行非常重要:var fi = new FileInfo(fileName); 一旦他知道了文件的长度,就可以使用它来读取文件中的所有内容。 - user2683973
我认为你不能在共享内存文件上使用FileInfo。 - tofutim

0
我想从MemoryStream.ToArray()方法中获取所有字节,并且以下代码对我有用:
using (MemoryMappedFile mmf = MemoryMappedFile.OpenExisting(MemoryMappedName))
{
    using (MemoryMappedViewStream stream = mmf.CreateViewStream())
    {
        using (MemoryStream memStream = new MemoryStream())
        {
            stream.CopyTo(memStream);
            return memStream.ToArray();
        }
    }
}

干杯!


0
只是将@Amer Sawan的解决方案翻译成Vb.NET:
' Usage Example:
' Dim ReadBytes As Byte() = ReadMemoryMappedFile(Name:="My MemoryMappedFile Name") ' Read the byte-sequence from memory.
' Dim Message As String = System.Text.Encoding.ASCII.GetString(ReadBytes.ToArray) ' Convert the bytes to String.
' Message = Message.Trim({ControlChars.NullChar}) ' Remove null chars (leading zero-bytes)
' MessageBox.Show(Message, "", MessageBoxButtons.OK) ' Show the message.    '
'
''' <summary>
''' Reads a byte-sequence from a <see cref="IO.MemoryMappedFiles.MemoryMappedFile"/> without knowing the exact size.
''' Note that the returned byte-length is rounded up to 4kb, 
''' this means if the mapped memory-file was written with 1 byte-length, this method will return 4096 byte-length. 
''' </summary>
''' <param name="Name">Indicates an existing <see cref="IO.MemoryMappedFiles.MemoryMappedFile"/> assigned name.</param>
''' <returns>System.Byte().</returns>
Private Function ReadMemoryMappedFile(ByVal Name As String) As Byte()

    Try
        Using MemoryFile As IO.MemoryMappedFiles.MemoryMappedFile =
            IO.MemoryMappedFiles.MemoryMappedFile.OpenExisting(Name, IO.MemoryMappedFiles.MemoryMappedFileRights.ReadWrite)

            Using Stream = MemoryFile.CreateViewStream()

                Using Reader As New BinaryReader(Stream)

                    Return Reader.ReadBytes(CInt(Stream.Length))

                End Using ' Reader

            End Using ' Stream

        End Using ' MemoryFile

    Catch exNoFile As IO.FileNotFoundException
        Throw
        Return Nothing

    Catch ex As Exception
        Throw

    End Try

End Function

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接