目录
数据样例:
手机号段规则:1300000为号段,山东为所属省份,济南是所属城市,联通是所属运营商
prefix phone province city isp post_code city_code area_code
130 1300000 山东 济南 联通 250000 0531 370100
130 1300001 江苏 常州 联通 213000 0519 320400
130 1300002 安徽 巢湖 联通 238000 0551 340181 ......
登陆网站流量:13026230503为手机号,提取域名baidu.com,20为上行流量,5000为下行流量
13026230503 http://v.baidu.com/tv 20 5000
13826544101 http://www.weibo.com/?category=7 20 5000
13926435656 http://v.baidu.com/tv 20 5000 ......
需求:
- 计算出用户上网流量总流量(上行+下行)最高的的网站Top3,
- 根据给的的手机号段归属地规则,计算出总流量最高的省份Top3
- 根据给的的手机号段运营商规则,计算出总流量最高的运营商Top3
分析:
需要从两张表中提取数据,因此可以将1300000的手机号前七位作为两张表之间的外键连接,同时手机号段表中的属性很多,可以将它封装成一个javaBean(根据javabean规范封装的一个类,此处不做说明),将它的对象作为value值存入Map中,将手机号前七位作为key值。这样可以在比较时分别提取不同属性进行对比。同时在域名的提取上,我们需要做一些判断,因为有些数据是脏数据。这些我们在遇到时就可以直接try...catch配合continue清洗掉。最后做域名切分时的一些小技巧见下述代码。
另外域名切割可以使用正则表达式.......
1.计算出用户上网流量总流量(上行+下行)最高的的网站Top3
先创建一个Map容器,将域名作为key存储,将上行流量与下行流量的和作为value存储。这样查找速度比每次使用IO流从头读取的速度要快的多。
public class WebFlowCountDemo {
public static void main(String[] args) throws Exception {
//创建Map容器,存储:用户 + 总流量
Map<String,Integer> map = new HashMap<>();
//创建缓冲字符流,读取数据集。参数也可以是转换流,这样可以设定编码格式
BufferedReader br = new BufferedReader(new FileReader("F:\\http.log"));
String str;
while((str=br.readLine())!=null) {
String[] s = str.split("\\s"); //使用空白字符进行分割,可以代替制表符,空格
String url = s[1]; //取出域名
int upFlow = Integer.parseInt(s[2]); //取出上行流量的整型数字
int downFlow = Integer.parseInt(s[3]); //取出下行流量的整型数字
int totalFlow = upFlow + downFlow; //总流量
String[] tempArr = url.split("\\."); //实心点具有特殊含义,需要使用转义字符
int index = 0;
//不符合预期格式的数据,称为脏数据,直接使用try...catch语句清洗掉(忽略掉)。
try {
index = tempArr[2].indexOf("/"); //Indexof(待查元素):存在时返回索引位置,元素不存在的时候返回-1
}catch(ArrayIndexOutOfBoundsException e) {
continue; //跳出本次异常,清洗掉脏数据
}
if(index != -1) { //若符号存在,则截取字符串
tempArr[2] = tempArr[2].substring(0, index);
}
String domainName = tempArr[1] + "." + tempArr[2]; //拼接域名
//方式一:拿到域名后,检测map的key是否有该域名
boolean bool = map.containsKey(domainName);
//如果没有,以域名为key,流量为value,存到map中
if(!bool) {
map.put(domainName, totalFlow);
}
//如果有,以域名为key,取出value,把流量加到value上,再存到map中
else {
int flow = map.get(domainName) + totalFlow;
map.put(domainName, flow);
}
//方式二:
int flow = map.getOrDefault(domainName, 0);
map.put(domainName, flow + totalFlow);
}
}
}
2.根据给的的手机号段归属地规则,计算出总流量最高的省份Top3
本次需求需要对两个表进行数据提取,连接点就是手机号前七位。由于手机号段归属地信息容量很大,需要先将其封装成javaBean对象,再作为value值存储进Map1中,这样可以便于处理后期需求的变动,以及提高查询速度。然后再创建一个Map2容器,用于存储提取后的省份与总流量信息。具体是在遍历登录网站流量的手机号时,判断Map2容器中是否存在该手机号key值,如果不存在则该手机号对应的省份作为key的初始值存入,将其对应的总流量作为value的初始值存入。如果存在则以省份为key,取出value,然后把当前流量加到value中,之后在存进Map2中。
public class ProvinceFlowDemo {
static Map<String,ProvinceBean> map; //全局变量
static {
map = new HashMap<>();
}
public static void main(String[] args) throws IOException {
getPhoneInfo();
Map<String,Integer> temp = countProvinceFlow();
//将Map对象转为ArrayList对象,便于使用Collections.sort()或者List.sort()方法进行排序
Set<Entry<String, Integer>> entrySet = temp.entrySet();
ArrayList<Entry<String, Integer>> arrayList = new ArrayList<>(entrySet);
//使用Stream的方式可以很容易的得到Top3省份
arrayList.stream().sorted((o1,o2)->o2.getValue()-o1.getValue());
arrayList.stream().limit(3).forEach(System.out::println);;
}
//将手机号段归属地规则信息存储进Map中
public static void getPhoneInfo() throws IOException {
//使用缓冲字符流存储信息:转换流可以设定以那种编码格式进行转换
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("F:\\多易大数据培训\\java数据分析与web\\day01\\案例分析资料\\手机号段规则.txt")));
String content;
br.readLine(); //第一行是字段名,忽略掉
while((content=br.readLine())!=null) {
String[] split = content.split("\\s");
String phonePrefix = split[1];
String province = split[2];
String city = split[3];
ProvinceBean pro = new ProvinceBean(phonePrefix,province,city);
//将bean对象存入map中
map.put(phonePrefix, pro);
}
}
//读取用户上网流量信息,并与上一个Map集合产生关联
public static Map<String,Integer> countProvinceFlow() throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("F:\\多易大数据培训\\java数据分析与web\\day01\\案例分析资料\\http.log")));
//创建新的Map对象,用于存储提取的省份+总流量信息
Map<String,Integer> flowMap = new HashMap<>();
String str;
while((str=br.readLine())!=null) {
String[] split = str.split("\\s");
//截取号码前七位
String phonePrefix = split[0].substring(0, 7);
int upFlow = Integer.parseInt(split[2]);
int downFlow = Integer.parseInt(split[3]);
int totalFlow = upFlow + downFlow;
//以手机号前7位为key,在map中获取value,然后从Bean中取得省份/城市等
ProvinceBean bean = map.get(phonePrefix);
String province = bean.getProvince();
//以省份为key,检测map中是否包含这个省份
//如果没有,以省份为key,流量为value,存入map
//如果有,以省份为key,取出value,然后把流量加到value中
Integer flow = flowMap.getOrDefault(province, 0);
flowMap.put(province, flow + totalFlow);
}
return flowMap;
}
}
注意事项:
- 对于切分时,使用空白字符“\\s”代表制表符与空格:正则表达式需要转义,即有特殊含义的字符需要转义
- 检索日志的时候可以添加计数器(编码阶段),对于脏数据可以准确的定位。同时使用try…catch语句处理异常(清洗掉脏数据),利用continue跳出本次循环。
- Indexof(待查元素):元素不存在的时候返回-1。
- 可以自定义几条数据用于验证结果集
- 使用IO流进行逐行读取速度很慢,可以直接将每行的信息封装成一个javaBean对象,存入Map中,这样检索速度会很快。