用C#实现的内存映射

当文件过大时,无法一次性载入内存时,就需要分次,分段的载入文件

主要是用了以下的WinAPI

LPVOID MapViewOfFile(HANDLE hFileMappingObject,

  DWORD dwDesiredAccess,

  DWORD dwFileOffsetHigh,

  DWORD dwFileOffsetLow,

  DWORD dwNumberOfBytesToMap);

  MapViewOfFile() 函数负责把文件数据映射到进程的地址空间,参数hFileMappingObject 为 CreateFileMapping()返回的文件映像对象句柄。参数dwDesiredAccess则再次指定了对文件数据的访问方式,而且同样要与 CreateFileMapping()函数所设置的保护属性相匹配。虽然这里一再对保护属性进行重复设置看似多余,但却可以使应用程序能更多的对数据的保护属性实行有效控制。MapViewOfFile()函数允许全部或部分映射文件,在映射时,需要指定数据文件的偏移地址以及待映射的长度。其中,文件的偏移地址由DWORD型的参数dwFileOffsetHigh和dwFileOffsetLow组成的64位值来指定,而且必须是操作系统的分配粒度的整数倍,对于Windows操作系统,分配粒度固定为64KB。当然,也可以通过如下代码来动态获取当前操作系统的分配粒度:

  SYSTEM_INFO sinf;

  GetSystemInfo(&sinf);

  DWORD dwAllocationGranularity = sinf.dwAllocationGranularity;

  参数dwNumberOfBytesToMap指定了数据文件的映射长度,这里需要特别指出的是,对于Windows 9x操作系统,如果MapViewOfFile()无法找到足够大的区域来存放整个文件映射对象,将返回空值(NULL);但是在Windows 2000下,MapViewOfFile()只需要为必要的视图找到足够大的一个区域即可,而无须考虑整个文件映射对象的大小。

由此看出,分页映射文件时,每页的起始位置startpos,必须为64K的整数倍。

以下贴出源代码,防止忘记了

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

namespace BlueVision.SaYuan.FileMapping
{
    public class ShareMemory
    {
        [DllImport( "user32.dll", CharSet = CharSet.Auto )]
        public static extern IntPtr SendMessage( IntPtr hWnd, int Msg, int wParam, IntPtr lParam );

        [DllImport( "Kernel32.dll", CharSet = CharSet.Auto )]
        public static extern IntPtr CreateFileMapping( IntPtr hFile, IntPtr lpAttributes, uint flProtect, uint dwMaxSizeHi, uint dwMaxSizeLow, string lpName );

        [DllImport( "Kernel32.dll", CharSet = CharSet.Auto )]
        public static extern IntPtr OpenFileMapping( int dwDesiredAccess, [MarshalAs( UnmanagedType.Bool )] bool bInheritHandle, string lpName );

        [DllImport( "Kernel32.dll", CharSet = CharSet.Auto )]
        public static extern IntPtr MapViewOfFile( IntPtr hFileMapping, uint dwDesiredAccess, uint dwFileOffsetHigh, uint dwFileOffsetLow, uint dwNumberOfBytesToMap );

        [DllImport( "Kernel32.dll", CharSet = CharSet.Auto )]
        public static extern bool UnmapViewOfFile( IntPtr pvBaseAddress );

        [DllImport( "Kernel32.dll", CharSet = CharSet.Auto )]
        public static extern bool CloseHandle( IntPtr handle );

        [DllImport( "kernel32", EntryPoint = "GetLastError" )]
        public static extern int GetLastError();

        [DllImport( "kernel32.dll" )]
        static extern void GetSystemInfo( out SYSTEM_INFO lpSystemInfo );

        [StructLayout( LayoutKind.Sequential )]
        public struct SYSTEM_INFO
        {
            public ushort processorArchitecture;
            ushort reserved;
            public uint pageSize;
            public IntPtr minimumApplicationAddress;
            public IntPtr maximumApplicationAddress;
            public IntPtr activeProcessorMask;
            public uint numberOfProcessors;
            public uint processorType;
            public uint allocationGranularity;
            public ushort processorLevel;
            public ushort processorRevision;
        }
        /// <summary>
        /// 获取系统的分配粒度
        /// </summary>
        /// <returns></returns>
        public static uint GetPartitionsize()
        {
            SYSTEM_INFO sysInfo;
            GetSystemInfo( out sysInfo );
            return sysInfo.allocationGranularity;
        }

