腾讯一面:40亿QQ号,不超过1G内存,如何去重?

前言

大家好,我是田螺.

分享一道网上很火的腾讯面试题:40亿的QQ号,如何去重,1G的内存. 不过,有腾讯上班的朋友说,我们没出过这种面试题~ 哈哈~

哈哈,anyway,这道题还是很有意思的. 它是一个非常经典的海量数据去重问题,并且做了内存限制,只能1G.本文田螺哥跟大家探讨一下.

  • 公众号捡田螺的小男孩 (有田螺精心原创的面试PDF)
  • github地址,感谢每颗star:github

1. 常规思路

我们日常开发中,如果谈到去重 ,最容易想到的就是放到HashSet,直接放到HashSet就好:

ini 复制代码
Set<Long> qqSet = new HashSet<>();
qqSet.add(qqNumber); // 自动去重

但是呢,是有个1G的内存限制的! 如果放到HashSet,那40亿的QQ数据,都是在内存中的,我们来算一下,40亿的QQ,要多大的内存:

如果每个QQ号是64位整数(8字节),那么40亿个QQ号的总存储量为:

ini 复制代码
40亿 * 8字节 = 320亿字节
转化位KB 32,000,000,000 字节/1024 = 31,250,000 KB
KB转化为MB 31,250,000 KB/ 1024 ≈ 30,517.578125 MB
MB转化为GB  30,517.578125 MB/ 1024 ≈ 29.8023223876953125 GB

那就是30GB左右,如果每个QQ号码是32位整数(4字节),则是15GB左右. 显然,都远超1GB的内存.

因此,直接放到HashSet并不可行.

因此,这道题我们需要换个思路,就是在内存有限的情况下,如何实现去重? 我们可以考虑一种更高效的数据结构来处理这个问题。

我们可以考虑BitMap(位图)来解决这个问题.

2. BitMap

2.1 BitMap 到底是什么

BitMap(位图)是一种非常高效的数据结构,特别适合处理大规模数据的去重和查询问题。它的基本思想是使用一个bit位来表示一个数字是否存在。

例如,如果我们有一个长度为10的BitMap,那么它可以表示数字0到9是否存在:

  • 如果BitMap的第0位是1,表示数字0存在;
  • 如果BitMap的第1位是1,表示数字1存在;
  • 如果BitMap的第2位是1,表示数字2存在;

以此类推~

数字9表示的BitMap如下:

如果用BitMap,比如我要记录的QQ号码分别是9、5、2、7, 那么BitMap表示为

显然只需要一个10位就可以表示,如果用传统方法来记录,一个整型4字节,4个QQ号码就是,44=16字节,然后一个字节8位,那就是 16 8=128位啦~. 可以发现用BitMap 可以大大节省存储空间.

2.2 用BitMap给40亿QQ去重

2.2.1 使用BitMap,40亿的QQ是否超过1GB内存

既然BitMap 可以大大节省存储空间,我们用BitMap来给40亿QQ去重,看看会不会超1G的内存.

我们来一起估算一下, 因为要40亿的QQ,那我们申请一个足够大的BitMap,假设就是40亿的位,那内存大概就是:

ini 复制代码
4,000,000,000/8 = 500,000,000 
500,000,000/1024/1024/1024 ≈ 0.466GB

可以发现,只需要0.466GB的内存就足够啦~ 在内存 这方面,是符合不超过1GB的限制的~

2.2.2 使用BitMap,给40亿QQ 去重流程

  1. 首先,初始化好40亿位的BitMap
  2. 其次,遍历这40亿的QQ,把每个QQ号码映射到BitMap中,给对应位置的bit,设置为1

比如,假设有个QQ号码为326443281,那么就在BitMap的对应位置,设置为1

  1. 遍历BitMap,收集所有设置为1的位对应的QQ号码,即为去重后的QQ号码。

2.3 BitMap去重的简单代码实现

给大家来个简单的代码模拟吧:

java 复制代码
import java.util.*;

