面试题答案
一键面试一、Objective-C 字典底层实现原理
- 哈希表基础:Objective-C 字典(
NSDictionary
和NSMutableDictionary
)底层主要基于哈希表数据结构。哈希表通过一个哈希函数将键(key)映射到一个哈希值,这个哈希值对应哈希表中的一个位置,称为桶(bucket)。当向字典中插入键值对时,先计算键的哈希值,找到对应的桶,若桶为空则直接插入;若桶已被占用(哈希冲突),则使用开放寻址法或链地址法等方式处理冲突。 - 哈希函数:在 Objective-C 中,对象要作为字典的键,必须实现
hash
方法和isEqual:
方法。hash
方法用于生成哈希值,它应该满足如果两个对象相等(通过isEqual:
判断),那么它们的哈希值必须相等;但哈希值相等的两个对象不一定相等。良好的哈希函数能够均匀地将键分布在哈希表中,减少哈希冲突。 - 冲突处理:
- 开放寻址法:当发生哈希冲突时,在哈希表中寻找下一个空的桶来存储新的键值对。常见的开放寻址法有线性探测(每次探测下一个桶)、二次探测(探测距离以平方递增)等。
- 链地址法:在每个桶中维护一个链表,当发生哈希冲突时,将新的键值对添加到链表中。这种方法适用于冲突较频繁的场景。在 Objective-C 字典底层,可能会采用链地址法来处理哈希冲突,链表中的节点存储键值对信息。
二、性能优化思路及技术点(数据量较大场景)
- 优化哈希函数:
- 思路:如果哈希函数分布不均匀,会导致大量哈希冲突,降低字典的查找、插入和删除性能。因此,需要优化键对象的
hash
方法,使其生成的哈希值更加均匀地分布在哈希表中。 - 技术点:对于自定义对象作为键,仔细设计
hash
方法。例如,如果对象有多个属性,可以将这些属性的哈希值进行合理的组合(如异或操作),避免属性值相近时哈希值也相近的情况。同时,要确保hash
方法和isEqual:
方法的一致性。
- 思路:如果哈希函数分布不均匀,会导致大量哈希冲突,降低字典的查找、插入和删除性能。因此,需要优化键对象的
- 减少哈希冲突:
- 思路:除了优化哈希函数,还可以通过调整哈希表的容量来减少哈希冲突。当哈希表的负载因子(已占用桶的数量与总桶数的比例)过高时,哈希冲突的概率会增加。
- 技术点:在适当的时候扩大哈希表的容量。比如,当负载因子达到一定阈值(如 0.75)时,创建一个更大的哈希表,并将原哈希表中的所有键值对重新插入到新的哈希表中。这一过程称为 rehash。但 rehash 操作会带来一定的性能开销,所以要权衡扩容的时机。
- 选择合适的冲突处理策略:
- 思路:根据应用场景的特点,选择更合适的冲突处理策略。如前所述,链地址法适合冲突频繁的场景,而开放寻址法在冲突较少时性能较好。
- 技术点:如果应用场景中预计会有较多的哈希冲突,可以考虑使用链地址法,并对链表进行优化。例如,使用双向链表代替单向链表,这样在删除节点时可以更高效;或者在链表长度达到一定阈值时,将链表转换为二叉搜索树或平衡二叉树,提高查找性能。
- 缓存与预取:
- 思路:利用缓存机制,对于频繁访问的键值对,可以将其缓存起来,减少对字典的直接查找次数。同时,对于可能即将访问的数据,可以进行预取操作。
- 技术点:可以使用
NSCache
类作为缓存。NSCache
是一个线程安全的缓存,它会自动根据系统内存情况释放缓存对象。对于预取,可以根据应用的业务逻辑,提前将可能需要的数据加载到内存中,例如在用户进行某个操作前,预测其可能需要访问的字典数据并提前读取。
- 多线程优化:
- 思路:在多线程环境下,字典的并发访问可能会导致性能问题和数据不一致。因此,需要采取相应的同步机制或优化措施。
- 技术点:
- 使用线程安全的字典:如
NSMutableDictionary
在多线程环境下不是线程安全的,可以使用NSMapTable
等线程安全的容器替代,它提供了线程安全的键值对存储。 - 读写锁:如果只需要在多线程环境下对字典进行读多写少的操作,可以使用读写锁(如
pthread_rwlock
)。读操作时可以允许多个线程同时进行,写操作时则独占锁,以保证数据一致性。 - 队列操作:将对字典的操作放入队列中,使用
NSOperationQueue
或dispatch_queue
来串行化对字典的访问,避免多线程冲突。
- 使用线程安全的字典:如