        const int ERROR_ALREADY_EXISTS = 183;

        const int FILE_MAP_COPY = 0x0001;
        const int FILE_MAP_WRITE = 0x0002;
        const int FILE_MAP_READ = 0x0004;
        const int FILE_MAP_ALL_ACCESS = 0x0002 | 0x0004;

        const int PAGE_READONLY = 0x02;
        const int PAGE_READWRITE = 0x04;
        const int PAGE_WRITECOPY = 0x08;
        const int PAGE_EXECUTE = 0x10;
        const int PAGE_EXECUTE_READ = 0x20;
        const int PAGE_EXECUTE_READWRITE = 0x40;

        const int SEC_COMMIT = 0x8000000;
        const int SEC_IMAGE = 0x1000000;
        const int SEC_NOCACHE = 0x10000000;
        const int SEC_RESERVE = 0x4000000;

        IntPtr m_fHandle;

        IntPtr m_hSharedMemoryFile = IntPtr.Zero;
        IntPtr m_pwData = IntPtr.Zero;
        bool m_bAlreadyExist = false;
        bool m_bInit = false;
        uint m_MemSize = 0x1400000;//20M
        long m_offsetBegin = 0;
        long m_FileSize = 0;
        FileReader File = new FileReader();

        /// <summary>
        ///  初始化文件
        /// </summary>
        /// <param name="MemSize">缓冲大小</param>
        public ShareMemory( string filename, uint memSize )
        {
            // 分页映射文件时,每页的起始位置startpos,必须为64K的整数倍。
            // memSize即缓存区的大小必须是系统分配粒度的整倍说,window系统的分配粒度是64KB
            this.m_MemSize = memSize;
            Init( filename );
        }

        /// <summary>
        /// 默认映射20M缓冲
        /// </summary>
        /// <param name="filename"></param>
        public ShareMemory( string filename )
        {
            this.m_MemSize = 0x1400000;
            Init( filename );
        }

        ~ShareMemory()
        {
            Close();
        }

        /// <summary>
        /// 初始化共享内存
        ///
        /// 共享内存名称
        /// 共享内存大小
        /// </summary>
        /// <param name="strName"></param>
        protected void Init( string strName )
        {
            //if (lngSize <= 0 || lngSize > 0x00800000) lngSize = 0x00800000;

            if ( !System.IO.File.Exists( strName ) ) throw new Exception( "未找到文件" );

            System.IO.FileInfo f = new System.IO.FileInfo( strName );

            m_FileSize = f.Length;

            m_fHandle = File.Open( strName );

            if ( strName.Length > 0 )
            {
                //创建文件映射
                m_hSharedMemoryFile = CreateFileMapping( m_fHandle, IntPtr.Zero, ( uint )PAGE_READONLY, 0, ( uint )m_FileSize, "mdata" );
                if ( m_hSharedMemoryFile == IntPtr.Zero )
                {
                    m_bAlreadyExist = false;
                    m_bInit = false;
                    throw new Exception( "CreateFileMapping失败LastError=" + GetLastError().ToString() );
                }
                else
                    m_bInit = true;

                ////映射第一块文件
                //m_pwData = MapViewOfFile(m_hSharedMemoryFile, FILE_MAP_READ, 0, 0, (uint)m_MemSize);
                //if (m_pwData == IntPtr.Zero)
                //{
                //    m_bInit = false;
                //    throw new Exception("m_hSharedMemoryFile失败LastError=" + GetLastError().ToString());
                //}

            }
        }
        /// <summary>
        /// 获取高32位
        /// </summary>
        /// <param name="intValue"></param>
        /// <returns></returns>
        private static uint GetHighWord( UInt64 intValue )
        {
            return Convert.ToUInt32( intValue >> 32 );
        }
        /// <summary>
        /// 获取低32位
        /// </summary>
        /// <param name="intValue"></param>
        /// <returns></returns>
        private static uint GetLowWord( UInt64 intValue )
        {

            return Convert.ToUInt32( intValue & 0x00000000FFFFFFFF );
        }