public class QQDeduplication {

    // 位图的大小为 4,294,967,296 bits,即 0.5 GB
    private static final long BITMAP_SIZE = 1L << 32; // 2^32
    private static final int BYTE_SIZE = 8; // 每个字节有8位

    private static List<Long> deduplicateQQNumbers(long[] qqNumbers) {
        // 创建位图,使用字节数组
        byte[] bitmap = new byte[(int) (BITMAP_SIZE / BYTE_SIZE)];

        // 更新位图
        for (long qqNumber : qqNumbers) {
            if (qqNumber >= 0 && qqNumber < BITMAP_SIZE) {
                // 计算字节索引和位索引
                int index = (int) (qqNumber / BYTE_SIZE);
                int bitPosition = (int) (qqNumber % BYTE_SIZE);
                // 设置对应的位
                bitmap[index] |= (1 << bitPosition);
            }
        }

        // 收集去重后的QQ号码
        List<Long> uniqueQQNumbers = new ArrayList<>();
        for (int i = 0; i < bitmap.length; i++) {
            for (int j = 0; j < BYTE_SIZE; j++) {
                if ((bitmap[i] & (1 << j)) != 0) {
                    long qqNumber = (long) i * BYTE_SIZE + j;
                    uniqueQQNumbers.add(qqNumber);
                }
            }
        }

        return uniqueQQNumbers;
    }
}

2.4 BitMap的优缺点

我们使用一种数据结构去解决问题,那肯定要知道它的优缺点对吧.

Bitmap的优点

  • 空间效率高

相比哈希表存储原始数据,Bitmap仅用1位/元素。对于密集数据(如连续QQ号),空间利用率极高。

  • 操作非常高效

插入和查询均为O(1)复杂度,位运算速度快,适合海量数据实时处理。

  • 去重逻辑简单

只需遍历数据,置位存在标记,无需复杂结构。

Bitmap的缺点

  • 存储空间依赖值域范围

若值域范围大但稀疏(如QQ号上限远大于实际数量),空间浪费严重。例如,若QQ号上限为1万亿,需125GB内存,难以承受。

  • 无法存储额外信息,只能记录有还是没有

仅记录是否存在,无法保存出现次数等元数据。

最后

有些伙伴认为,使用布隆过滤器也可以实现,40亿的QQ号,不超过1G的内存,进行去重.大家觉得呢? 欢迎评论区留言讨论哈. 希望大家都找到心仪的offer ~~

BitMap 的存储空间与值域强相关

BitMap 的存储空间需求直接取决于 值域的大小,而不是实际数据的数量。

值域:指数据可能的取值范围(如 QQ 号的最小值和最大值之间的范围)。

存储空间:BitMap 需要为值域中的每一个可能值分配一个 bit 位,无论该值是否实际存在。

相关推荐
we风29 分钟前
【SpringCache 提供的一套基于注解的缓存抽象机制】
java·缓存
趙卋傑3 小时前
网络编程套接字
java·udp·网络编程·tcp
两点王爷3 小时前
Java spingboot项目 在docker运行,需要含GDAL的JDK
java·开发语言·docker
云泽8085 小时前
模块化设计,static和extern(面试题常见)
c语言·面试·职场和发展
万能螺丝刀15 小时前
java helloWord java程序运行机制 用idea创建一个java项目 标识符 关键字 数据类型 字节
java·开发语言·intellij-idea
zqmattack5 小时前
解决idea与springboot版本问题
java·spring boot·intellij-idea
Hygge-star5 小时前
【Java进阶】图像处理:从基础概念掌握实际操作
java·图像处理·人工智能·程序人生·职场和发展
Honmaple5 小时前
IDEA修改JVM内存配置以后,无法启动
java·ide·intellij-idea
Humbunklung6 小时前
Rust 编程实现猜数字游戏
开发语言·后端·rust
小于村6 小时前
pom.xml 文件中配置你项目中的外部 jar 包打包方式
xml·java·jar