热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

全面介绍Windows内存管理机制及C++内存分配实例(四):内存映射文件

本文旨在全面介绍Windows内存管理机制及C++内存分配实例中的内存映射文件。通过对内存映射文件的使用场合和与虚拟内存的区别进行解析,帮助读者更好地理解操作系统的内存管理机制。同时,本文还提供了相关章节的链接,方便读者深入学习Windows内存管理及C++内存分配实例的其他内容。

本文背景:

在编程中,很多Windows或C++的内存函数不知道有什么区别,更别谈有效使用;根本的原因是,没有清楚的理解操作系统的内存管理机制,本文企图通过简单的总结描述,结合实例来阐明这个机制。

本文目的:

对Windows内存管理机制了解清楚,有效的利用C++内存函数管理和使用内存。

本文内容:

本文一共有六节,由于篇幅较多,故按节发表。其他章节请看本人博客的Windows内存管理及C++内存分配实例(一)(二)(三)(五)和(六)。

4.      内存管理机制--内存映射文件 (Map)

    和虚拟内存一样,内存映射文件可以用来保留一个进程地址区域;但是,与虚拟内存不同,它提交的不是物理内存或是虚拟页文件,而是硬盘上的文件。

·        使用场合

它有三个主要用途:

系统加载EXE和DLL文件

操作系统就是用它来加载exe和dll文件建立进程,运行exe。这样可以节省页文件和启动时间。

访问大数据文件

如果文件太大,比如超过了进程用户区2G,用fopen是不能对文件进行操作的。这时,可用内存映射文件。对于大数据文件可以不必对文件执行I/O操作,不必对所有文件内容进行缓存。

进程共享机制

内存映射文件是多个进程共享数据的一种较高性能的有效方式,它也是操作系统进程通信机制的底层实现方法。RPC、COM、OLE、DDE、窗口消息、剪贴板、管道、Socket等都是使用内存映射文件实现的。

·        系统加载EXE和DLL文件

ü      EXE文件格式

每个EXE和DLL文件由许多节(Section)组成,每个节都有保护属性:READ,WRITE,EXECUTE和SHARED(可以被多个进程共享,关闭页面的COPY-ON-WRITE属性)。

以下是常见的节和作用:


节名

作用

.text

.exe和.dll文件的代码

.data

已经初始化的数据

.bss

未初始化的数据

.reloc

重定位表(装载进程的进程地址空间)

.rdata

运行期只读数据

.CRT

C运行期只读数据

.debug

调试信息

.xdata

异常处理表

.tls

线程的本地化存储

.idata

输入文件名表

.edata

输出文件名表

.rsrc

资源表

.didata

延迟输入文件名表


 

ü      加载过程

1.      系统根据exe文件名建立进程内核对象、页目和页表,也就是建立了进程的虚拟空间。

2.      读取exe文件的大小,在默认基地址0x0040 0000上保留适当大小的区域。可以在链接程序时用/BASE 选项更改基地址(在VC工程属性/链接器/高级上设置)。提交时,操作系统会管理页目和页表,将硬盘上的文件映射到进程空间中,页表中保存的地址是exe文件的页偏移。

3.      读取exe文件的.idata节,此节列出exe所用到的所有dll文件。然后和

exe文件一样,将dll文件映射到进程空间中。如果无法映射到基地址,系统会重新定位。

4.   映射成功后,系统会把第一页代码加载到内存,然后更新页目和页

表。将第一条指令的地址交给线程指令指针。当系统执行时,发现代码没有在内存中,会将exe文件中的代码加载到内存中。

              

ü      第二次加载时(运行多个进程实例)

1.      建立进程、映射进程空间都跟前面一样,只是当系统发现这个exe已

      经建立了内存映射文件对象时,它就直接映射到进程空间了;只是当

     系统分配物理页面时,根据节的保护属性赋予页面保护属性,对于代码

     节赋予READ属性,全局变量节赋予COPY-ON-WRITE属性。

2.      不同的实例共享代码节和其他的节,当实例需要改变页面内容时,会

      拷贝页面内容到新页面,更新页目和页表。

3.      对于不同进程实例需要共享的变量,exe文件有一

      个默认的节, 给这个节赋予SHARED属性。

4.      你也可以创建自己的SHARED节

