大文本文件(接近7GB): 统计频数,Top K问题求解

目录

    • 本文说明
    • 问题
    • TopK单机思路(分治)
    • 具体实践
      • 步骤 1:每行一个IP:超过内存的大文件
        • 步骤2: 分治求解:
        • 2.1 大文件分成小文件
          • 2.2 小文件统计,并最后统计
          • 2.3 小文件的统计结果 再做一次统计 求出出现频数最高的那个数
      • 步骤3: 程序输出
      • 附:java源代码
    • 总结

本文说明

本来转自这个地址(http://blog.csdn.net/dbt666666/article/details/16974415)的博文, 不过太差了,遂自己实现一把真正的topK问题,网上的文章只作为本文的参考。因为很早写的,当时在windows上实践的,不过应当实现有点问题,确实也不是真正的TopK求解。鉴于本文截止2020-05-01的阅读量已经很多了,所以本文标题就不改了;2020-05-01(今天)重新实践下,并重写本文,并标记本文为原创

请按照目录阅读,要明白我们的问题,我们怎么求解,我们怎么输出,这里先不考虑大数据方案,就单纯的一个能让大一,大二同学就能直接实践并且能够学到知识

问题

你只有一个2C4G的机器(即内存是有限的)

然后很简单的一个问题: 一个6G的txt文件,每一行都出现了一个IP,要统计这其中出现频次最高的IP,返回出现TopK的IP,输出如下

<topCnt1, Ip1>
<topCnt2, Ip1>
...
<topCntK, IpK>

TopK单机思路(分治)

分治思想应该是容易想到的,如果读大二还不知道,那么赶快去学习
大文本文件(接近7GB): 统计频数,Top K问题求解_第1张图片

具体实践

步骤 1:每行一个IP:超过内存的大文件

程序模拟产生了一个1G行,每行一个[0,100000]区间的整数

在这里插入图片描述

步骤2: 分治求解:

2.1 大文件分成小文件

这里根据IP Hash 到 1024个小文件中,显然有:

在单个文件中,topK的才是最终整体有可能的topK(一个文件中可能出现相同次数的,也要考虑进来);非topK的直接抛弃即可

*注意: 我们是普通的Hash,不过数据还是相对均匀的,所以每个小文件确实是挺小的;考虑现实中数据极端情况,可能出现分治分不了,业即:大数据常见的数据倾斜问题(这里引入下,不做过多说明)

2.2 小文件统计,并最后统计

对每个小文件,可以用堆,hash,内部排序等等方法进行处理;

2.3 小文件的统计结果 再做一次统计 求出出现频数最高的那个数

步骤3: 程序输出

结果如下: 第一列是次数,第二列是对应的IP

/*
访问次数最多IP的top_k:10
11178	39293
11143	69144
11140	29718
11133	67370
11131	40798
11130	72952
11127	57661
11122	25185
11120	93643
11118	40562
 */
  • 关于结果的正确性: 我已经用 hadoop MapReduce 实践了一遍,对的上;后续会用另外一篇文章补充hadoop MapReduce的实践(参见:大文本文件(接近7GB): 统计频数,Top K问题求解(二)

附:java源代码

import java.io.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @Author mubi
 * @Date 2020/5/1 12:15
 */
class IP implements Serializable {

    private static final long serialVersionUID = -8903000680469719698L;
    private String ip;
    private int count;

    public IP(String ip2, Integer integer) {
        this.ip = ip2;
        this.count = integer;
    }

    public int getCount() {
        return count;
    }

    public String getIp() {
        return ip;
    }

}

/**
 * @Author mubi
 * @Date 2020/5/1 12:15
 */
public class Data {

    static String fileLoc = "/Users/mubi/test_data/nums.txt";

    static int TOP_K = 10;

    static int FILE_CNT = 1024;

    /**
     * 将大文件hash成1024个小文件,顺序读写,按照IP hashCode 打散
     *
     * 则显然在单个文件中,topK的才是最终整体有可能的topK(一个文件中可能出现相同次数的,也要考虑进来)
     */
    private static void hashToSmallFiles() throws IOException {
        BufferedReader br = new BufferedReader(new FileReader(fileLoc));
        String ip;
        HashMap fileWriters = new HashMap();
        while ((ip = br.readLine()) != null) {
            int tmp = Math.abs(ip.hashCode() % FILE_CNT);
            String fileName = fileLoc + tmp + ".txt";
            FileWriter fw;
            if (fileWriters.containsKey(fileName)) {
                fw = fileWriters.get(fileName);
            } else {
                fw = new FileWriter(fileName, true);
                fileWriters.put(fileName, fw);
            }
            fw.write(ip + "\n");
        }
        br.close();
        for (FileWriter ff : fileWriters.values()) {
            ff.close();
        }
    }

    /**
     * 多线程处理
     */
    static Map> mpList = new ConcurrentHashMap<>();

    static class MyThread implements Runnable{

        int index;

        public MyThread(int index){
            this.index = index;
        }

        @Override
        public void run() {
            try {
                List list = new ArrayList<>();
                File file = new File(fileLoc + index + ".txt");
                if (!file.exists()) {
                    return;
                }
                long startTime = System.currentTimeMillis();
                BufferedReader br1 = new BufferedReader(new FileReader(file));
                String ip1;
                // 这里就是单个小文件求topK的问题了
                HashMap hm = new HashMap();
                while ((ip1 = br1.readLine()) != null) {
                    if (!hm.containsKey(ip1)) {
                        hm.put(ip1, 1);
                    }
                    else {
                        hm.put(ip1, hm.get(ip1) + 1);
                    }
                }
                TreeMap treeMap = new TreeMap<>((a,b)->{
                    return -(a-b);
                });
                // 这里使用 TreeMap 有序结构处理
                for(String ip: hm.keySet()){
                    int num = hm.get(ip);
                    treeMap.put(num, ip);
                }
                int iCnt = 0;
                for(Integer cnt: treeMap.keySet()){
                    IP ip = new IP(treeMap.get(cnt), cnt);
                    iCnt++;
                    if(iCnt > TOP_K){
                        if(iCnt == list.get(TOP_K-1).getCount()){
                            list.add(ip);
                        }else {
                            break;
                        }
                    }else {
                        list.add(ip);
                    }
                }
                long endTime = System.currentTimeMillis();
                mpList.put(index, list);
                System.out.println("已经统计文件:" + fileLoc + index + ".txt,用时:" + (endTime - startTime) + " 毫秒");
            }catch (Exception e){

            }
        }
    }
    /**
     * 利用HashMap<> 统计每个小文件频数最高的ip;
     * @return 所有小文件的结果 组成List 返回
     * @throws FileNotFoundException
     * @throws IOException
     */
    private static Map> countEverySmallFile() throws FileNotFoundException, IOException {
        try {
            Thread[] thread = new Thread[FILE_CNT];
            for (int i = 0; i < FILE_CNT; i++) {
                thread[i] = new Thread(new MyThread(i));
            }
            for (int i = 0; i < FILE_CNT; i++) {
                thread[i].start();
            }
            for (int i = 0; i < FILE_CNT; i++) {
                thread[i].join();
            }
        }catch (Exception e){

        }
        return mpList;
    }

    /**
     * 由 1024 个 topK 结果,综合起来,再求出最后的 topK
     * 综合计算TopK
     */
    private static List calculateResult(Map> mpList) {

        HashMap hm = new HashMap();
        for(Integer fileIndex: mpList.keySet()){
            List ipList = mpList.get(fileIndex);
            for(IP ip: ipList){
                if (! hm.containsKey(ip.getIp())) {
                    hm.put(ip.getIp(), ip.getCount());
                }else {
                    hm.put(ip.getIp(), hm.get(ip.getIp()) + ip.getCount());
                }
            }

        }
        TreeMap treeMap = new TreeMap<>((a,b)->{
            return -(a-b);
        });
        // 这里使用 TreeMap 有序结构处理
        for(String ip: hm.keySet()){
            int num = hm.get(ip);
            treeMap.put(num, ip);
        }
        List list = new ArrayList<>();
        int iCnt = 0;
        for(Integer cnt: treeMap.keySet()){
            IP ip = new IP(treeMap.get(cnt), cnt);
            iCnt++;
            if(iCnt > TOP_K){
                if(iCnt == list.get(TOP_K-1).getCount()){
                    list.add(ip);
                }else {
                    break;
                }
            }else {
                list.add(ip);
            }
        }
        return list;
    }

    public static void findIp() throws IOException, ClassNotFoundException {
        // 当个文件计算完
        Map> mpList = countEverySmallFile();
        // 最后统计
        List ipList = calculateResult(mpList);
        System.out.println("访问次数最多IP的top_k:" + TOP_K);
        for(IP ip: ipList){
            System.out.println(ip.getCount() + "\t" + ip.getIp());
        }
    }

    /**
     * 产生大文件
     * 每一行是一个【0,100000】之间随机整数(用来表示出现的IP)
     */
    public static void getBigFile() {
        try
        {
            File file = new File(fileLoc);
            if(!file.exists())
            {   //如果不存在则创建
                file.createNewFile();
                System.out.println("文件创建完成,开始写入");
            }
            FileWriter fw = new FileWriter(file);       //创建文件写入
            BufferedWriter bw = new BufferedWriter(fw);

            //产生随机数据,写入文件
            Random random = new Random();

            for(int i=0;i<1024*1024*1024;i++)
            {
                int randint = (int)Math.floor((random.nextDouble()*100000.0));//产生【0,10000】之间随机数
                bw.write(String.valueOf(randint));      //写入一个随机数
                bw.newLine();       //新的一行
            }
            bw.close();
            fw.close();
            System.out.println("文件写入完成");
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws Exception {

//        getBigFile();

        long startTime = System.currentTimeMillis();
//        hashToSmallFiles();
        // 约 283059
        System.out.println("hashToSmallFiles cost " + (System.currentTimeMillis() - startTime) + " 毫秒");

        startTime = System.currentTimeMillis();
        findIp();
        // 约 91043
        System.out.println("findIp cost " + (System.currentTimeMillis() - startTime) + " 毫秒");

        System.out.println("Finished");
    }
}

/*
访问次数最多IP的top_k:10
11178	39293
11143	69144
11140	29718
11133	67370
11131	40798
11130	72952
11127	57661
11122	25185
11120	93643
11118	40562
 */

总结

主要是学习分治思想,当然也是考察对CPU计算,磁盘IO,多线程,Hash / 堆 等数据结构的认识。回到问题,实践方式很多,本文只是一个参考,还请读者务必明白,后面学习大数据相关也离不开这些基础。

如果有交流的同学,请直接评论或私聊

你可能感兴趣的:(#,大数据相关,hadoop)