选举2029:邮编处理技术详解
在上一篇关于记录和集合的实践性文章之后,本篇内容可能不会给读者提供太多可直接应用于自身项目的思路,也没有向微软提出功能需求。但这个领域让我觉得非常有趣。
英国邮编简介
我意识到本文大多数读者可能不在英国。虽然多数国家都有某种形式的邮政编码,但具体细节因国家而异。例如,英国邮编的精细程度与美国邮编大不相同——一个英国邮编通常只对应街道的某一段,因此结合门牌号和邮编就能精确定位地址。
对我的选举网站重要的是,每个邮编都唯一对应一个选区,这也正是我想利用的特性。我的选区页面允许用户输入选区名称或邮编前缀,并实时筛选结果。我推测相当比例的英国居民知道自己的邮编但不知道所属选区名称(特别是在2023年选区边界调整后),因此同时支持两种查询方式很有帮助。
数据结构与存储挑战
逻辑上,输入数据只是简单的“邮编-选区代码”序列。选区代码长度为9字符。如果采用最朴素的存储方式(270万条记录,每条占用16字符),需要约42MB空间,这还不包括数据分隔的开销。
按外码分组
邮编数据存在大量冗余。通过按外码(如"RG30")分组可以发现,3077个外码中:
- 851个外码对应1个选区
- 975个对应2个选区
- 774个对应3个选区
- 其余分布更分散
每个外码对应4000个可能的内码(从0AA到9ZZ)。最初尝试用每值1字节的简单映射需要12.3MB空间。
压缩策略演进
尝试过多种压缩方案:
- 按位存储:根据选区数量使用1/2/4位存储,可将映射压缩至3.1MB
- 手动游程编码:代码复杂度显著增加
- 通用压缩算法:最终使用DeflateStream/InflateStream,将总大小压缩至500KB,远低于Firestore文档1MB的限制
查询处理逻辑
内存中保持解压后的数据,每个外码映射使用每值1字节存储,占用约12MB内存。查询处理的关键在于处理邮编格式的歧义性:
- 单字符输入不返回结果
- 建立“前两个字符→外码映射列表”的字典
- 根据剩余前缀长度分级处理:
- 0字符:返回该外码所有选区
- 1字符:返回对应扇区选区(每外码仅10个扇区)
- 2字符:遍历20种可能的“半单元”组合
- 3字符:精确匹配单个邮编
技术方案对比
本文展示了两种技术路线的平衡:
- 领域特定优化:利用邮编与选区的聚类特性进行初步分组
- 通用算法应用:对分组后的映射使用标准压缩算法
虽然手动优化能将原始数据从7MB压缩,但通用压缩算法在保持代码简洁性的同时实现了更好的压缩效果(0.5MB)。查询延迟主要来自网络传输(约60ms),ASP.NET Core内部处理时间不足1毫秒。
总结
这个项目的两大亮点:一是技术实现的隔离性,只需理解邮编和选区的关系即可;二是用相对简单的代码实现了惊人的性能——在0.5MB空间内存储270万条邮编数据,并实现瞬时查询响应。这充分展示了在特定领域知识和通用算法之间找到平衡点的价值。