几天前遇到这么一个问题:有一个文件记录大量用户的上网记录,要求合并这些信息统计出每个用户的上网时间段。文件中的记录如下:
用户1 开始时间9:15结束时间9:18
用户3 开始时间8:22结束时间8:45
用户1 开始时间9:10结束时间9:16
用户2 开始时间8:31结束时间8:52
用户1 开始时间10:23结束时间10:47
用户1 开始时间9:22结束时间9:38
.......
计算结果是:
用户1 (9:10-18 9:22-9:38 10:23-10:47)
用户2 (8:31-8:52)
用户3 (8:22-8:45)
文件中的记录数据量很大几个亿,用户数量和每个用户的记录都很多,时间是只包含一天中的时间,或许还会要求精确到秒级别,我们以分钟为单位讨论。
解决方案:
按照用户将此问题分解为合并一个用户的上网时间段,每个时间段转化成当天的分钟数(如果精确到秒则转化成当天的秒数)如14:45 是14*60+45=885分钟;或者15:32:21是(15*60+32)*60+21=55941秒。
这样就将问题转化成合并数值段问题最数值为1440-1(精确到分钟)或者86400-1(精确到秒)毫秒类似。这样把问题转化类似成在一个数值范围内的有限数值去重的问题。可以很快想到很多解决方案,用集合,数组,排序等等。
二进制存储结构保存结果:
Java中一个int型数值是32位,我们用一个int数值可以保存32个结果,如时间段是5-27我们将int数值从低5位到27为置1,即使0x07 FF FF F0(0000 0111 1111 1111 1111 1111 1111 0000),因为我们最大值是(86400-1)一个int数值明显不够,可以采用int数组a = int[2700]来保存(一个int可以保存32个结果,86400个只需要2700个int即可)。a[0]记录0-31的结果a[1]记录32-63的结果...依次类推。
数值初始化为0,每处理一个数值段的时候(8776-9844)只需要将数组中对应的位置置1即可如下面的putNum函数,处理完所有数据后,再将结果转化成一个一个时间段就是我们需要的结果如下面的print函数。
int[] a = new int[10000];
public static void putNum(int start,int stop){
int atStart = start>>5;
int begin = start<<27>>>27;
int atStop = stop>>5;
int end = stop<<27>>>27;
for(int i = atStart+1; i < atStop; i++){
a[i] = 0xFFFFFFFF;
}
if(atStart == atStop) {
a[atStart] = a[atStart] | (0xFFFFFFFF << begin & 0xFFFFFFFF >>> 32-end);
} else {
a[atStart] = a[atStart] | (0xFFFFFFFF << begin);
a[atStop] = a[atStop] | (0xFFFFFFFF >>>31-end);
}
}
public static void print(){
boolean currStateIsStart = false;
for(int i = 0; i <a.length; i++){
int num = 1;
for(int j = 0; j < 32; j++){
if((a[i] & num) != 0){
if(!currStateIsStart){
currStateIsStart = true;
System.out.print("开始:" + ((i << 5) + j));
}
} else {
if(currStateIsStart){
currStateIsStart = false;
System.out.println("结束:" + ((i << 5) + j - 1));
}
}
num = num<<1;
}
}
if(currStateIsStart){
System.out.println("最后结束:" + (a.length << 5 - 1));
}
}