iOS标准库中经常使用数据结构和算法以内存池

上一篇:iOS标准库中经常使用数据结构和算法之位串linux

⛲️内存池

内存池提供了内存的复用和持久的存储功能。设想一个场景,当你分配了一块大内存而且填写了内容,可是你又不是常常去访问这块内存。这样的内存利用率将不高,并且没法复用。而若是是采用内存池则能够很轻松解决这个问题:你只须要从内存池中申请这块内存,设置完内容后当不须要用时你能够将这块内存放入内存池中,供其余地方在申请时进行复用,而当你再次须要时则只须要从新申请便可。内存池提供了内存分配编号并且设置dirty标志的概念,当你把分配的内存放入内存池并设置dirty标志后,系统就会在适当的时候将这块内存的内容写回到磁盘,这样当你再次根据内存编号来访问内存时,系统就又会从磁盘中将内容读取到内存中去。算法

功能:在iOS中提供了一套内存池管理的API,你能够用这套API来实现上述的功能,并且系统内部不少功能也是借助内存池来实现内存复用和磁盘存储的。缓存

头文件: #include <mpool.h>, #include <db.h>bash

平台: BSD系统,linux系统cookie

1、内存池的建立、同步和关闭

功能:建立和关闭一个内存池对象并和磁盘文件绑定以便进行同步操做。数据结构

函数签名app

//建立一个内存池对象
 MPOOL * mpool_open(void *key, int fd, pgno_t pagesize, pgno_t maxcache);

//将内存池中的脏数据同步写回到磁盘文件中
int mpool_sync(MPOOL *mp);

//关闭和销毁内存池对象。
int mpool_close(MPOOL *mp);

复制代码

参数数据结构和算法

key:[in] 保留字段,暂时没有用处,传递NULL便可。函数

fd:[in] 内存池关联的磁盘文件句柄,文件句柄须要用open函数来打开。post

pagesize:[in] 内存池中每次申请和分配的内存的尺寸大小,单位是字节。

maxcache:[in] 内存池中内存页的最大缓存数量。若是池中缓存的内存数量超过了最大缓存的数量就会复用已经存在的内存,而不是每次都分配新的内存。 return:[out] 返回一个新建立的内存池对象,其余两个函数成功返回0,失败返回非0.

描述

  1. 内存池中的内存的分配和获取是以页为单位进行的,每次分配的页的尺寸大小由pagesize指定,同时内存池也指定了最大的缓存页数量maxcache。每次从内存池中分配一页内存时,除了会返回分配的内存地址外,还会返回这一页内存的编号。这个编号对于内存池中的内存页来讲是惟一的。由于内存池中的内存是能够被复用的,所以就有多是不一样的编号的内存页所获得的内存地址是相同的。

  2. 每个内存池对象都会要和一个文件关联起来,以便可以实现内存数据的永久存储和内存的复用。文件句柄必须用open函数来打开,好比下面的例子:

int fd = open("/Users/apple/mpool", O_RDWR|O_APPEND|O_CREAT,0660);
复制代码
  1. 当咱们不须要使用某个内存页时或者内存页的内容有改动则咱们须要将这个内存页放入回内存池中,并将页标志为dirty标志。这样系统就会在适当的时候将此内存页的数据写回到磁盘文件中,同时此内存页也会在后续被重复利用。

  2. 当咱们想将全部设置为dirty标志的内存页当即写入磁盘时则须要调用mpool_sync函数进行同步处理。

  3. 当咱们再也不须要内存池时,则能够经过mpool_close来关闭内存池对象,须要注意的是关闭内存池并不会将内存中的数据回写到磁盘中去。

2、内存池中内存的获取

功能: 从内存池中申请分配一页新的内存或者获取现有缓存中的内存。

函数签名:

//从内存池中申请分配一页新的内存
void *  mpool_new(MPOOL *mp, pgno_t *pgnoaddr);
//根据内存编号页获取对应的内存。
void * mpool_get(MPOOL *mp, pgno_t pgno, u_int flags);
复制代码

参数:

mp:[in] 内存池对象。

pgnoaddr:[out] 用于mpool_new函数,用于保存新分配的内存页编号。

pngno:[in] 用于mpool_get函数,指定要获取的内存页的编号。

flags:[in] 此参数暂时无用。

return:[out] 返回分配或者获取的内存地址。若是分配或者获取失败则返回NULL。

描述:

  1. 不管是new仍是get每次从内存池里面分配或者获取的内存页的大小都是由上述mpool_open函数中的pagesize参数指定的大小。
  2. 系统内部分配的内存是用calloc函数实现的,可是咱们不须要手动调用free来对内存进行释放处理。
  3. 每一个内存页都有一个惟一的页编号,并且每次分配的页编号也会一直递增下去。
  4. mpool_new函数申请分配新的内存时,若是当前缓存中的内存页小于maxcache数量则老是分配新的内存,只有当缓存数量大于maxcache时才会从现有的缓存中寻找一页能够被重复利用的内存页,若是没有能够重复利用的页面,则会继续分配新的内存页。
  5. mpool_get函数则根据内存页的编号获取对应的内存页。若是编号不存在则返回NULL。须要注意的是通常在获取了某一页内存后,不要进行重复获取操做,不然在DEBUG状态下会返回异常。另一个状况是有可能相同的页编号下两次获取的内存地址是不同的,由于系统实现内部有内存复用的机制。

