Big mobile md5 file filter and shuffle

68 阅读4分钟

Big mobile md5 file filter and shuffle

Status: Not started

我们现在有两个文件,文件 A 有 15 亿条数据,每条数据是一个手机号的 md5 值。文件 B 有 6 亿条数据,同样每条数据是一个手机号 md5 值。现在想要将文件 A 中与文件 B 重复的手机号去除。文件 A 和文件 B 都不能一次性加载入内存。

方案一

最容易想到的,也更普遍的,就是将文件 A 分成 20份(比如叫 A1,A2,A3…..),文件 B 分成 20 份(B1,B2,B3……),然后将 A1,B1 读入内存,然后使用 Set 等取差集。将 A1,B2 读入内存,做差集。很像笛卡尔积的样子。

方案二

我们不将 A 文件切分,将文件 B 切分(比如叫文件 B1,B2,B3….),然后将文件 B1 读入内存,流式读取文件 A,判断文件 A 的每一行是否在 B1 内,如果在说明重复,如果不在说明可能不重复,将可能不重复的写入到文件 A1。

将文件 B2 加载入内存,流式读取文件 A2,将 A2 中与 B2 重复的过滤掉,得到 A3

将文件 B3 加载入内存,流式读取文件 A3,将 A3 中与 B3 重复的过滤掉,得到 A4

以此类推。。。

我们应该尽可能将文件 B 不要切分数量太多,这样就能少遍历几次 A。

方案三

由于文件中都是手机号的 md5,我们可以按照 md5 的前两位字母做分片,这样会将文件 A 和文件 B 各分为 256 个文件。

类似的文件名为:00.csv, 0a.csv, 0b.csv, 0c.csv, 0d.csv ….

这样做过滤时,就是拿文件 A 的 00.csv 和文件B 的 00.csv,将他们加载到内存,然后做过滤。得到一个过滤后的文件 00_filtered.csv

类似的,拿文件A 的 0a.csv 和文件 B 的 0a.csv,将他们呢加载到内存,然后做过滤。得到一个过滤后的文件 0a_filtered.csv

以此类推。我们会拿到所有的过滤文件,最后将这过滤后的 256 个文件做合并就好了。

关于方案三,我有示例代码:

    /**
     * 方案设计:
     * <p>
     * 第一阶段:对第一个文件进行分片
     * <p>
     * 读取第一个文件的MD5值
     * 使用前N位(如前2位)作为分片依据
     * 将MD5值分散写入不同的小文件中(称为A组文件)
     * 第二阶段:处理第二个文件并标记重复项
     * <p>
     * 读取第二个文件的每个MD5值
     * 使用前N位(如前2位)作为分片依据
     * 将MD5值分散写入不同的小文件中(称为B组文件)
     * 第三阶段:B 组文件中过滤 A 组文件中重复的MD5值
     * <p>
     * 对应处理A组和B组的同编号文件
     * 将A组文件的MD5值加载到HashSet中
     * 检查B组文件中的每个MD5值是否在HashSet中存在
     * 如果存在说明重复,不做任何操作。
     * 如果不存在说明不重复,将该MD5值写入最终结果文件
     *
     * @param areaFilePath 要过滤的地区的 md5 数据
     * @param packageFile  底包文件
     * @param resultFile   底包过滤出重复的最终结果文件
     */
    private void bigFileFilter(String areaFilePath, String packageFile, String resultFile) {
​
        AtomicLong resultFileRowCount = new AtomicLong(0L);
        String areaFileDir = FileUtil.getParent(areaFilePath, 1);
        String packageFileDir = FileUtil.getParent(packageFile, 1);
        // 第一阶段:处理第一个文件
        fileSplit(areaFilePath, areaFileDir);
​
        // 第二阶段:处理第二个文件
        fileSplit(packageFile, packageFileDir);
​
        // 第三阶段:B 组文件中过滤 A 组文件中重复的MD5值
        // 对应处理A组和B组的同编号文件
        // 将A组文件的MD5值加载到HashSet中
        // 检查B组文件中的每个MD5值是否在HashSet中存在
        // 如果存在说明重复,不做任何操作。
        // 如果不存在说明不重复,将该MD5值写入最终结果文件
        FileAppender resultFileAppender = new FileAppender(FileUtil.file(resultFile), 1000_0000, true);
        FileUtil.loopFiles(areaFileDir + File.separator + "分组").forEach(file -> {
            AtomicLong filterCount = new AtomicLong(0L);
            String key = file.getName().substring(0, 2);
            FileReader areaFileReader = new FileReader(file);
            HashSet<String> areaMd5Set = new HashSet<>();
            areaFileReader.readLines((LineHandler) s -> areaMd5Set.add(s));
            File Bfile = FileUtil.file(packageFileDir + File.separator + "分组" + File.separator + key + ".csv");
            if (!Bfile.exists()) {
                log.error("文件不存在:{}", packageFileDir + File.separator + "分组" + File.separator + key + ".csv");
                return;
            }
            FileReader packageFileReader = new FileReader(Bfile);
            packageFileReader.readLines((LineHandler) s -> {
                if (!areaMd5Set.contains(s)) {
                    resultFileAppender.append(s);
                    resultFileRowCount.incrementAndGet();
                } else {
                    filterCount.incrementAndGet();
                }
            });
            resultFileAppender.flush();
            log.info("file: {} 过滤掉的条数:{}", file.getName(), filterCount.get());
        });
​
        log.info("resultFile生成总条数:{}", resultFileRowCount.get());
    }
​
    private static void fileSplit(String areaFilePath, String areaFileDir) {
        // 第一阶段:对第一个文件进行分片
        // 读取第一个文件的MD5值
        // 使用前N位(如前2位)作为分片依据
        // 将MD5值分散写入不同的小文件中(称为A组文件)
        FileReader areaFileReader = new FileReader(FileUtil.file(areaFilePath));
        Map<String, FileAppender> fileAppenderMap = new HashMap<>();
        areaFileReader.readLines((LineHandler) s -> {
            // 如果不是md5值的,跳过
            if (s.length() != 32) {
                return;
            }
            String key = s.substring(0, 2);
            FileAppender fileAppender = fileAppenderMap.get(key);
            if (fileAppender == null) {
                fileAppender = new FileAppender(FileUtil.file(areaFileDir + File.separator + "分组" + File.separator + key + ".csv"), 20_0000, true);
                fileAppenderMap.put(key, fileAppender);
            }
            fileAppender.append(s);
        });
        for (FileAppender appender : fileAppenderMap.values()) {
            appender.flush();
        }
    }
```# Big mobile md5 file filter and shuffle