iOS 数组的实现原理

有关NSArray的

image.png

不管是NSArray,还是NSMutableArray ,alloc之后的得到都是__NSPlacrholderArray.
当我们nsarray一个空数组,得到的是__NSArray0
nsarray只有一个元素时,得到的是__NSSingleObjectArrayI
nsarray.count > 1 时, 得到 __NSArrayI
nsmutablearray 返回的都是__NSArrayM
placeHolder 和 placeHoldes 的内存地址一样,说明是一个单例,该类内部只有一个isa指针,init后被新的实例换掉了

CFArray 是CoreFoundation中的, 和Foundation中的NSArray相对应,他们是Toll-Free-Briaged. 用的环形缓冲区实现的.

C数组的原理 连续的内存空间, 在下标0处插入一个元素时, 移动其后面所有的元素, 即memmove原理


image.png

同样的移除第一个元素,需要进行相同的动作


image.png

当数组非常大时,就有问题了,NSMutableArray使用环形缓冲区,_NSArrayM用了环形缓冲区(circular buffer),这个数据结构相对简单,只是比常规数组/缓冲区复杂点.环形缓冲区的内容能在到达任意一段时绕向另一端.

环形缓冲区,在删除的时候不会清楚指针, 如果我们在中间进行插入和删除, 只会移动最少的一边元素.


image.png

遍历数组的几个方法:

  • for循环
  • NSEnumerator
  • forin
    enumerateObjectsUsingBlock:(通过block回调,在子线程中遍历,对象的回调次序是乱序的,而且调用线程会等待该遍历过程完成:)
    这几个 forin性能最好,for循环较低, 多线程遍历方式是性能最差的.

__NSArrayI{
NSInterger _userd; 数组的元素个数,调用[array count]时,返回的就是_userd的值。
id_list[0]; 当做id_list来用,即一个存储id对象的buff.由于__NSArrayI的不可变,所以_list一旦分配,释放之前都不会再有移动删除操作了。
}

__NSArrayI的实现

image.png

__NSArrayI对这个方法的实现中,主要把内部数组的_list赋给state->itemsPtr,并返回_used数组大小。state->mutationsPtr指向一个局部静态变量,state->state看起来是一个标志,再次用同一个state调用这个方法就直接返回0.
快速枚举的意思就是一下就把全部对象获取到了,而且在一个c数组里,之后要获得哪个位置的对象都可以快速寻址到,通过state->itemsPtr来访问数组。
__NSSingleObjectArrayI{
id object; 因为只有在创建只包含一个对象的不可变数组时,才会得到__NSSingleObjectArrayI对象,所以其内部结构更加简单
}
__NSArrayM{ 它的内部对象数组时一块连续内存id *_list
NSUInterger _used; 当前对象数目 [nsmutablearray count]
NSUInterger _offset; 时机对象数组的起始偏移
int_size: 28; 已分配的_list大小,能存储的对象个数,不是字节数
int_unused: 4;
uint32_t _mutations;修改标记,每次对__NSArrayM的修改操作都会使_mutations +1 "Collection <__NSArrayM: 0x1002076b0> was mutated while being enumerated" 这个异常就是通过对_mutations的识别来引发的。
id *_list;是个循环数组,并且在增删操作时会动态地重新分配以符合当前的存储需求
}

__NSArrayM的实现

image.png

从实现来看,如果_list还没有构成循环,第一次就获得了全部元素,跟__NSArrayI一样。但是如果_list构成了玄幻,就需要两次,第一次获取_offset到_list末端的元素,第二次获取存放在_list起始处的剩余元素。


image.png

__NSArrayM的_list是个循环数组,它的其实由_offset标识.
forin速度最快的原因是遵从了NSFastEnumertation协议,它是直接从C数组中去对象对于可变数组来说,最多只需要两次就可以获取全部数据。如果数组没有构成循环,第一次就获得了全部元素,跟不可变数组一样,如果数组构成了循环,那么就需要两次,第一次获取对象数组的起始偏移到循环数组末端的元素,第二次获取存放在循环数组起始处的剩余元素。而for循环之所以慢一点,是每次都要调用objectAtIndex:,添加@autoreleasepool,可以提高效率,如果我们每次遍历不需要知道下标,选择forin。


image.png

forin是基于快速枚举实现的,编译器将for in 转化为两层循环,外层调用快速枚举方法批量获取元素,内层通过c数组取得一批元素中的每一个,并且在每次获取元素前,检查是否对数组对象进行了变更操作,如果是,则抛出异常。
block循环,系统已经帮我加了@autoreleasepool,其他的循环可以通过@autoreleasepool来优化。

NSEnumerationConcurrent+Block的方式耗时最大,我认为是因为它采用多线程,就这个方法来讲,多线程的优势并不在遍历多快,它的回调在各个子线程。

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