        /// <summary>
        /// 获取下一个文件块 块大小为20M
        /// </summary>
        /// <returns>false 表示已经是最后一块文件</returns>
        public uint GetNextblock()
        {
            if ( !this.m_bInit ) throw new Exception( "文件未初始化。" );
            //if ( m_offsetBegin + m_MemSize >= m_FileSize ) return false;

            uint m_Size = GetMemberSize();
            if ( m_Size == 0 ) return m_Size;

            // 更改缓冲区大小
            m_MemSize = m_Size;

            //卸载前一个文件
            //bool l_result = UnmapViewOfFile( m_pwData );
            //m_pwData = IntPtr.Zero;

            m_pwData = MapViewOfFile( m_hSharedMemoryFile, FILE_MAP_READ, GetHighWord( ( UInt64 )m_offsetBegin ), GetLowWord( ( UInt64 )m_offsetBegin ), m_Size );
            if ( m_pwData == IntPtr.Zero )
            {
                m_bInit = false;
                throw new Exception( "映射文件块失败" + GetLastError().ToString() );
            }
            m_offsetBegin = m_offsetBegin + m_Size;

            return m_Size; //创建成功
        }
        /// <summary>
        /// 返回映射区大小
        /// </summary>
        /// <returns></returns>
        private uint GetMemberSize()
        {
            if ( m_offsetBegin >= m_FileSize )
            {
                return 0;
            }
            else if ( m_offsetBegin + m_MemSize >= m_FileSize )
            {
                long temp = m_FileSize - m_offsetBegin;
                return ( uint )temp;
            }
            else
                return m_MemSize;
        }

        /// <summary>
        /// 关闭内存映射
        /// </summary>
        public void Close()
        {
            if ( m_bInit )
            {
                UnmapViewOfFile( m_pwData );
                CloseHandle( m_hSharedMemoryFile );
                File.Close();
            }
        }

        /// <summary>
        /// 从当前块中获取数据
        /// </summary>
        /// <param name="bytData">数据</param>
        /// <param name="lngAddr">起始数据</param>
        /// <param name="lngSize">数据长度,最大值=缓冲长度</param>
        /// <param name="Unmap">读取完成是否卸载缓冲区</param>
        /// <returns></returns>
        public void Read( ref byte[] bytData, int lngAddr, int lngSize, bool Unmap )
        {
            if ( lngAddr + lngSize > m_MemSize )
                throw new Exception( "Read操作超出数据区" );
            if ( m_bInit )
            {
                // string bb = Marshal.PtrToStringAuto(m_pwData);//
                Marshal.Copy( m_pwData, bytData, lngAddr, lngSize );
            }
            else
            {
                throw new Exception( "文件未初始化" );
            }

            if ( Unmap )
            {
                bool l_result = UnmapViewOfFile( m_pwData );
                if ( l_result )
                    m_pwData = IntPtr.Zero;
            }
        }

        /// <summary>
        /// 从当前块中获取数据
        /// </summary>
        /// <param name="bytData">数据</param>
        /// <param name="lngAddr">起始数据</param>
        /// <param name="lngSize">数据长度,最大值=缓冲长度</param>
        /// <exception cref="Exception: Read操作超出数据区"></exception>
        /// <exception cref="Exception: 文件未初始化"></exception>
        /// <returns></returns>
        public void Read( ref byte[] bytData, int lngAddr, int lngSize )
        {
            if ( lngAddr + lngSize > m_MemSize )
                throw new Exception( "Read操作超出数据区" );
            if ( m_bInit )
            {
                Marshal.Copy( m_pwData, bytData, lngAddr, lngSize );
            }
            else
            {
                throw new Exception( "文件未初始化" );
            }
        }

