词频统计TopN+单机+内存限制+java

本文介绍了一道关于统计1M文件中频数最高的50个词的题目,采用文件分片、内存限制100KB的策略。通过HashMap统计子文件词频,维护一个50节点的最小堆,实现词频统计。文章讨论了文件切分、内存计算、最小堆实现以及代码实现,最后反思了单机内存限制下的优化问题。

前言

  前几天,同学问了我一道笔试题,网上大部分只有思路,良莠不齐。并且没找到java代码来实现的~所以就动手敲了一份,尽可能在思路上做到全面,也欢迎大家指教。代码地址

题目

  有一个1G大小的一个文件,里面每一行是一个词,词的大小不超过16字节,内存限制大小是1M。返回频数最高的50个词。
  为了本机测试方便,将题目改成
  有一个1M大小的一个文件,里面每一行是一个词,词的大小不超过16字节,内存限制大小是100KB。返回频数最高的50个词。

思路

  毕竟是经典题目,大体思路如下:

  1. 文件分片,使得子文件不超过内存限制大小。
  2. 通过hashMap统计每个子文件中出现词的频率。
  3. 维护一个含有50结点的最小堆,将hashMap的值依次与堆顶(最小值)作比较。如果大于堆顶值,则替换堆顶值,并重新调整最小堆。所有子文件遍历完,最小堆中剩下的节点就是频数最高的50个单词。

  写代码前,还要多向自己提问,这样才能让思路尽可能全面些:

  • Q:怎样切分文件,尽可能平均且方便?
  • A:参照HashMap分桶原理(当然redis等的切片也类似),将 word的hash值 & 切分文件数量;这样可以防止hashCode为负的情况,为了切片更均匀,切分文件的数量要为2^n 。
//HashMap分桶源码
 if ((p = tab[i = (n - 1) & hash]) == null)
   tab[i] = newNode(hash, key, value, null);
 else {
   
   ...}
  • Q:怎样确定切分文件的数量?
  • A:当然要通过待切分文件的大小,和实际内存大小计算得出。然后再取最接近该结果的2^n,这里也可以参考HashMap获取初始容量的算法。
    /**
     * HashMap源码:获取初始容量的方法 → 找到最接近设置值的2^n
     * Returns a power of two size for the given target capacity.
     */
    static final int tableSizeFor(int cap) {
   
   
        int n = cap - 1;	//-1防止本身cap就是2的指数
        n |= n >>> 1;		//n=n|(n>>>1)
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }
  • Q:最小堆一般是基于数组来维护的,怎样同时存放word和count?
  • A:通过ArrayList来维护最小堆,里面存放wordCount的实体类。

项目结构

在这里插入图片描述
红框内,从上至下:

  • domain/WordCount → 实体类;
  • FileUtils → File操作类,包含文件分片、生成符合题目要求的文件等方法;
  • MinHeapArray → 基于数组维护的最小堆,对本项目无用,作为思路参考;
  • MinHeapList → 基于ArrayList维护的最小堆;
  • WordCountMain → 主运行文件,入口;

代码

  多说无益,上代码:

  1. 先要生成符合题目要求的文件,用于测试:
    /**
     * 由一篇英文文章生成
     * 生成每一行是一个词的文件,每个文件不超过16个字节
     * @param srcFileName
     * @param destFileName
     * @throws IOException
     */
    public static void generateWordTxt(String srcFileName, String destFileName) throws IOException {
   
   
        BufferedReader br = new BufferedReader(
                new InputStreamReader(new FileInputStream(srcFileName)));
        StringBuffer stringBuffer = new StringBuffer();
        String line;

        while ((line = br.readLine()) != null) {
   
   
            stringBuffer.append(line);
        }

        String[] words = stringBuffer.toString().split("[^a-zA-Z]+");

        PrintWriter pw = new PrintWriter(new FileWriter(destFileName, true));
        for (String word : words) {
   
   

//          ASCII码、UTF-8、GBK 一个英文字母等于一个字节
//          Unicode、utf-16be   一个英文字母等于两个字节
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值