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

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

⛲️内存池

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

功能:在iOS中提供了一套内存池管理的API,你可以用这套API来实现上述的功能,而且系统内部很多功能也是借助内存池来实现内存复用和磁盘存储的。
头文件: #include <mpool.h>, #include <db.h>
平台: BSD系统,linux系统

一、内存池的创建、同步和关闭

功能:创建和关闭一个内存池对象并和磁盘文件绑定以便进行同步操作。
函数签名


//创建一个内存池对象
 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函数来打开。
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. 当我们想将所有设置为脏标志的内存页立即写入磁盘时则需要调用mpool_sync函数进行同步处理。

  3. 当我们不再需要内存池时,则可以通过mpool_close来关闭内存池对象,需要注意的是关闭内存池并不会将内存中的数据回写到磁盘中去。

二、内存池中内存的获取

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

//从内存池中申请分配一页新的内存
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状态下会返回异常。另外一个情况是有可能相同的页编号下两次获取的内存地址是不一样的,因为系统实现内部有内存复用的机制。
三、内存池中内存的放回

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

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时,则表明将这页内存中的数据设置为脏标志,除了同样将内存放入缓存中重复利用外,则会在适当的时候将内存中的数据写入到磁盘中,以便下次进行读取。
四、内存池磁盘读写通知

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

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);

五、实例代码
#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返回的地址是不一样的。因此在内存池中不能通过地址来做唯一比较,而应该将编号来进行比较。
       
//将所有设置为脏标志的内存也写回到磁盘中去。
 mpool_sync(pool);

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

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

内存池为iOS系统底层开发提供了一个非常重要的能力,我们可以好好利用内存池来对内存进行管理,以及一些需要进行持久化的数据也可以借助内存池来进行保存,通过内存池提高内存的重复利用率。

下一篇:iOS标准库中常用数据结构和算法之cache


欢迎大家访问欧阳大哥2013的github地址简书地址

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,036评论 1 32
  • 所有知识点已整理成app app下载地址 J2EE 部分: 1.Switch能否用string做参数? 在 Jav...
    侯蛋蛋_阅读 2,356评论 1 4
  • InnoDB体系架构 上图简单显示了InnoDB存储引擎的体系架构图中可见,InnoDB存储引擎有多个内存块,可以...
    Rick617阅读 3,951评论 0 6
  • 在一个方法内部定义的变量都存储在栈中,当这个函数运行结束后,其对应的栈就会被回收,此时,在其方法体中定义的变量将不...
    Y了个J阅读 4,390评论 1 14
  • 水石轩主人阅读 111评论 0 0