超大文件(1TB)统计访问次数最多的来源IP及访问次数

题目解读

1. 文件格式:访问时间,来源IP,响应结果,响应耗时

2. 文件大小:超大规模,TB数量级

解题思路

首先,数据量过大,通过内存计算肯定是不可行的。

考虑采用分治,将大文件切割成小文件,再对小文件分析,找出访问次数最多的,此时将问题转化为:切割小文件算法

具体思路如下:

将1T的文件按照IP的高8位(代码是按照高8位实现的,ipv4的高位地址不均匀,按照低8位>比较合理)分隔成2^8份。
每一份写入到文件名为"tmp_{高8位地址}"的文件中,文件中的数据为低24位的整型字符串(踢出了高8位,以方便用int类型表示)。
 开始顺序处理每个"tmp_{高8位地址}"文件:
 1. 申请一块2^24大小的int内存块(arr)
 2. 初始化内存块为0值
 3. 读取每一行数据,转换成整型值后做为下标i,将arr[i]++,表示出现了一次
 4. 重复3操作,一直到处理完整个文件为止
 5. 遍历arr,找出最大值的下标maxCountIndex
 6. 遍历arr,找出所有与最大值相同的值,将ip和出现次数写入到maxIpCountMap(用于存储每个文件中出现次数最多的IP地址及出现次数)中
 7. 重复2-6步骤,一直到所有切割文件处理完毕
 8. 遍历一遍maxIpCountMap,找出IP最大次数max(所有IP中,出现最多的次数)
 9. 重新遍历一遍maxIpCountMap,将所有出现次数等于max的IP和次数打印出来

上代码

import java.io.*;
import java.net.URISyntaxException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 所有用到的辅助数据:
 * 1. 2^8个切割文件
 * 2. 2^24个整数的数组,大小为64M
 * 3. 2^8个文件对象,用于加快查询
 * 4. 用于存储每个文件中最大值的HashMap(没有考虑极端情况,如果1T的文件中所有IP出现次数相同,这个HashMap就太大了,方案得重新调整)
 *
 * Created by ronghantao on 2019/3/4.
 */
public class TopN {
    private static int MASK_TOP8 = 0XFF000000; //获取高8位的掩码
    private static int MAST_LEFT24 = 0X00FFFFFF; //获取低24位掩码
    //ip地址的pattern
    private static String PATTERN_FILTER = "(2(5[0-5]{1}|[0-4]\\d{1})|[0-1]?\\d{1,2})(\\.(2(5[0-5]{1}|[0-4]\\d{1})|[0-1]?\\d{1,2})){3}";

    //用于存储文件,再次读取的时候,用这里的数据
    private File[] files = new File[0xFF];

    //每个分隔文件中,数值最大的存储于此
    private Map<String, Integer> maxIpCountMap = new HashMap<>();

    private static Pattern r = Pattern.compile(PATTERN_FILTER);

    /**
     * 串接整个流程
     */
    public void printTopCountList(String fileName) throws IOException, URISyntaxException {
        //开始切分文件
        this.splitFile(fileName);
        //针对每个文件,进行计算了,找出每个文件中数量最大的IP
        this.countTopN();
        //开始找出数量最多的ip列表
        if (this.maxIpCountMap.size() == 0) {
            //空的
            System.out.println("empty list.");
            return;
        }
        int max = 0;
        for (String k : this.maxIpCountMap.keySet()) {
            if (this.maxIpCountMap.get(k) > max) {
                max = this.maxIpCountMap.get(k);
            }
        }
        //开始打印所有的最大值列表了
        for (String k : this.maxIpCountMap.keySet()) {
            if (this.maxIpCountMap.get(k) == max) {
                System.out.println("ip: " + k + ", count: " + this.maxIpCountMap.get(k));
            }
        }
    }

