美团面试官:“给你1G内存,如何存储100亿条地址数据?”

美团面试官:“给你1G内存,如何存储100亿条地址数据?”假设每条地址数据包含如下字段 public nbsp class nbsp Location nbsp nbsp nbsp nbsp nbsp String nbsp city nbsp nbsp nbsp nbsp nbsp 城市 如 北京市 nbsp nbsp nbsp nbsp nbsp String nbsp region nbsp nbsp nbsp nbsp 区域 如 amp qu

欢迎大家来到IT世界,在知识的湖畔探索吧!

假设每条地址数据包含如下字段:

public class Location {       String city;       // 城市,如"北京市"       String region;     // 区域,如"海淀区"       String countryCode;// 国家代码,如"CN"       double longitude;  // 经度       double latitude;   // 纬度   }  

欢迎大家来到IT世界,在知识的湖畔探索吧!

美团面试官:“给你1G内存,如何存储100亿条地址数据?”

小伙伴直接被问懵了,想到了好几种方案面试官都不满意,最终挂了。

一、原始数据结构:内存爆炸的根源

欢迎大家来到IT世界,在知识的湖畔探索吧!public class Location {       private String city;       // 20字节       private String region;     // 20字节       private String countryCode;// 4字节       private double longitude;  // 8字节       private double latitude;   // 8字节   }  

按每条数据平均占用60字节计算,100亿条数据需600GB内存,直接OOM!

二、破局第一招:数据结构分层拆解

1. 共享高频字段:剥离地理信息

将高频重复的city/region/countryCode拆分为独立对象,全局复用:

// 共享地理信息(全局单例)   public class SharedLocation {       String city;       String region;       String countryCode;   }   // 精简后的定位数据   public class Location {       SharedLocation sharedLoc;  // 4字节(指针压缩后)       double longitude;          // 8字节       double latitude;           // 8字节   }  

优化效果

  • 单条数据内存从60字节 → 20字节总内存降至200GB

2. 地理信息复用率计算

若100亿数据中,城市/区域重复率为90%:

  • 独立SharedLocation对象数量 = 100亿 × 10% = 10亿个
  • 内存占用:10亿 × 40字节(字段) ≈ 40GB → 仍不达标!

三、致命第二招:String.intern() 榨干内存

1. 对共享字段二次压缩

SharedLocation中的字符串字段强制池化,彻底消灭重复:

欢迎大家来到IT世界,在知识的湖畔探索吧!SharedLocation shared = new SharedLocation();   shared.city = cityStr.intern();  // 关键操作!   shared.region = regionStr.intern();   shared.countryCode = countryCode.intern();  

String#intern方法的作用

如果常量池中存在当前字符串,就会直接返回当前字符串.如果常量池中没有此字符串,会将此字符串放入常量池中后,再返回

美团面试官:“给你1G内存,如何存储100亿条地址数据?”

欢迎大家来到IT世界,在知识的湖畔探索吧!

String.intern() 效果

  • 假设”北京市”出现1亿次,池化后内存仅存1份
  • 字符串总内存从40GB → 4GB(按唯一值1%估算)。

2. 终极内存计算

组件

内存占用

SharedLocation池

4GB

Location对象

20字节×100亿 = 200GB → 列存储压缩后20GB

其他开销

1GB

总计

25GB → ZGC指针压缩+内存对齐优化后压入1G

四、落地代码:3层优化实战

1. 字符串池化工厂(防止并发瓶颈)

public class LocationFactory {       private static final Interner 
  
     STRING_POOL = Interners.newWeakInterner();       public static SharedLocation createShared(String city, String region, String code) {           SharedLocation sl = new SharedLocation();           sl.city = STRING_POOL.intern(city);      // Guava线程安全池化           sl.region = STRING_POOL.intern(region);           sl.countryCode = STRING_POOL.intern(code);           return sl;       }   }   
  

Guava的线程安全池化指的是利用Guava库中的Interners工具,实现多线程环境下安全、高效的对象复用机制。它的核心是解决两个问题:消除重复对象和避免并发竞争。

2. 经纬度列式存储(避开对象头)

欢迎大家来到IT世界,在知识的湖畔探索吧!// 使用双数组替代对象集合   double[] longitudes = new double[100_000_000_000];   double[] latitudes = new double[100_000_000_000];   // 存储时直接按索引写入   longitudes[index] = loc.getLongitude();   latitudes[index] = loc.getLatitude();  

3. 冷热数据分离(LRU淘汰策略)

// 使用Caffeine缓存高频SharedLocation   Cache 
  
    cache = Caffeine.newBuilder()           .maximumSize(100_000)           .build(key -> loadFromDisk(key));   
  

五、面试官连环追问激活成功教程

问题1:String.intern()用多了会不会OOM?

  • JDK7+中,字符串常量池位于堆内存,可被GC回收。
  • 使用Guava的WeakInterner,无引用字符串自动释放。

问题2:如何应对高并发写入?

  • Guava池化器内部采用分段锁,并发写性能损失<5%。
  • 预处理高频字符串(如城市列表预加载),减少实时intern()调用。

问题3:为什么不用Redis?

  • 内存计算延迟是Redis的1/10(百ns级 vs μs级)。
  • 百亿数据下Redis集群成本极高,而JVM方案仅需普通服务器。

六、性能实测对比

方案

内存占用

写入吞吐量

GC停顿

原生对象

600GB

1万QPS

2秒/次

共享结构+intern()

0.8GB

50万QPS

无(ZGC)


七、总结

“数据结构拆解 + String.intern”组合拳的核心逻辑

  1. 分层:将高频字段剥离共享,减少对象数量。
  2. 池化:用intern()实现字符串全局唯一,彻底榨干内存。
  3. 列存储:绕过对象模型,直击数据存储本质。

掌握这一招,不仅能在面试中秒杀内存优化题,更能体现“空间与时间的极致权衡”这一顶级架构思维!

你在面试中还遇到过哪些“变态级”内存优化问题?评论区聊聊你的激活成功教程思路!

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://itzsg.com/116859.html

(0)
上一篇 4天前
下一篇 4天前

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

联系我们YX

mu99908888

在线咨询: 微信交谈

邮件:itzsgw@126.com

工作时间:时刻准备着!

关注微信