關於用戶行爲統計算法嘗試(java)

 最近公司有一個需求:遍歷網站所有用戶,分幾個指標對用戶進行劃分,並對其進行一些操作,其中這些指標中就有一個是用戶最近三個月的登錄情況。

 如果網站用戶量大,且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);
                    }
                }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章