iOS 有关哈希的理解

46 阅读6分钟

iOS 面试 - 哈希核心要点(必背版)

🎯 一、基础概念(必答)

1. 什么是哈希?

答案:

  • 哈希 = 把任意数据转换成固定长度数字的机制
  • 哈希值 = 数据的"编号",用于快速定位
  • 哈希表 = 用哈希值作为索引的数据结构

一句话总结: 哈希就是给数据一个编号,通过编号快速找到数据


🎯 二、iOS 中的哈希应用(必考)

1. NSDictionary(字典)

NSDictionary *dict = @{@"name": @"张三"};
dict[@"name"];  // 内部使用哈希表,O(1) 查找

要点:

  • 使用哈希表存储 key-value
  • 查找时间复杂度:O(1)
  • key 的哈希值用于定位存储位置

2. NSSet(集合)

NSSet *set = [NSSet setWithObjects:@"A", @"B", @"A", nil];
// 自动去重,因为使用哈希表

要点:

  • 使用哈希表去重
  • 查找时间复杂度:O(1)

3. Runtime 方法缓存(最重要!)

[obj doSomething];  // 第一次:慢速查找 + 存入 cache
[obj doSomething];  // 第二次:哈希查找 cache,快速返回

要点:

  • 每个类有 cache_t 结构,存储 selector → IMP 映射
  • 使用哈希表实现快速查找
  • 缓存命中率通常 >90%
  • 这是 iOS 底层最重要的哈希应用

🎯 三、Runtime 方法缓存详解(高频考点)

数据结构

// cache_t(简化)
struct cache_t {
    bucket_t *_buckets;  // 哈希桶数组
    uint32_t _mask;      // 容量 - 1
    uint32_t _occupied;  // 已占用数量
};

// bucket_t
struct bucket_t {
    SEL _key;   // selector(方法名)
    IMP _imp;   // 方法实现(函数指针)
};

查找流程

objc_msgSend(receiver, selector)
  ↓
1. 检查 receiver 是否为 nil2. 获取 receiver 的 isa → class3.class 的 cache 中哈希查找
  ↓
4. 命中 → 直接调用 IMP(快速路径,90%+)
  ↓
5. 未命中 → 慢速查找(method_list)

为什么快?

  • 哈希表查找:O(1) 时间复杂度
  • 缓存命中率高:>90% 的方法调用走快速路径
  • 汇编优化objc_msgSend 用汇编实现

🎯 四、哈希冲突(必考)

什么是哈希冲突?

答案: 不同的数据计算出相同的哈希值

例子:

"张三" 的哈希值 = 12345
"李四" 的哈希值 = 12345  // 冲突了!

解决方法(必答)

1. 开放寻址法(Runtime 使用)
如果位置被占用,向后找下一个空位置
位置 45:已被占用
位置 46:空 ✅ 存储
2. 链地址法(NSDictionary 可能使用)
每个位置存储一个链表
位置 45:→ 数据1 → 数据2 → 数据3

🎯 五、对象的 hash 方法(常考)

默认实现

// 对象默认用内存地址作为哈希值
Person *p = [[Person alloc] init];
NSUInteger hash = [p hash];  // 返回内存地址

如何重写(面试可能要求手写)

- (NSUInteger)hash {
    // 使用属性的哈希值组合
    return [self.name hash] ^ [@(self.age) hash];
}

// 必须同时重写 isEqual
- (BOOL)isEqual:(id)object {
    if (self == object) return YES;
    if (![object isKindOfClass:[self class]]) return NO;
    
    Person *other = (Person *)object;
    return [self.name isEqualToString:other.name] 
        && self.age == other.age;
}

要点:

  • hashisEqual 必须一起重写
  • 相等的对象必须有相同的哈希值
  • 不同对象尽量有不同的哈希值

🎯 六、性能对比(必背)

操作数组哈希表
查找O(n)O(1)
插入O(1)O(1)
删除O(n)O(1)

例子:

// 数组查找:O(n) - 需要遍历
NSArray *arr = @[@"A", @"B", @"C", @"D", @"E"];
[arr indexOfObject:@"E"];  // 需要检查 5 个元素

// 哈希表查找:O(1) - 直接定位
NSDictionary *dict = @{@"A": @1, @"B": @2, @"C": @3, @"D": @4, @"E": @5};
dict[@"E"];  // 计算哈希值,直接定位

🎯 七、面试高频问题(标准答案)

