需求简介
需要在页面中上展示该用户本月的签到情况,已经签到过的日期就要做一个标记。
如下图
这里主要解决的是数据存储和读取的问题。
数据结构
简单解决方案
简单思考的话很好解决,可以建议使用下面的表结构来实现(去除非必要字段)
字段名 | 字段类型 | 说明 |
---|---|---|
user_id | int | 用户 |
month | int | 所属月份 |
day | int | 签到日 |
这样的话简单明了,但是有一个问题,一个用户一个月就是30条数据,50万月度活跃用户(设计目标)一年就能产生 500000*30*12=180,000,000 ,也就是1.8亿条数据……
哈哈,看上去能行,实际上,呃……
进阶解决方案
上面每一次签到都记录一次数据,看上去精细(可以保存每一次操作的创建时间之类的),但是实际保存了签到的日就行了,这个设计肯定会有非常多的冗余数据的。
首先一个月最多31天,可以理解为这里只要一个长度为31的boolean的数组,序号代表日期,boolean值代表是否已经签到。
其次,boolean类型的本质不就是 01嘛!
ok了,问题解决了!
那就是一个长度为31的byte数组了!
31位的byte数组,那还不如32呢,为啥?32位直接可以转换成一个int值啊!
妙!
说干就干,数据结构可以简单变一下就行了
字段名 | 字段类型 | 说明 |
---|---|---|
user_id | int | 用户 |
month | int | 所属月份 |
day | int | 签到日集合 |
只要每次展示的时候解析day这个代表当月签到日集合的Int数值,就可以知道当月的已签到日,当月总计签到日。
按照50万月度活跃用户预估,一年能产生 500000*1*12=6000,000 ,比之前肯定是减小一个数量级了。
稳,优雅永不过时!
关键代码实现
public class DateUtils {
/**
* 指定日期签到
*
* @param sign 已签到的集合
* @param day 需要添加的签到日期 1,2,3...
* @return
*/
public static Integer sign(Integer sign, Integer day) {
return sign | 1 << day;
}
/**
* 检查指定日期是否已签到
*
* @param sign
* @param day
* @return
*/
public static boolean checkSign(Integer sign, Integer day) {
return 0 != (sign & 1 << day);
}
/**
* 统计本月已签到次数
*
* @param sign
* @return
*/
public static Integer countSign(Integer sign) {
int count = 0;
while (sign != 0) {
count += (sign & 1);
sign = sign >>> 1;
}
return count;
}
/**
* 获取以前到的日期列表
*
* @param sign
* @return
*/
public static List<String> getSignDays(int sign) {
List<String> result = new ArrayList<String>();
int day = 0;
while (sign != 0) {
day++;
sign = sign >>> 1;
if (1 == (sign & 1)) {
result.add(String.valueOf(day));
}
}
return result;
}
public static void main(String[] args) {
Integer defaultSign = 0;
Integer sign = sign(defaultSign, 2);
sign = sign(defaultSign, 2);
Assert.isTrue(countSign(sign) == 1);
Assert.isTrue(checkSign(sign, 2));
sign = sign(sign, 13);
Assert.isTrue(!checkSign(sign, 12));
Assert.isTrue(checkSign(sign, 2));
Assert.isTrue(checkSign(sign, 13));
Assert.isTrue(!checkSign(sign, 12));
Assert.isTrue(countSign(sign) == 2);
for (int i = 0; i <= 31; i++) {
sign = sign(sign, i);
}
System.out.println(getSignDays(sign).toString());
}
}