面试场景题2
1.大文件求交集
给两个文件,每个文件每行都是字符串,如何找出两个文件中行相同的字符串。
假设文件为a,b
1.分批载入A和B的一部分数据,每次在内存里求交集(可以用set),最后合并结果(读写IO可能比较高)。
2.利用哈希思想。先把a文件hash,再遍历b文件,去判断是否存在。时间复杂度降低为O(n) ,但是空间复杂度上来了,以空间换时间。
第一步:遍历文件a,使用Hash函数将a文件中的字符分别存储到1000个小文件中,如(a0....a999)这样每个小文件的大约为300M; 50G(50亿)×64=320G
遍历文件b,使用相同的Hash函数,将每个url存储到1000个小文件中,如(b0....b999)。
这样,所有可能相同的字符都存在对应的小文件中(a0对应b0),不对应的文件不可能存在相同的字符。
第二步:求每对小文件中相同的字符,可以把其中一个小文件的字符存储到hash_set中。
然后遍历另一个小文件的每个字符,看其是否在刚才构建的hash_set中。
如果是,那么就是共同的字符,存到文件里面就可以了。
3.布隆过滤器,布隆过滤器就是一种能够快速判断元素是否在集合里的数据结构,那就可以分别加载两个文件,最后判断A中的字符是否在B中,从而找出交集(两轮遍历O(n))。
对于大文件的话,一般都是使用分治思想,将文件分割成多个小文件来处理。
2. 4G大小的文本,统计单词的个数 / 统计一篇超过10G的文章中每个单词出现的次数
(1)首先,将文本切分成多个小文件,每个小文件大小适中,比如 100MB。将这些小文件分别分配到多台计算机上进行处理。
(2)在每台计算机上,使用多线程读取小文件中的内容,将单词拆分出来,并将每个单词作为 key,对应的出现次数作为 value 存储到本地内存中的一个 hash map 中。
(3)当一个小文件被处理完成后,将这个 hash map 中的所有单词出现次数合并到一个全局的 hash map 中。这个全局的 hash map 可以存储在一个共享的存储系统中,比如 Redis。
(4)所有小文件处理完成后,最终得到的全局 hash map 就是每个单词在整个文章中出现的次数了。
这种实现方式的优点是可以利用多台计算机和多线程来并行处理数据,从而提高处理速度。同时,每个计算机只需要在本地存储一部分数据,可以有效减少单个计算机的内存占用。
/** * @author yanfengzhang * @description 统计一篇超过10G的文章中每个单词出现的次数 * @date 2023/4/9 12:24 */ public class WordCount { /*线程数*/ private static final int NUM_THREADS = 4; /*单词的正则表达式*/ private static final Pattern WORD_PATTERN = Pattern.compile("[^a-zA-Z0-9]"); public static void main(String[] args) throws IOException, InterruptedException { long startTime = System.currentTimeMillis(); String filePath = "/Users/yanfengzhang/Downloads/zyfTestData.txt"; /*读取文件并分割成多个子任务*/ Map<String, Integer> wordCounts = new HashMap<>(); BloomFilter<CharSequence> bloomFilter = BloomFilter.create( Funnels.stringFunnel(Charset.forName("UTF-8")), 1000000000, 0.01); try (BufferedReader br = new BufferedReader(new FileReader(filePath))) { String line; while ((line = br.readLine()) != null) { String[] words = WORD_PATTERN.split(line); for (String word : words) { if (!word.isEmpty()) { String lowerCaseWord = word.toLowerCase(); if (!bloomFilter.mightContain(lowerCaseWord)) { bloomFilter.put(lowerCaseWord); wordCounts.put(lowerCaseWord, wordCounts.getOrDefault(lowerCaseWord, 0) + 1); } } } } } /*按照线程数分割成多个任务并发执行*/ ExecutorService executor = Executors.newFixedThreadPool(NUM_THREADS); int numWordsPerThread = wordCounts.size() / NUM_THREADS; int startIndex = 0; for (int i = 0; i < NUM_THREADS; i++) { int endIndex = (i == NUM_THREADS - 1) ? wordCounts.size() : (startIndex + numWordsPerThread); executor.submit(new WordCounter(wordCounts, bloomFilter, startIndex, endIndex)); startIndex = endIndex; } executor.shutdown(); executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS); long endTime = System.currentTimeMillis(); /*输出结果*/ wordCounts.entrySet().stream() .sorted(Map.Entry.comparingByValue((a, b) -> b - a)) .limit(100) .forEach(entry -> System.out.println(entry.getKey() + ": " + entry.getValue())); System.out.println("Elapsed time: " + (endTime - startTime) / 1000.0 + "s"); } /** * 子任务:统计指定区间的单词出现次数 */ private static class WordCounter implements Runnable { private final Map<String, Integer> wordCounts; private final BloomFilter<CharSequence> bloomFilter; private final int startIndex; private final int endIndex; public WordCounter(Map<String, Integer> wordCounts, BloomFilter<CharSequence> bloomFilter, int startIndex, int endIndex) { this.wordCounts = wordCounts; this.bloomFilter = bloomFilter; this.startIndex = startIndex; this.endIndex = endIndex; } @Override public void run() { int i = 0; for (Map.Entry<String, Integer> entry : wordCounts.entrySet()) { if (i >= startIndex && i < endIndex) { String word = entry.getKey(); if (!bloomFilter.mightContain(word)) { /*如果布隆过滤器中不存在该单词,则跳过*/ continue; } int count = entry.getValue(); wordCounts.put(word, count); } i++; } } } }
3.布隆过滤器
布隆过滤器可以看作是由一组哈希函数和一个二进制的比特向量构成的。具体来说,我们首先根据需要过滤的元素数量和期望的误判率计算出所需比特向量大小和哈希函数个数,接着将每个元素通过哈希函数映射到一组位上,并将比特向量中对应的位置赋值为1。
当需要查询一个元素是否存在时,我们同样将该元素通过相同的哈希函数映射到一组位上,并检查对应的比特向量位置是否全为1,如果是,则认为该元素存在于过滤器中,否则认为该元素不存在。
布隆过滤器判断存在的不一定存在,但是判断不存在的一定不存在。