Q1: 什么是哈希?哈希表是什么?

答案:

  • 哈希:把数据转换成数字的机制
  • 哈希表:用哈希值作为索引的数据结构
  • 优势:查找速度快(O(1))

Q2: iOS 中哪些地方用到了哈希?

答案:

  1. NSDictionary:key-value 存储
  2. NSSet:去重和快速查找
  3. Runtime 方法缓存:cache_t 存储 selector → IMP(最重要)
  4. 对象的 hash 方法:用于字典 key、集合元素

Q3: Runtime 的方法缓存为什么快?

答案:

  • 使用哈希表存储最近调用的方法
  • 查找时间复杂度 O(1)
  • 缓存命中率通常 >90%
  • 大部分方法调用走快速路径

Q4: 什么是哈希冲突?如何解决?

答案:

  • 冲突:不同数据计算出相同哈希值
  • 解决方法
    1. 开放寻址法:向后找空位置(Runtime 使用)
    2. 链地址法:每个位置存链表(NSDictionary 可能使用)

Q5: 如何重写对象的 hash 方法?

答案:

- (NSUInteger)hash {
    return [self.name hash] ^ [@(self.age) hash];
}
// 必须同时重写 isEqual 方法

Q6: 哈希表的查找时间复杂度是多少?

答案:

  • 平均情况:O(1) - 直接定位
  • 最坏情况:O(n) - 所有数据冲突,退化成链表

Q7: NSDictionary 的底层实现原理?

答案:

  • 使用哈希表存储 key-value
  • key 的哈希值用于定位存储位置
  • 解决冲突可能使用链地址法
  • 查找、插入、删除都是 O(1) 平均时间复杂度

🎯 八、记忆口诀(快速回忆)

核心概念

哈希 = 编号机制
哈希表 = 快速定位
查找 = O(1) 时间复杂度

iOS 应用

字典、集合、方法缓存
都用哈希表实现

Runtime 缓存

cache_t 存 selector → IMP
哈希查找,快速返回
命中率 >90%

哈希冲突

不同数据,相同哈希值
开放寻址 or 链地址

🎯 九、面试回答模板

模板1:什么是哈希?

哈希是把数据转换成数字的机制。
在 iOS 中,NSDictionaryNSSet 和 Runtime 方法缓存都使用哈希表。
哈希表的优势是查找速度快,时间复杂度是 O(1)。

模板2:Runtime 方法缓存

Runtime 的方法缓存使用哈希表存储 selector 到 IMP 的映射。
每次调用方法时,先在 cache 中哈希查找。
如果命中,直接调用 IMP,这是快速路径。
缓存命中率通常 >90%,所以方法调用很快。

模板3:哈希冲突

哈希冲突是指不同数据计算出相同哈希值。
解决方法有两种:
1. 开放寻址法:向后找空位置(Runtime 使用)
2. 链地址法:每个位置存链表(NSDictionary 可能使用)

🎯 十、必背清单(考前检查)

  • 哈希 = 把数据转换成数字
  • 哈希表查找 = O(1) 时间复杂度
  • iOS 中:字典、集合、方法缓存都用哈希表
  • Runtime 方法缓存使用 cache_t 结构
  • 缓存命中率 >90%,大部分走快速路径
  • 哈希冲突的两种解决方法
  • 如何重写对象的 hash 方法
  • hash 和 isEqual 必须一起重写

🎯 十一、加分项(高级回答)

1. 哈希函数的设计原则

  • 分布均匀:不同输入产生不同哈希值
  • 计算快速:O(1) 时间复杂度
  • 确定性:相同输入总是产生相同哈希值

2. Runtime cache 的扩容机制

  • 当占用率超过阈值时,扩容
  • 重新哈希所有数据
  • 容量通常是 2 的幂次

3. 哈希表的负载因子

  • 负载因子 = 已占用数量 / 总容量
  • 负载因子过高会导致冲突增多
  • 通常保持在 0.75 左右

📝 总结(30秒快速回忆)

  1. 哈希 = 编号机制,用于快速定位
  2. iOS 应用 = 字典、集合、方法缓存
  3. Runtime 缓存 = cache_t,哈希查找,命中率 >90%
  4. 哈希冲突 = 开放寻址法 or 链地址法
  5. 性能 = O(1) 查找,比数组快

记住:面试时先说概念,再说 iOS 应用,最后说 Runtime 缓存!