最近公司有一个需求:遍历网站所有用户,分几个指标对用户进行划分,并对其进行一些操作,其中这些指标中就有一个是用户最近三个月的登录情况。
如果网站用户量大,且lv较高的话,遍历用户select一个log库,必然低效;有的人提出用一个字串进行表示,比如0010表示今天未登录,昨天登录,倒退两天都未登录,这种方式可行,但是如果记录90天,每个用户都必须记录一个长度为90的varchar,而且用java操作String比较复杂,也相当消耗内存;而我的想法就是二进制进行记录。
我最开始想设计一个表,uid为主键,一个×××字段来表示用户行为:昨天登录,今天登录即为11B=3;昨天登录,今天未登录即为10B=2;昨天未登录,今天登录即为01B=1;昨天未登录,今天未登录即为00B=0,这样数据库中只需要存储一个整数就OK了。
但是在java中一个long才64位,怎样才能记录90天甚至更长时间呢?没用过bigint,不知道其效率如何,我后来想了一个办法,用int数组,如果数组的长度设置成12或者更多,甚至都能精确记录用户一年的行为,经过一番研究修改与优化,最终终于做出来一套算法了,问题解决了,但缺点是代码可读性差,不知道符不符合算法的设计原理
以下就是代码
一、定义操作方法:
package com.dajie.centre.mail.utils;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public abstract class ActionService {
protected static final long A_DAY = 24 * 3600 * 1000L;
protected static final long EIGHT_HOUR = 8 * 3600 * 1000L;
private static final Map datePool = new HashMap();
private static final Map dayPool = new HashMap();
private static final Map monthPool = new HashMap();
private static final Map dayOfMaonthPool = new HashMap();
private static final String FORMAT = "yyyy-MM-dd";
protected String statDay;
protected int statDayOf;
protected int nums;
/**
* 记录登录
*
* @param actions
* @param date
* @param isLog
* @return
*/
public abstract int[] login(int[] actions, boolean isLog);
/**
* 记录登录
*
* @param actions
* @param date
* @param isLog
* @return
*/
public abstract int[] login(int[] actions, String date, boolean isLog);
/**
* 登录记录由List到int数组
*
* @param actions
* @param date
* @param isLog
* @return
*/
public abstract int[] login(Collection dates);
/**
* 登录记录由int数组到List
*
* @param actions
* @return
*/
public abstract List loginDates(int[] actions);
/**
* 登录记录由字符串到数组的转换
*
* @param actions
* @param date
* @param isLog
* @return
*/
public int[] login(String actionStrs) {
String[] actionStr = actionStrs.split(",");
int length = actionStr.length;
int[] actions = new int[length];
for (int i = 0; i < length; i++) {
actions[i] = Integer.parseInt(actionStr[i]);
}
return actions;
}
/**
* 登录记录由数组到字符串的转换
*
* @param actions
* @param date
* @param isLog
* @return
*/
public String loginStr(int[] actions) {
int length = actions.length;
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
sb.append(actions[i]);
if (i != length - 1) {
sb.append(",");
}
}
return sb.toString();
}
/**
* 查看最近几天内的登录次数
*
* @param actions
* @param days
* @return
*/
public abstract int loginCounts(int[] actions, int startDay, int endDay, int weekend);
/**
* 查看是否是周末
*
* @param dayOf
* @return
*/
protected boolean isWeekend(int dayOf) {
return dayOf % 7 == 3 || dayOf % 7 == 2;
}
/**
* 查看日期距离1970-01-01有多少天
*
* @param date
* @return
*/
protected int getDayOf(String date) {
date = date.trim();
Integer dayOf = datePool.get(date);
if (dayOf == null) {
try {
Date day = new SimpleDateFormat(FORMAT).parse(date);
dayOf = ((int) ((day.getTime() + EIGHT_HOUR) / (A_DAY)));
datePool.put(date, dayOf);
dayPool.put(dayOf, date);
} catch (ParseException e) {
e.printStackTrace();
}
}
return dayOf;
}
/**
* 查看日期距离1970-01-01有多少天
*
* @param date
* @return
*/
protected String getDateOf(int dayOf) {
String date = dayPool.get(dayOf);
if (date == null || "".equals(date)) {
Date day = new Date(A_DAY * dayOf + EIGHT_HOUR);
date = new SimpleDateFormat(FORMAT).format(day);
datePool.put(date, dayOf);
dayPool.put(dayOf, date);
}
return date;
}
/**
* 查看日期的月份
*
* @param date
* @return
*/
public int getMonth(String date) {
date = date.trim();
Integer month = monthPool.get(date);
if (month == null) {
try {
Date day = new SimpleDateFormat(FORMAT).parse(date);
Calendar cal = Calendar.getInstance();
cal.setTime(day);
month = cal.get(Calendar.MONTH);
monthPool.put(date, month);
dayOfMaonthPool.put(date, cal.get(Calendar.DAY_OF_MONTH) - 1);
} catch (ParseException e) {
e.printStackTrace();
}
}
return month;
}
/**
* 查看日期是一个月的第多少天
*
* @param date
* @return
*/
public int getDayOfMonth(String date) {
date = date.trim();
Integer dayOfMonth = dayOfMaonthPool.get(date);
if (dayOfMonth == null) {
try {
Date day = new SimpleDateFormat(FORMAT).parse(date);
Calendar cal = Calendar.getInstance();
cal.setTime(day);
dayOfMonth = cal.get(Calendar.DAY_OF_MONTH) - 1;
monthPool.put(date, cal.get(Calendar.MONTH));
dayOfMaonthPool.put(date, dayOfMonth);
} catch (ParseException e) {
e.printStackTrace();
}
}
return dayOfMonth;
}
}
二、新建一个工厂类,添加一些操作功能方法,并实现这个接口:
package com.dajie.centre.mail.utils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class ActionFactory {
public static ActionService getInstance(String date) {
return new Action1(date);
}
private static class Action1 extends ActionService {
private int month = 0;
private int days = 0;
private Action1(String date) {
this.statDay = date;
this.statDayOf = getDayOf(date);
this.month = getMonth(date);
this.days = getDayOfMonth(date);
this.nums = 12;
}
@Override
public int[] login(int[] actions, boolean isLog) {
actions[month] = isLog ? actions[month] | (1 << days) : actions[month] & ~(1 << days);
return actions;
}
@Override
public int[] login(int[] actions, String date, boolean isLog) {
int thatDayOf = getDayOf(date);
if(statDayOf >= thatDayOf && statDayOf - thatDayOf < 366){
int thatMonth = getMonth(date);
int thatDayOfMonth = getDayOfMonth(date);
actions[thatMonth] = isLog ? actions[thatMonth] | (1 << thatDayOfMonth) : actions[thatMonth] & ~(1 << thatDayOfMonth);
}
return actions;
}
@Override
public int[] login(Collection dates) {
int[] actions = new int[nums];
for (String date : dates) {
login(actions, date, true);
}
return actions;
}
@Override
public List loginDates(int[] actions) {
List dates = new ArrayList();
int dayOf = getDayOf(statDay);
for (int i = 0; i < 366; i++) {
String date = getDateOf(dayOf);
int month = getMonth(date);
int dayOfMonth = getDayOfMonth(date);
if ((actions[month] & (1 << dayOfMonth)) > 0) {
dates.add(date);
}
dayOf--;
}
return dates;
}
@Override
public int loginCounts(int[] actions, int startDay, int endDay, int weekend) {
int logCnt = 0;
int days = endDay - startDay;
int dayOf = getDayOf(statDay) - startDay;
boolean once = false;
for (int i = 0; i < days; i++) {
String date = getDateOf(dayOf);
int month = getMonth(date);
int dayOfMonth = getDayOfMonth(date);
if ((actions[month] & (1 << dayOfMonth)) > 0) {
if (isWeekend(dayOf)) {
if (weekend == 1 && !once) {
logCnt++;
once = true;
}else if(weekend == 2){
logCnt++;
}else{
System.out.println(date);
}
} else {
logCnt++;
once = false;
}
} else {
once = false;
}
dayOf--;
}
return logCnt;
}
}
static void print(List list) {
StringBuilder sb = new StringBuilder("dates : [");
int length = list.size();
for (int i = 0; i < length; i++) {
sb.append(list.get(i));
if (i != length - 1) {
sb.append(", ");
}
}
sb.append("]");
System.out.println(sb);
}
}
三、测试代码:
测试一:
while (true) {
synchronized (RunningThread.class) {
uids = kpiDemoDao.getLoginUid(maxUid, ONCE);
if (!uids.isEmpty()) {
int maxTmpUid = Collections.max(uids);
maxUid = maxUid < maxTmpUid ? maxTmpUid : maxUid;
}
}
if (uids != null && !uids.isEmpty()) {
for (int uid : uids) {
List dates = kpiDemoDao.getLoginLog(uid);
int size = dates.size();
if (size > 0) {
int[] actions = actionService.login(dates);
String lastDate = dates.get(size - 1);
emailCentreDao.deleteAction(uid);
emailCentreDao.insertAction(uid, actions, lastDate);
}
}
} else {
break;
}
}
测试二:
int month = actionService.getMonth(date);
int[] actions = new int[12];
actionService.login(actions, true);
log.info("size : " + uids.size());
for (int uid : uids) {
if (uid%THREAD == this.id) {
int update = emailCentreDao.updateAction(uid, month, actions[month], date);
if (update <= 0) {
emailCentreDao.insertAction(uid, actions, date);
}
}
}