最近公司有一个需求:遍历网站所有用户,分几个指标对用户进行划分,并对其进行一些操作,其中这些指标中就有一个是用户最近三个月的登录情况。

 如果网站用户量大,且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<String, Integer> datePool = new HashMap<String, Integer>();
    private static final Map<Integer, String> dayPool = new HashMap<Integer, String>();
    private static final Map<String, Integer> monthPool = new HashMap<String, Integer>();
    private static final Map<String, Integer> dayOfMaonthPool = new HashMap<String, Integer>();
    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<String> dates);

    /**
     * 登录记录由int数组到List
     * 
     * @param actions
     * @return
     */
    public abstract List<String> 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<String> dates) {
            int[] actions = new int[nums];
            for (String date : dates) {
                login(actions, date, true);
            }
            return actions;
        }

        @Override
        public List<String> loginDates(int[] actions) {
            List<String> dates = new ArrayList<String>();
            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<String> 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<String> 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);
                    }
                }
}