有关NSArray的
不管是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原理
同样的移除第一个元素,需要进行相同的动作
当数组非常大时,就有问题了,NSMutableArray使用环形缓冲区,_NSArrayM用了环形缓冲区(circular buffer),这个数据结构相对简单,只是比常规数组/缓冲区复杂点.环形缓冲区的内容能在到达任意一段时绕向另一端.
环形缓冲区,在删除的时候不会清楚指针, 如果我们在中间进行插入和删除, 只会移动最少的一边元素.
遍历数组的几个方法:
- for循环
- NSEnumerator
- forin
enumerateObjectsUsingBlock:(通过block回调,在子线程中遍历,对象的回调次序是乱序的,而且调用线程会等待该遍历过程完成:)
这几个 forin性能最好,for循环较低, 多线程遍历方式是性能最差的.
__NSArrayI{
NSInterger _userd; 数组的元素个数,调用[array count]时,返回的就是_userd的值。
id_list[0]; 当做id_list来用,即一个存储id对象的buff.由于__NSArrayI的不可变,所以_list一旦分配,释放之前都不会再有移动删除操作了。
}
__NSArrayI的实现
__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的实现
从实现来看,如果_list还没有构成循环,第一次就获得了全部元素,跟__NSArrayI一样。但是如果_list构成了玄幻,就需要两次,第一次获取_offset到_list末端的元素,第二次获取存放在_list起始处的剩余元素。
__NSArrayM的_list是个循环数组,它的其实由_offset标识.
forin速度最快的原因是遵从了NSFastEnumertation协议,它是直接从C数组中去对象对于可变数组来说,最多只需要两次就可以获取全部数据。如果数组没有构成循环,第一次就获得了全部元素,跟不可变数组一样,如果数组构成了循环,那么就需要两次,第一次获取对象数组的起始偏移到循环数组末端的元素,第二次获取存放在循环数组起始处的剩余元素。而for循环之所以慢一点,是每次都要调用objectAtIndex:,添加@autoreleasepool,可以提高效率,如果我们每次遍历不需要知道下标,选择forin。
forin是基于快速枚举实现的,编译器将for in 转化为两层循环,外层调用快速枚举方法批量获取元素,内层通过c数组取得一批元素中的每一个,并且在每次获取元素前,检查是否对数组对象进行了变更操作,如果是,则抛出异常。
block循环,系统已经帮我加了@autoreleasepool,其他的循环可以通过@autoreleasepool来优化。
NSEnumerationConcurrent+Block的方式耗时最大,我认为是因为它采用多线程,就这个方法来讲,多线程的优势并不在遍历多快,它的回调在各个子线程。