3、内存池中内存的放回

功能:将分配或者申请的内存页放回到内存池中去,以便进行重复利用。

函数签名:

int  mpool_put(MPOOL *mp, void *pgaddr, u_int flags);

复制代码

参数:

mp: [in] 内存池对象。

pgaddr:[in] 要放入缓存的内存页地址。这个地址由mpool_get/new两个函数返回。

flags:[in] 放回的属性,通常设置为0或者MPOOL_DIRTY。

return:[in] 函数调用成功返回0,失败返回非0

描述

  1. 这个函数用来将内存页放入回内存池缓存中,以便对内存进行重复利用。当将某个内存地址放入回缓存后,将不能再次访问这个内存地址了。若是要想继续访问内存中的数据则须要借助上述的mpool_get/new函数来从新获取。
  2. flags:属性若是指定为0时,代表放弃此次内存中的内容的修改,系统不会将内存中的内容写入到磁盘中,而只是将内存放入缓存中供其余地方重复使用。而若是设置为MPOOL_DIRTY时,则代表将这页内存中的数据设置为dirty标志,除了一样将内存放入缓存中重复利用外,则会在适当的时候将内存中的数据写入到磁盘中,以便下次进行读取。

4、内存池磁盘读写通知

功能:注册回调函数,当某页内存要写回到磁盘或者要从磁盘中读取时就会调用指定的回调函数。

函数签名:

void mpool_filter(MPOOL *mp, void (*pgin)(void *, pgno_t, void *),
         void (*pgout)(void *, pgno_t, void *), void *pgcookie);
复制代码

参数:

mp:[in] 内存池对象.

pgin: [in]: 回调函数,当某个内存页的数据须要从磁盘读取时,会在读取完成后调用这个回调函数。

pgout:[in]: 回调函数,当某个内存页的数据要到磁盘时,会在写入完成后调用这个回调函数。

pgcookie: [in] 上述两个回调函数的附加参数。

描述

由于内存池中的内存页会进行复用,以及会在适当的时候将内容同步到磁盘中,或者从磁盘中将内容读取到内存中,所以能够借助这个函数来监控这些磁盘文件和内存之间的读写操做。pgin和pgout函数的格式定义以下:

//pgin和pgout回调函数的格式。
//pgcookie:是mpool_filter函数中传入的参数。
//pgno: 要进行读写的内存页编号
//pageaddr: 要进行读写的内存地址。
void (*pgcallback)(void *pgcookie, pgno_t pgno, void *pageaddr);

复制代码

5、示例代码

#include <mpool.h>
#include <db.h>

 //建立并打开一个文件。
 int fd = open("/Users/apple/mpool", O_RDWR|O_APPEND|O_CREAT,0660);

//建立一个内存池对象,每页的内存100个字节,最大的缓存数量为4
 MPOOL *pool = mpool_open(NULL, fd, 100, 4);

   
//从内存池中分配一个新的内存页,这里对返回的内存填写数据。
 pgno_t pidx1, pidx2 = 0;
 char *mem1 =  (char*)mpool_new(pool, &pidx1);
 memcpy(mem1, "aaa", 4);
    
 char *mem2 = (char*)mpool_new(pool, &pidx2);
 memcpy(mem2, "bbb", 4);
    
//将分配的内存mem1放回内存池中,可是内容不保存到磁盘
 mpool_put(pool, mem1, 0);
//将分配的内存mem2放回内存池中,可是内容保存到磁盘。
 mpool_put(pool, mem2, MPOOL_DIRTY);
    
//通过上面的操做后mem1,mem2将不能继续再访问了,须要访问时须要再次调用mpool_get。   
mem1 = (char*)mpool_get(pool, pidx1, 0);
mem2 =   (char*)mpool_get(pool, pidx2, 0);

//上面的mem1和mem2可能和前面的new返回的地址是不同的。所以在内存池中不能经过地址来作惟一比较,而应该将编号来进行比较。
       
//将全部设置为dirty标志的内存也写回到磁盘中去。
 mpool_sync(pool);

 mpool_close(pool);  //关闭内存池。

 close(fd);  //关闭文件。

复制代码

内存池为iOS系统底层开发提供了一个很是重要的能力,咱们能够好好利用内存池来对内存进行管理,以及一些须要进行持久化的数据也能够借助内存池来进行保存,经过内存池提升内存的重复利用率。

相关文章
相关标签/搜索