        /// <summary>
        /// 从当前块中获取数据
        /// </summary>
        /// <param name="lngAddr">缓存区偏移量</param>
        /// <param name="byteData">数据数组</param>
        /// <param name="StartIndex">数据数组开始复制的下标</param>
        /// <param name="lngSize">数据长度,最大值=缓冲长度</param>
        /// <exception cref="Exception: 起始数据超过缓冲区长度"></exception>
        /// <exception cref="Exception: 文件未初始化"></exception>
        /// <returns>返回实际读取值</returns>
        public uint ReadBytes( int lngAddr, ref byte[] byteData, int StartIndex, uint intSize )
        {
            if ( lngAddr >= m_MemSize )
                throw new Exception( "起始数据超过缓冲区长度" );

            if ( lngAddr + intSize > m_MemSize )
                intSize = m_MemSize - ( uint )lngAddr;

            if ( m_bInit )
            {
                IntPtr s = new IntPtr( ( long )m_pwData + lngAddr ); // 地址偏移
                Marshal.Copy( s, byteData, StartIndex, ( int )intSize );
            }
            else
            {
                throw new Exception( "文件未初始化" );
            }

            return intSize;
        }

        /// <summary>
        /// 写数据
        /// </summary>
        /// <param name="bytData">数据</param>
        /// <param name="lngAddr">起始地址</param>
        /// <param name="lngSize">个数</param>
        /// <returns></returns>
        private int Write( byte[] bytData, int lngAddr, int lngSize )
        {
            if ( lngAddr + lngSize > m_MemSize ) return 2; //超出数据区
            if ( m_bInit )
            {
                Marshal.Copy( bytData, lngAddr, m_pwData, lngSize );
            }
            else
            {
                return 1; //共享内存未初始化
            }
            return 0; //写成功
        }
    }
    internal class FileReader
    {
        const uint GENERIC_READ = 0x80000000;
        const uint OPEN_EXISTING = 3;
        System.IntPtr handle;

        [DllImport( "kernel32", SetLastError = true )]
        public static extern System.IntPtr CreateFile(
            string FileName,          // file name
            uint DesiredAccess,       // access mode
            uint ShareMode,           // share mode
            uint SecurityAttributes,  // Security Attributes
            uint CreationDisposition, // how to create
            uint FlagsAndAttributes,  // file attributes
            int hTemplateFile         // handle to template file
        );

        [System.Runtime.InteropServices.DllImport( "kernel32", SetLastError = true )]
        static extern bool CloseHandle
        (
            System.IntPtr hObject // handle to object
        );

        public IntPtr Open( string FileName )
        {
            // open the existing file for reading
            handle = CreateFile
            (
                FileName,
                GENERIC_READ,
                0,
                0,
                OPEN_EXISTING,
                0,
                0
            );

            if ( handle != System.IntPtr.Zero )
            {
                return handle;
            }
            else
            {
                throw new Exception( "打开文件失败" );
            }
        }

        public bool Close()
        {
            return CloseHandle( handle );
        }
    }
}
时间: 2024-10-15 08:19:15

用C#实现的内存映射的相关文章

JAVA NIO 内存映射(转载)

原文地址:http://blog.csdn.net/fcbayernmunchen/article/details/8635427 Java类库中的NIO包相对于IO 包来说有一个新功能是内存映射文件,日常编程中并不是经常用到,但是在处理大文件时是比较理想的提高效率的手段.本文我主要想结合操作系统中(OS)相关方面的知识介绍一下原理. 在传统的文件IO操作中,我们都是调用操作系统提供的底层标准IO系统调用函数 read().write() ,此时调用此函数的进程(在JAVA中即java进程)由当

Java利用内存映射文件实现按行读取文件

