超大文件(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不多,不会出现问题,否则,也会占用较大的内存。

原文地址:https://www.cnblogs.com/ronghantao/p/10551432.html

时间: 2024-11-03 14:26:02

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

找出字符串中出现次数最多的字符,和最大次数

/*找出字符串中出现次数最多的字符,和最大次数*/ function countMax(str){ var max = 0; // 记录出现的最大次数 var maxChar = ""; // 记录出现最多次数的字符 var counts = new Array(127); // 记录中间计算结果 for(var i = 0; i < counts.length; i++){ counts[i] = 0; } for(var i = 0; i < str.length; i

JS中判断字符串中出现次数最多的字符及出现的次数

1 <script type="text/javascript"> 2 var str = 'qwertyuilo.,mnbvcsarrrrrrrrtyuiop;l,mhgfdqrtyuio;.cvxsrtyiuo'; 3 var json = {}; 4 //遍历str拆解其中的每一个字符将其某个字符的值及出现的个数拿出来作为json的kv 5 for (var i = 0; i < str.length; i++) { 6 //判断json中是否有当前str的值

查找输入字符串中出现字符次数最多的那个字和重复次数

public class chongfu { //找出字符串中重复次数最多的那个字符: /** * public static void main(String[] args){ System.out.println("请输入字符串"); Scanner a = new Scanner(System.in); String b=a.nextLine(); int count=0; int d=0; char t = 0; for(int i=0;i<b.length();i++)

我的Java开发学习之旅------&gt;求字符串中出现次数最多的字符串以及出现的次数

金山公司面试题:一个字符串中可能包含a~z中的多个字符,如有重复,如String data="aavzcadfdsfsdhshgWasdfasdf",求出现次数最多的那个字母及次数,如有多个重复的则都求出. 此题的解题思路如下: 引入TreeSet:通过集合快速找到所有出现过的字符串 引入ArrayList:为了快速排序,再通过StringBuffer生成排序后的字符串 通过String的indexOf方法和lastIndexOf方法来计算每个字符串出现的次数最大值 使用HashMap

查找字符串中出现次数最多的字母和出现的次数. 示例: var str =&quot; fdadffddfdffj&quot;; ====&gt; 出现次数最多的是f, 出现了 6 次;

<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Title</title> <script> var a = 'abcdeeffffffjjjjjjjjjjj'; var b = {}; for (var i = 0;i < a.length;i++){ if (!b[a[i]]) { b[

grunt默认只允许localhost和访问,如何设置外部IP地址访问

使用Yeoman生成器创建web项目,使用grunt server启动,默认访问地址为127.0.0.1:9000或者localhost:9000 如果用本机地址如:192.168.1.100:9000访问默认是访问不到的 想要通过IP地址访问需要修改Gruntfile.js的配置: 修改connect节点配置,原本的配置如下: 可以看到hostname上面有注释,大概意思是:将地址改为'0.0.0.0'可从外部访问. 修改成下图,我们的grunt server就可以从外部访问啦!

使用域名访问,禁止通过服务器ip地址访问,过滤客户端ip

httpd-vhosts.conf <VirtualHost *:80> DocumentRoot "d:/wampwww/" ServerName  xxx.xxx.xxx.xxx <Directory /> Options FollowSymLinks #不许可别人修改我们的页面 AllowOverride None #设置访问权限 Order allow,deny Deny from All </Directory> </VirtualH

Tomcat--配置tomcat,使其除了接受本地访问外,拒绝其他 IP 的访问

解决方案:修改server.xml文件,在</host>前添加代码: <Valve className="org.apache.catalina.valves.RemoteAddrValve" allow="127.0.0.1" deny=""/> 重启tomcat,即可

面试笔试-脚本-1:使用shell脚本输出登录次数最多的用户

原题目: 一个文本类型的文件,里面每行存放一个登陆者的IP(某些行是重复的),写一个shell脚本输出登陆次数最多的用户. 之前刚看到这个题目时,马上没有想到一行直接解决的办法,虽然知道可以先进行排序,但是后面由于对uniq命令的参数不熟悉,所以用了比较背的办法,就是直接编写shell脚本程序来解决这个问题. 现在假设测试数据如下: 111.111.111.111 10.10.10.10 222.222.222.222 111.111.111.111 333.333.333.333 10.10.