数据样例:
access.log:电信运营商的用户上网数据:
20090121000132095572000|125.213.100.123|show.51.com|/shoplist.php?phpfile.......
20090121000132406516000|117.101.222.68|gg.xiaonei.com|/view.jsp?p=389|Mo.......
ip.txt:ip地址和归属地的规则数据:
1.0.8.0|1.0.15.255|16779264|16781311|亚洲|中国|广东|广州||电信|440100|China|CN|113.280637|23.125178
1.0.32.0|1.0.63.255|16785408|亚洲|中国|广东|广州||电信|440100|China|CN|113.280637|23.125178
1.1.0.0|1.1.0.255|16842752|16843007|亚洲|中国|福建|福州||电信|350100|China|CN|119.306239|26.075302
需求:
通过计算access.log中的用户行为数据,统计出各个省份访问量(一次请求记作一次独立的访问量),并按照各个省份的访问量的从高到低进行排序。
分析:
根据.Log文件数据获取所有的ip数据,根据ip去ip的规则数据中查询区域或者运营商数据,因为在规则数据中,每个城市/省份的IP都在一个区段范围内,因此需要先通过位运算将IP转为长整型数字,在使用二分查找进行位置判断。
1 读取ip规则数据 将每行数据封装成javabean对象 。把所有的数据(不存在映射关系)封装在list中,便于后期需求更改。
2 在遍历用户上网数据的IP的同时,将IP转换成长整型,并使用二分查找算法找寻所属区段
3 创建一个新的Map集合,将省份作为key值,将上网次数作为value值,存储结果信息。
public class ProviceFlowCount {
public static void main(String[] args) throws Exception {
//建立List容器,存储ip地址与省份封装的对象信息
ArrayList<IpBean> arrayList = new ArrayList<>();
//创建缓冲字符流,读取文件
BufferedReader br = new BufferedReader(new FileReader("F:\\多易大数据培训\\ip.txt"));
String str;
while((str=br.readLine())!=null) {
try {
String[] split = str.split("\\|");
long upIp = Long.parseLong(split[2]);
long downIp = Long.parseLong(split[3]);
String provice = split[6];
String city = split[7];
//这里会不断的new
arrayList.add(new IpBean(upIp,downIp,provice,city));
}catch(NumberFormatException e) {
continue;
}
}
//创建缓冲字符流,读取文件。并将IP转为长整型
BufferedReader br1 = new BufferedReader(new FileReader("F:\\access.log"));
//创建Map集合用于存储最后的结果
Map<String,Integer> map = new HashMap<>();
String str1;
while((str1=br1.readLine())!=null) {
String[] split = str1.split("\\|");
String[] split2 = split[1].split("\\.");
//将ip的点十进制表示形式转为长整型
long ip = 0;
for(int i=0;i<4;i++) {
long b = Long.parseLong(split2[i]);
long temp = b<<((3-i)*8); //转成10进制整型后可以直接进行位运算。
ip += temp;
}
//使用二分查找算法
int start = 0;
int end = arrayList.size()-1;
while(start <= end) {
int middleIndex = (start + end)/2 ;
IpBean bean = arrayList.get(middleIndex);
long startIp = bean.getUpIp();
long endIp = bean.getDownIp();
if(ip<startIp) { //小于下限,则往前找
end = middleIndex-1;
}else if(ip>endIp){ //大于上限,则往后找
start = middleIndex+1;
}else {
String provice = bean.getProvice();
int count = map.getOrDefault(provice, 0);
map.put(provice, count+1);
//最差的情况会在start=end时满足要求,此时end与start的值并不会变化,需要我们手动的跳出循环
break;
}
}
}
//集合排序有两种方式:Collections.sort()与List集合中的sort方法,因此需要先将Map转为List。
Set<Entry<String, Integer>> entrySet = map.entrySet();
ArrayList<Entry<String, Integer>> arrList2 = new ArrayList<>(entrySet);
arrList2.sort((o1,o2)->o2.getValue()-o1.getValue());
System.out.println(arrList2);
}
}
注意事项:
每一个区域的IP都是一个字段,里面包含大量的IP地址。因此区分IP属于哪一个区域可以将IP转为一个长整型数据,比较是否在IP的区段范围内...
那么具体怎么在一个有序的IP地址文件中进行查询操作呢: 二分查找。二分查找算法的时间复杂度是不稳定的,但是要快于普通的暴力查询。
存储的时候不能使用Map集合了,因为Map 的键值对是唯一的,而这里IP的区段是一个范围。
编码小记:
JavaWeb中默认创建的编码格式:iso8859-1
简体中文Windows默认使用GBK编码
UTF-8:基于Unicode字符集,能表示世界上所有具有计算机的语言、符号
UTF-8 BOM:会在文本开头添加3个字节的BOM信息,这是UTF-8编码的描述信息,很多余,文本编辑器显示时会自动忽略。
ANSI:本地编码(比如简体windows的本地编码是GBK)
eg:
UTF-8的汉字编码格式:1110 xxxx 10xx xxxx 10xx xxxx 3个字节
文 0x6587 unicode编码 占三个字节
0110 0101 1000 0111 :两字节
1110 0110 1001 0110 1000 0111 :三字节