我们知道内存映射文件读取是各种读取方式中速度最快的,但是内存映射文件读取的API里没有提供按行读取的方法,需要自己实现.下面就是我利用内存映射文件实现按行读取文件的方法,如有错误之处请指出,或者有更好更快的实现方式麻烦也提供一下代码. 代码如下: public class testMemoryMappedFile { public static void main(String[] agrs) throws IOException{ RandomAccessFile memoryMappedFi

内存映射文件

一段内存地址空间,映射着物理存储器上一个已经存在于磁盘上的文件.在对该文件进行操作之前必须首先对文件进行映射.使用内存映射文件处理存储于磁盘上的文件时,将不必再对文件执行I/O操作. 内存映射文件,是由一个文件到一块内存的映射.Win32提供了允许应用程序把文件映射到一个进程的函数 (CreateFileMapping). API: HANDLE CreateFileMapping(HANDLE hFile,LPSECURITY_ATTRIBUTES lpFileMappingAttribute

【转】C#大文件读取和查询--内存映射

笔者最近需要快速查询日志文件,文件大小在4G以上. 需求如下: 1.读取4G左右大小的文件中的指定行,程序运行占用内存不超过500M. 2.希望查询1G以内容,能控制在20s左右. 刚开始觉得这个应该不难.研究一天之后,发现这个需要使用内存映射技术. 查阅了相关资料之后 https://msdn.microsoft.com/zh-cn/library/dd997372(v=vs.110).aspx?cs-save-lang=1&cs-lang=csharp#code-snippet-1 发现还是

20150222 IO端口映射和IO内存映射(详解S3C24XX_GPIO驱动)

20150222 IO端口映射和IO内存映射(详解S3C24XX_GPIO驱动) 2015-02-22 李海沿 刚刚我们实现了linux系统内存的分配,读写,释放功能,下面,我们一鼓作气将IO端口映射及IO内存映射搞定,加油! (一)地址的概念 1)物理地址:CPU地址总线传来的地址,由硬件电路控制其具体含义.物理地址中很大一部分是留给内存条中的内存的,但也常被映射到其他存储器上(如显存.BIOS等).在程序指令中的虚拟地址经过段映射和页面映射后,就生成了物理地址,这个物理地址被放到CPU的地址

Linux下C编程-----IO/文件操作/内存映射 实现简单记录存储(3)

利用linux下的文件内存映射可以实现进程共享数据,我们可以把一个文件映射到虚拟内存中使多个进程进行共享, 到这里我们大概能想到他能应用到的领域 是很广泛的 主要涉及到 mmap  munmap   msync 三个函数的应用 下面贴代码 下面一段代码是为文件建立一个简单的记录存储,并且通过内存映射修改文件内容 /************************************************************************* > File Name: memdb

Java使用内存映射实现大文件的上传

在处理大文件时,如果利用普通的FileInputStream 或者FileOutputStream 抑或RandomAccessFile 来进行频繁的读写操作,都将导致进程因频繁读写外存而降低速度.如下为一个对比实验. package test; import java.io.BufferedInputStream; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOExc

第二十天:mmap内存映射

可以说,一天的时间都在了解内存映射mmap这个函数,冯诺依曼结构中表示运算器不能直接对硬盘上的文件进行操作.mmap函数的功能就是将文件映射到某一段内存中,然后操作内存就相当与操作文件.这样的话对文件操作更加方便.mamp函数的定义如下:void *mmap(void *start,size_t length,int prot,int flags,int fd,off_t offsize);一共有六个参数,参数算是比较多了.第一个表示映射内存的起始地址,如果为NULL,那么操作系统会自动的找到空

inux内存映射和共享内存理解和区别

可以看到内存映射中需要的一个参数是int fd(文件的标识符),可见函数是通过fd将文件内容映射到一个内存空间, 我需要创建另一个映射来得到文件内容并统计或修改,这时我创建这另一个映射用的仍是mmap函数, 它仍需要用到fd这个文件标识,那我不等于又重新打开文件读取文件里的数据 1.既然这样那同对文件的直接操作有什么区别呢? 2.映射到内存后通过映射的指针addr来修改内容的话是修改共享内存里的内容还是文件的内容呢? 3.解决上面2个问题,我还是想确切知道共享内存有什么用??? 一种回答|: 1

随机访问文件RandomAccessFile 与 内存映射文件MappedByteBuffer

一.RandomAccessFile RandomAccessFile是用来访问那些保存数据记录的文件的,你就可以用seek( )方法来访问记录,并进行读写了.这些记录的大小不必相同:但是其大小和位置必须是可知的.但是该类仅限于操作文件. RandomAccessFile不属于InputStream和OutputStream类系的.实际上,除了实现DataInput和DataOutput接口之外(DataInputStream和DataOutputStream也实现了这两个接口),它和这两个类系