    /**
     * 计算top1的IP地址
     */
    public void countTopN() throws IOException {
        int[] arr = new int[0x00FFFFFF];
        for (int i = 0; i < 0xFF; i++) {
            //先初始化buffer,都初始化成0
            for (int j = 0; j < arr.length; j++) {
                arr[j] = 0;
            }
            //开始处理文件
            File toProcessFile = this.files[i];
            BufferedReader r = new BufferedReader(new InputStreamReader(new FileInputStream(toProcessFile)));
            try {
                String line;
                boolean flag = false; //标识文件是否为空
                while ((line = r.readLine()) != null) {
                    //读到数据了,开始处理
                    arr[Integer.valueOf(line)]++; //直接将对应的下标+1即可
                    flag = true;//有数据了
                }
                if (flag == false){
                    continue; //没有读到数据,继续处理其他文件
                }
                //处理完后,找到本文件中最大的值的下标
                int maxCountIndex = 0;
                for (int j = 1; j < arr.length; j++) {
                    if (arr[j] > arr[maxCountIndex]) {
                        maxCountIndex = j;
                    }
                }
                //然后找出所有与此最大值相同的ip列表,写入到treeMap中
                for (int j = 0; j < arr.length; j++) {
                    if (arr[j] == arr[maxCountIndex]) {
                        //写入到treeMap,需要做IP转换
                        this.maxIpCountMap.put(this.longToIp(((long) j) + (((long) (i + 1) << 24))), arr[j]);
                    }
                }
            } finally {
                r.close();
            }
        }
    }

    public void splitFile(String fileName) throws URISyntaxException, IOException {
        //todo 文件合法性校验
        InputStreamReader inputStreamReader = new InputStreamReader(TopN.class.getClassLoader().getResourceAsStream(fileName));
        BufferedReader reader = new BufferedReader(inputStreamReader);
        BufferedWriter[] writers = new BufferedWriter[0xFF]; //保存每个文件是在什么位置,使用writer,不需要每次都打开了
        for (int i = 0; i < 0xFF; i++) {
            File f = new File("tmp_" + Integer.toHexString(i+1).toUpperCase());
            writers[i] = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(f)));
            this.files[i] = f;
        }
        try {
            String line;
            //一行一行的读文件,然后找到第一个匹配的ip地址
            while ((line = reader.readLine()) != null) {
                Matcher m = r.matcher(line);
                if (!m.find()) {
                    System.out.println(line + " not match!");
                    continue;
                }
                String ipString = m.group();
                //将大文件按照高8位的规则进行分片,存储到分片文件中
                //获取IP的分片信息
                long ipLong = this.ipToLong(ipString);
                int fileIndex = this.getTop8Int(ipLong) - 1; //下标从0开始,需要-1操作
                BufferedWriter w = writers[fileIndex];
                //将ip剩余的24位整型值写入到文件中
                w.write(String.valueOf(this.getLeft24Int(ipLong))+"\n");
            }
        } finally {
            //关闭所有文件
            for (int i = 0; i < 0xFF; i++) {
                if (writers[i] != null) {
                    try {
                        writers[i].close();
                    } catch (IOException e) {
                        //todo nothing
                    }
                }
            }
            if (reader != null) {
                reader.close();
            }
            if (inputStreamReader != null) {
                inputStreamReader.close();
            }
        }
    }

    public int getLeft24Int(long ip) {
        return (int) (ip & MAST_LEFT24);
    }

    public int getTop8Int(long ip) {
        return (int) ((ip & MASK_TOP8) >> 24);
    }

    public long ipToLong(String ipStr) {
        long result = 0;
        String[] ipAddressInArray = ipStr.split("\\.");
        for (int i = 3; i >= 0; i--) {
            long ip = Long.parseLong(ipAddressInArray[3 - i]);
            result |= ip << (i * 8);
        }
        return result;
    }

    /**
     * long转换成ip地址格式
     *
     * @param ip ip的long表示
     * @return 点分隔ip字符串
     */
    public String longToIp(long ip) {
        return ((ip >> 24) & 0xFF) + "."
                + ((ip >> 16) & 0xFF) + "."
                + ((ip >> 8) & 0xFF) + "."
                + (ip & 0xFF);
    }
}

遗留问题

对于以上算法,如果1TB的文件中,访问量最高的IP不多,不会出现问题,否则,也会占用较大的内存。

posted @ 2019-03-18 12:25  ronghantao  阅读(2115)  评论(0编辑  收藏  举报