#pragma data_seg(“节名”)

Long instCount;

#pragma data_seg()

然后,你需要在链接程序时告诉编译器节的默认属性。

/SECTION: 节名,RWS

或者,在程序里用以下表达式:

#pragma comment(linker,“/SECTION:节名,RWS”)

这样的话编译器会创建.drective节来保存上述命令,然后链接时会用它改变节属性。

注意,共享变量有可能有安全隐患,因为它可以读到其他进程的数据。

C++程序:多个进程共享变量举例

*.cpp开始处:

#pragma data_seg(".share")

long shareCount=0;

#pragma data_seg()

#pragma comment(linker,"/SECTION:.share,RWS")

ShareCount++;

 

注意,同一个exe文件产生的进程会共享shareCount,必须是处于同一个位置上exe

 

·        访问大数据文件

ü      创建文件内核对象

使用CreateFile(文件名,访问属性,共享模式,…) API可以创建。

其中,访问属性有:

0 不能读写 (用它可以访问文件属性)

GENERIC_READ

GENERIC_WRITE

GENERIC_READ|GENERIC_WRITE;

共享模式:

0 独享文件,其他应用程序无法打开

FILE_SHARE_WRITE

FILE_SHARE_READ|FILE_SHARE_WRITE

这个属性依赖于访问属性,必须和访问属性不冲突。

当创建失败时,返回INVALID_HANDLE_VALUE。

 

C++程序如下:

试图打开一个1G的文件:

MEMORYSTATUS memStatus;

GlobalMemoryStatus(&memStatus);

