热门标签 | 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得以释放进程空间和物理内存。

 


推荐阅读
  • 阿里云ecs怎么配置php环境,阿里云ecs配置选择 ... [详细]
  • 本文详细介绍了 Visual FoxPro 中 GETCOLOR() 函数的功能及其使用方法,帮助开发者更好地理解和应用该函数。 ... [详细]
  • 通过Web界面管理Linux日志的解决方案
    本指南介绍了一种利用rsyslog、MariaDB和LogAnalyzer搭建集中式日志管理平台的方法,使用户可以通过Web界面查看和分析Linux系统的日志记录。此方案不仅适用于服务器环境,还提供了详细的步骤来确保系统的稳定性和安全性。 ... [详细]
  • 深入理解一致性哈希算法及其应用
    本文详细介绍了分布式系统中的一致性哈希算法,探讨其原理、优势及应用场景,帮助读者全面掌握这一关键技术。 ... [详细]
  • 本文详细介绍了网络存储技术的基本概念、分类及应用场景。通过分析直连式存储(DAS)、网络附加存储(NAS)和存储区域网络(SAN)的特点,帮助读者理解不同存储方式的优势与局限性。 ... [详细]
  • 本文介绍了如何在C#应用程序中有效隐藏SQLCMD命令行窗口,确保程序运行时不会弹出黑色命令提示符窗口。 ... [详细]
  • 如何使用PyCharm及常用配置详解
    对于一枚pycharm工具的使用新手,正确了解这门工具的配置及其使用,在使用过程中遇到的很多问题也可以迎刃而解,文中有非常详细的介绍, ... [详细]
  • 本文探讨了Java编程的核心要素,特别是其面向对象的特性,并详细介绍了Java虚拟机、类装载器体系结构、Java类文件和Java API等关键技术。这些技术使得Java成为一种功能强大且易于使用的编程语言。 ... [详细]
  • 本文深入探讨了 Redis 的两种持久化方式——RDB 快照和 AOF 日志。详细介绍了它们的工作原理、配置方法以及各自的优缺点,帮助读者根据具体需求选择合适的持久化方案。 ... [详细]
  • 本文介绍如何使用 Python 的 xlrd 库读取 Excel 文件,并将其数据处理后存储到数据库中。通过实际案例,详细讲解了文件路径、合并单元格处理等常见问题。 ... [详细]
  • Python 异步编程:ASGI 服务器与框架详解
    自 Python 3.5 引入 async/await 语法以来,异步编程迅速崛起,吸引了大量开发者的关注。本文将深入探讨 ASGI(异步服务器网关接口)及其在现代 Python Web 开发中的应用,介绍主流的 ASGI 服务器和框架。 ... [详细]
  • HBase运维工具全解析
    本文深入探讨了HBase常用的运维工具,详细介绍了每种工具的功能、使用场景及操作示例。对于HBase的开发人员和运维工程师来说,这些工具是日常管理和故障排查的重要手段。 ... [详细]
  • 本文介绍了多个关于JavaScript的书籍资源、实用工具和编程实例,涵盖从入门到进阶的各个阶段,帮助读者全面提升JavaScript编程能力。 ... [详细]
  • FinOps 与 Serverless 的结合:破解云成本难题
    本文探讨了如何通过 FinOps 实践优化 Serverless 应用的成本管理,提出了首个 Serverless 函数总成本估计模型,并分享了多种有效的成本优化策略。 ... [详细]
  • Netflix利用Druid实现高效实时数据分析
    本文探讨了全球领先的在线娱乐公司Netflix如何通过采用Apache Druid,实现了高效的数据采集、处理和实时分析,从而显著提升了用户体验和业务决策的准确性。文章详细介绍了Netflix在系统架构、数据摄取、管理和查询方面的实践,并展示了Druid在大规模数据处理中的卓越性能。 ... [详细]
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社区 版权所有