HANDLE hn=CreateFile(L"D://1G.rmvb",GENERIC_READ|GENERIC_WRITE,

FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);

              if(hn==INVALID_HANDLE_VALUE)

                        cout<<"打开文件失败!"<

              FILE *p&#61;fopen("D://1G.rmvb","rb");

              if(p&#61;&#61;NULL)

                        cout<<"用fopen不能打开大文件!"<

              MEMORYSTATUS memStatus2;

              GlobalMemoryStatus(&memStatus2);

              cout<<"打开文件后的空间&#xff1a;"<

cout<<"减少物理内存&#61;"<

cout<<"减少可用页文件&#61;"<

cout<<"减少可用进程空间&#61;"

<

结果如下&#xff1a;

 

可见&#xff0c;系统需要一些内存来管理内核对象&#xff0c;每一次运行的结果都不一样&#xff0c;但差别不会太大。

用c语言的fopen不能打开这么大的文件。理论上&#xff0c;32位系统能支持232字节&#xff0c;但是&#xff0c;进程空间只有2G&#xff0c;它只能表示那么大的空间。

ü      创建文件映射内核对象

API如下&#xff1a;

HANDLE CreateFileMapping(Handle 文件&#xff0c;PSECURITY_ATTRIBUTES 安全属性&#xff0c;DWORD 保护属性&#xff0c;DWORD 文件大小高32位&#xff0c;DWORD 文件大小低32位&#xff0c;PCTSTR  映射名称)

“文件”是上面创建的句柄&#xff1b;

“安全属性”是内核对象需要的&#xff0c;NULL表示使用系统默认的安全属性&#xff1b;“保护属性”是当将存储器提交给进程空间时&#xff0c;需要的页面属性&#xff1a;PAGE_READONLY, PAGE_READWRITE和PAGE_WRITECOPY。这个属性不能和文件对象的访问属性冲突。除了这三个外&#xff0c;还有两个属性可以和它们连接使用(|)。当更新文件内容时&#xff0c;不提供缓存&#xff0c;直接写入文件&#xff0c;可用SEC_NOCACHE&#xff1b;当文件是可执行文件时&#xff0c;系统会根据节赋予不同的页面属性&#xff0c;可用SEC_IMAGE。另外&#xff0c;SEC_RESERVE和SEC_COMMIT用于稀疏提交的文件映射&#xff0c;详细介绍请参考下文。

“文件大小高32位”和“文件大小低32位”联合起来告诉系统&#xff0c;这个映射所能支持的文件大小&#xff08;操作系统支持264B文件大小&#xff09;&#xff1b;当这个值大于实际的文件大小时&#xff0c;系统会扩大文件到这个值&#xff0c;因为系统需要保证进程空间能完全被映射。值为0默认为文件的大小&#xff0c;这时候如果文件大小为0&#xff0c;创建失败。

“映射名称”是给用户标识此内核对象&#xff0c;供各进程共享&#xff0c;如果为NULL&#xff0c;则不能共享。

对象创建失败时返回NULL。

创建成功后&#xff0c;系统仍未为文件保留进程空间。

 

C&#43;&#43;程序&#xff1a;

                        MEMORYSTATUS memStatus2;

                        GlobalMemoryStatus(&memStatus2);

HANDLE hmap&#61;CreateFileMapping(hn,NULL,PAGE_READWRITE,0,0,L"Yeming-Map");

                        if(hmap&#61;&#61;NULL)

                        cout<<"建立内存映射对象失败!"<

                        MEMORYSTATUS memStatus3;

                        GlobalMemoryStatus(&memStatus3);

                        cout<<"建立内存映射文件后的空间&#xff1a;"<

cout<<"减少物理内存&#61;"<

cout<<"减少可用页文件&#61;"<

         cout<<"减少可用进程空间&#61;"

<

            结果如下&#xff1a;

      

 

默认内存映射的大小是1G文件。没有损失内存和进程空间。它所做的是建立内核对象&#xff0c;收集一些属性。

 

ü      文件映射内核对象映射到进程空间

API如下&#xff1a;

PVOID MAPViewOfFile(HANDLE 映射对象&#xff0c;DWORD访问属性&#xff0c;DWORD 偏移量高32位&#xff0c;DWORD 偏移量低32位&#xff0c;SIZE_T 字节数)

“映射对象”是前面建立的对象&#xff1b;

“访问属性”可以是下面的值&#xff1a;FILE_MAP_WRITE(读和写)、FILE_MAP_READ、FILE_MAP_ALL_ACCESS(读和写)、FILE_MAP_COPY。当使用FILE_MAP_COPY时&#xff0c;系统分配虚拟页文件&#xff0c;当有写操作时&#xff0c;系统会拷贝数据到这些页面&#xff0c;并赋予PAGE_READWRITE属性。

可以看到&#xff0c;每一步都需要设置这类属性&#xff0c;是为了可以多点控制&#xff0c;试想&#xff0c;如果在这一步想有多种不同的属性操作文件的不同部分&#xff0c;就比较有用。

“偏移高32位”和“偏移低32位”联合起来标识映射的开始字节&#xff08;地址是分配粒度的倍数&#xff09;&#xff1b;

“字节数”指映射的字节数&#xff0c;默认0为到文件尾。

 

当你需要指定映射到哪里时&#xff0c;你可以使用&#xff1a;

PVOID MAPViewOfFile(HANDLE 映射对象&#xff0c;DWORD访问属性&#xff0c;DWORD 偏移量高32位&#xff0c;DWORD 偏移量低32位&#xff0c;SIZE_T 字节数&#xff0c;PVOID 基地址)

“基地址”是映射到进程空间的首地址&#xff0c;必须是分配粒度的倍数。

 

C&#43;&#43;程序&#xff1a;

MEMORYSTATUS memStatus3;

            GlobalMemoryStatus(&memStatus3);

            LPVOID pMAP&#61;MapViewOfFile(hmap,FILE_MAP_WRITE,0,0,0);

            cout<<"映射内存映射文件后的空间&#xff1a;"<

if(pMAP&#61;&#61;NULL)

               cout<<"映射进程空间失败!"<

            else

               printf("首地址&#61;%x/n",pMAP);

            MEMORYSTATUS memStatus4;

            GlobalMemoryStatus(&memStatus4);

cout<<"减少物理内存&#61;"<

cout<<"减少可用页文件&#61;"<

cout<<"减少可用进程空间&#61;"

<

结果如下&#xff1a;

 

进程空间减少了1G&#xff0c;系统同时会开辟一些内存来做文件缓存。

ü      使用文件

1.      对于大文件&#xff0c;可以用多次映射的方法达到访问的目的。有点像AWE技术。

2.      Windows只保证同一文件映射内核对象的多次映射的数据一致性&#xff0c;比如&#xff0c;当有两次映射同一对象到二个进程空间时&#xff0c;一个进程空间的数据改变后&#xff0c;另一个进程空间的数据也会跟着改变&#xff1b;不保证不同映射内核对象的多次映射的一致性。所以&#xff0c;使用文件映射时&#xff0c;最好在CreateFile时将共享模型设置为0独享&#xff0c;当然&#xff0c;对于只读文件没这个必要。

    C&#43;&#43;程序&#xff1a;使用1G的文件

MEMORYSTATUS memStatus4;

                        GlobalMemoryStatus(&memStatus4);

                        cout<<"读取1G文件前&#xff1a;"<

                        cout<<"可用物理内存&#61;"<

                        cout<<"可用页文件&#61;"<

                        cout<<"可用进程空间&#61;"<

                        int* pInt&#61;(int*)pMAP;

                        cout<<"更改前&#61;"<

                        for(int i&#61;0;i<1000001536/4-1;i&#43;&#43;)

                             pInt[i]&#43;&#43;;

                        pInt[1000001536/4-1]&#61;10;

                        pInt[100]&#61;90;

                        pInt[101]&#61;100;

                        cout<<"读取1G文件后&#xff1a;"<

                        MEMORYSTATUS memStatus5;

                        GlobalMemoryStatus(&memStatus5);

                        cout<<"可用物理内存&#61;"<

                        cout<<"可用页文件&#61;"<

                        cout<<"可用进程空间&#61;"<

           

结果如下&#xff1a;

 

程序将1G文件的各个整型数据加1&#xff0c;从上图看出内存损失了600多兆&#xff0c;但有时候损失不过十几兆&#xff0c;可能跟系统当时的状态有关。

不管怎样&#xff0c;这样你完全看不到I/O操作&#xff0c;就像访问普通数据结构一样方便。

 

ü      保存文件修改

为了提高速度&#xff0c;更改文件时可能只更改到了系统缓存&#xff0c;这时&#xff0c;需要强制保存更改到硬盘&#xff0c;特别是撤销映射前。

BOOL FlushViewOfFile(PVOID 进程空间地址&#xff0c;SIZE_T 字节数)

“进程空间地址”指的是需要更改的第一个字节地址&#xff0c;系统会变成页面的地址&#xff1b;

“字节数”&#xff0c;系统会变成页面大小的倍数。

写入磁盘后&#xff0c;函数返回&#xff0c;对于网络硬盘&#xff0c;如果希望写入网络硬盘后才返回的话&#xff0c;需要将FILE_FLAG_WRITE_THROUGH参数传给CreateFile。

 

当使用FILE_MAP_COPY建立映射时&#xff0c;由于对数据的更改只是对虚拟页文件的修改而不是硬盘文件的修改&#xff0c;当撤销映射时&#xff0c;会丢失所做的修改。如果要保存&#xff0c;怎么办&#xff1f;

你可以用FILE_MAP_WRITE建立另外一个映射&#xff0c;它映射到进程的另外一段空间&#xff1b;扫描第一个映射的PAGE_READWRITE页面(因为属性被更改)&#xff0c;如果页面改变&#xff0c;用MoveMemory或其他拷贝函数将页面内容拷贝到第二次映射的空间里&#xff0c;然后再调用FlushViewOfFile。当然&#xff0c;你要记录哪个页面被更改。

ü      撤销映射

用以下API可以撤销映射&#xff1a;

BOOL  UnmapViewOfFile(PVOID pvBaseAddress)

这个地址必须与MapViewOfFile返回值相同。

 

ü      关闭内核对象

在不需要内核对象时&#xff0c;尽早将其释放&#xff0c;防止内存泄露。由于它们是内核对象&#xff0c;调用CloseHandle(HANDLE)就可以了。

在CreateFileMapping后马上关闭文件句柄&#xff1b;

在MapViewOfFile后马上关闭内存映射句柄&#xff1b;

最后再撤销映射。

·        进程共享机制

ü      基于硬盘文件的内存映射

如果进程需要共享文件&#xff0c;只要按照前面的方式建立内存映射对象&#xff0c;然后按照名字来共享&#xff0c;那么进程就可以映射这个对象到自己的进程空间中。

C&#43;&#43;程序如下&#xff1a;

HANDLE mapYeming&#61;OpenFileMapping(FILE_MAP_WRITE,true,L"Yeming-Map");

                        if(mapYeming&#61;&#61;NULL)

                        cout<<"找不到内存映射对象:Yeming-Map!"<

                        MEMORYSTATUS memStatus3;

                        GlobalMemoryStatus(&memStatus3);

LPVOID pMAP&#61;MapViewOfFile(mapYeming,FILE_MAP_WRITE,0,0,100000000);

                        cout<<"建立内存映射文件后的空间&#xff1a;"<

                        if(pMAP&#61;&#61;NULL)

                        cout<<"映射进程空间失败!"<

                        else

                        printf("首地址&#61;%x/n",pMAP);

           

                        MEMORYSTATUS memStatus4;

                        GlobalMemoryStatus(&memStatus4);

           

cout<<"减少物理内存&#61;"<

cout<<"减少可用页文件&#61;"<

cout<<"减少可用进程空间&#61;"<

 

                        int* pInt&#61;(int*)pMAP;

         cout<

        

         结果如下&#xff1a;

 

在2.exe中打开之前1.exe创建的内存映射对象(当然&#xff0c;1.exe得处于运行状态)&#xff0c;然后映射进自己的进程空间&#xff0c;当1.exe改变文件的值时&#xff0c;2.exe的文件对应值也跟着改变&#xff0c;Windows保证同一个内存映射对象映射出来的数据是一致的。可以看见&#xff0c;1.exe将值从90改为91&#xff0c;2.exe也跟着改变&#xff0c;因为它们有共同的缓冲页。

 

ü      基于页文件的内存映射

如果只想共享内存数据时&#xff0c;没有必要创建硬盘文件&#xff0c;再建立映射。可以直

接建立映射对象&#xff1a;

只要传给CreateFileMapping一个文件句柄INVALID_HANDLE_VALUE就行了。所以&#xff0c;CreateFile时&#xff0c;一定要检查返回值&#xff0c;否则会建立一个基于页文件的内存映射对象。接下来就是映射到进程空间了&#xff0c;这时&#xff0c;系统会分配页文件给它。

C&#43;&#43;程序如下&#xff1a;

 

HANDLE hPageMap&#61;CreateFileMapping(INVALID_HANDLE_VALUE,NULL,PAGE_READWRITE,0,

100000000,L"Yeming-Map-Page");

            if(hPageMap&#61;&#61;NULL)

                        cout<<"建立基于页文件的内存映射对象失败!"<

            MEMORYSTATUS memStatus6;

            GlobalMemoryStatus(&memStatus6);

            cout<<"建立基于页文件的内存映射文件后的空间&#xff1a;"<

cout<<"减少物理内存&#61;"<

cout<<"减少可用页文件&#61;"<

cout<<"减少可用进程空间&#61;"<

LPVOID pPageMAP&#61;MapViewOfFile(hPageMap,FILE_MAP_WRITE,0,0,0);        

            结果如下&#xff1a;

       

 

可见&#xff0c;和基于数据文件的内存映射不同&#xff0c;现在刚建立内核对象时就分配了所要的100M内存。好处是&#xff0c;别的进程可以通过这个内核对象共享这段内存&#xff0c;只要它也做了映射。

 

ü      稀疏内存映射文件

在虚拟内存一节中&#xff0c;提到了电子表格程序。虚拟内存解决了表示很少单元格有数据但必须分配所有内存的内存浪费问题&#xff1b;但是&#xff0c;如果想在多个进程之间共享这个电子表格结构呢&#xff1f;

如果用基于页文件的内存映射&#xff0c;需要先分配页文件&#xff0c;还是浪费了空间&#xff0c;没有了虚拟内存的优点。

Windows提供了稀疏提交的内存映射机制。

当使用CreateFileMapping时&#xff0c;保护属性用SEC_RESERVE时&#xff0c;其不提交物理存储器&#xff0c;使用SEC_COMMIT时&#xff0c;其马上提交物理存储器。注意&#xff0c;只有文件句柄为INVALID_HANDLE_VALUE时&#xff0c;才能使用这两个参数。

按照通常的方法映射时&#xff0c;系统只保留进程地址空间&#xff0c;不会提交物理存储器。

当需要提交物理内存时才提交&#xff0c;利用通常的VirtualAlloc函数就可以提交。

当释放内存时&#xff0c;不能调用VirtualFree函数&#xff0c;只能调用UnmapViewOfFile来撤销映射&#xff0c;从而释放内存。

 

C&#43;&#43;程序如下&#xff1a;

HANDLE hVirtualMap&#61;CreateFileMapping(INVALID_HANDLE_VALUE,NULL,PAGE_READWRITE|SEC_RESERVE,0,100000000,L"Yeming-Map-Virtual");

if(hPageMap&#61;&#61;NULL)

                        cout<<"建立基于页文件的稀疏内存映射对象失败!"<

            MEMORYSTATUS memStatus8;

            GlobalMemoryStatus(&memStatus8);

            cout<<"建立基于页文件的稀疏内存映射文件后的空间&#xff1a;"<

cout<<"减少物理内存&#61;"<

cout<<"减少可用页文件&#61;"<

cout<<"减少可用进程空间&#61;"<

           

LPVOID pVirtualMAP&#61;MapViewOfFile(hVirtualMap,FILE_MAP_WRITE,0,0,0);

            cout<<"内存映射进程后的空间&#xff1a;"<

            if(pVirtualMAP&#61;&#61;NULL)

                        cout<<"映射进程空间失败!"<

            else

                        printf("首地址&#61;%x/n",pVirtualMAP);

           

            MEMORYSTATUS memStatus9;

            GlobalMemoryStatus(&memStatus9);

           

cout<<"减少物理内存&#61;"<

cout<<"减少可用页文件&#61;"<

cout<<"减少可用进程空间&#61;"<

        

结果如下&#xff1a;

 

用了SEC_RESERVE后&#xff0c;只是建立了一个内存映射对象&#xff0c;和普通的一样&#xff1b;不同的是&#xff0c;它映射完后&#xff0c;得到了一个虚拟进程空间。现在&#xff0c;这个空间没有分配任何的物理存储器给它&#xff0c;你可以用VirtualAlloc 提交存储器给它&#xff0c;详细请参考上一篇<虚拟内存&#xff08;VM&#xff09;>。

注意&#xff0c;你不可以用VirtualFree来释放了,只能用UnmapViewOfFile来。

C&#43;&#43;程序如下&#xff1a;

LPVOID pP&#61;VirtualAlloc(pVirtualMAP,100*1000*1000,MEM_COMMIT,PAGE_READWRITE); 

            MEMORYSTATUS memStatus10;

            GlobalMemoryStatus(&memStatus10);

           

cout<<"减少物理内存&#61;"<

cout<<"减少可用页文件&#61;"<

cout<<"减少可用进程空间&#61;"<

 

            bool result&#61;VirtualFree(pP,100000000,MEM_DECOMMIT);

            if(!result)

                        cout<<"释放失败!"<

             result&#61;VirtualFree(pP,100000000,MEM_RELEASE);

            if(!result)

                        cout<<"释放失败!"<

 

            CloseHandle(hVirtualMap);

            MEMORYSTATUS memStatus11;

            GlobalMemoryStatus(&memStatus11);

cout<<"增加物理内存&#61;"<

cout<<"增加可用页文件&#61;"<

cout<<"增加可用进程空间&#61;"<

 

            result&#61;UnmapViewOfFile(pVirtualMAP);

            if(!result)

                        cout<<"撤销映射失败!"<

 

            MEMORYSTATUS memStatus12;

            GlobalMemoryStatus(&memStatus12);

cout<<"增加物理内存&#61;"<

cout<<"增加可用页文件&#61;"<

cout<<"增加可用进程空间&#61;"

<

结果如下&#xff1a;

 

可以看见&#xff0c;用VirtualFree是不能够释放这个稀疏映射的&#xff1b;最后用UnmapViewOfFile得以释放进程空间和物理内存。

 


推荐阅读
  • Android Studio Bumblebee | 2021.1.1(大黄蜂版本使用介绍)
    本文介绍了Android Studio Bumblebee | 2021.1.1(大黄蜂版本)的使用方法和相关知识,包括Gradle的介绍、设备管理器的配置、无线调试、新版本问题等内容。同时还提供了更新版本的下载地址和启动页面截图。 ... [详细]
  • 本文介绍了使用PHP实现断点续传乱序合并文件的方法和源码。由于网络原因,文件需要分割成多个部分发送,因此无法按顺序接收。文章中提供了merge2.php的源码,通过使用shuffle函数打乱文件读取顺序,实现了乱序合并文件的功能。同时,还介绍了filesize、glob、unlink、fopen等相关函数的使用。阅读本文可以了解如何使用PHP实现断点续传乱序合并文件的具体步骤。 ... [详细]
  • 本文介绍了Hyperledger Fabric外部链码构建与运行的相关知识,包括在Hyperledger Fabric 2.0版本之前链码构建和运行的困难性,外部构建模式的实现原理以及外部构建和运行API的使用方法。通过本文的介绍,读者可以了解到如何利用外部构建和运行的方式来实现链码的构建和运行,并且不再受限于特定的语言和部署环境。 ... [详细]
  • Voicewo在线语音识别转换jQuery插件的特点和示例
    本文介绍了一款名为Voicewo的在线语音识别转换jQuery插件,该插件具有快速、架构、风格、扩展和兼容等特点,适合在互联网应用中使用。同时还提供了一个快速示例供开发人员参考。 ... [详细]
  • 本文介绍了在Windows环境下如何配置php+apache环境,包括下载php7和apache2.4、安装vc2015运行时环境、启动php7和apache2.4等步骤。希望对需要搭建php7环境的读者有一定的参考价值。摘要长度为169字。 ... [详细]
  • 本文介绍了win7系统休眠功能无法启动和关闭的解决方法,包括在控制面板中启用休眠功能、设置系统休眠的时间、通过命令行定时休眠、手动进入休眠状态等方法。 ... [详细]
  • 如何用JNI技术调用Java接口以及提高Java性能的详解
    本文介绍了如何使用JNI技术调用Java接口,并详细解析了如何通过JNI技术提高Java的性能。同时还讨论了JNI调用Java的private方法、Java开发中使用JNI技术的情况以及使用Java的JNI技术调用C++时的运行效率问题。文章还介绍了JNIEnv类型的使用方法,包括创建Java对象、调用Java对象的方法、获取Java对象的属性等操作。 ... [详细]
  • 恶意软件分析的最佳编程语言及其应用
    本文介绍了学习恶意软件分析和逆向工程领域时最适合的编程语言,并重点讨论了Python的优点。Python是一种解释型、多用途的语言,具有可读性高、可快速开发、易于学习的特点。作者分享了在本地恶意软件分析中使用Python的经验,包括快速复制恶意软件组件以更好地理解其工作。此外,作者还提到了Python的跨平台优势,使得在不同操作系统上运行代码变得更加方便。 ... [详细]
  • ejava,刘聪dejava
    本文目录一览:1、什么是Java?2、java ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • ASP.NET2.0数据教程之十四:使用FormView的模板
    本文介绍了在ASP.NET 2.0中使用FormView控件来实现自定义的显示外观,与GridView和DetailsView不同,FormView使用模板来呈现,可以实现不规则的外观呈现。同时还介绍了TemplateField的用法和FormView与DetailsView的区别。 ... [详细]
  • 本文介绍了连接库的定义和使用方法。连接库是通过编译生成的dll文件,例如php_mysql.dll。在使用扩展时,需要去掉配置文件中的分号,并通过phpinfo查看是否正确加载了mysql连接库。详细内容请参考链接:https://www.cnblogs.com/xiaobiaomei/p/7654750.html。摘要字数:180字。 ... [详细]
  • Python中sys模块的功能及用法详解
    本文详细介绍了Python中sys模块的功能及用法,包括对解释器参数和功能的访问、命令行参数列表、字节顺序指示符、编译模块名称等。同时还介绍了sys模块中的新功能和call_tracing函数的用法。推荐学习《Python教程》以深入了解。 ... [详细]
  • PHP5.5在Windows下安装memcached的方法下载服务端资源
    本文介绍了在Windows下安装PHP5.5的memcached的方法,包括下载服务端资源、解压、注册、卸载、启动和停止等步骤,并提供了相关的学习推荐。同时还提供了下载PHP在Windows下的php_memcache.dll文件的链接。 ... [详细]
  • 在加载一个第三方厂商的dll文件时,提示“找不到指定模块,加载失败”。由于缺乏必要的技术支持,百思不得期间。后来发现一个有用的工具 ... [详细]
author-avatar
雨爱艳6688
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有