一、思路
使用位操作。思路很简单,一个月最多31天,四个字节有32位,每个bit位代表一天,签到则置为1,默认是0代表没签到。
二、代码
public class SigningManager { /** * 使用一个32位的int型整数表示签到状态,每个bit位代表月份中的一天,当天签到了则置1 * 只需使用4byte就能保存一个月的签到状态信息 * redis有相同实现 */ private int element; public SigningManager() { this.element = 0; } /** * 当天签到 * * @return 签到成功true, 失败false */ public boolean signToday() { int dayOfMonth = Calendar.getInstance().get(Calendar.DAY_OF_MONTH); int old = this.element; element |= 1 << (dayOfMonth - 1); return element != old; } /** * 查询当月某天是否签到 * * @param dayOfMonth 当月第几天 * @return 签到返回true, 否则false */ public boolean getSignStatus(int dayOfMonth) { return (element & (1 << (dayOfMonth - 1))) != 0; } /** * @return 当月签到次数 */ public int getSignDays() { return Integer.bitCount(element); }
三、代码解析
1.使用一个int型变量element保存一个月的签到状态,初始值是0,此时32个bit位上都是0,假设今天是当月的第7天,签到后则将第七个bit位设为1。怎么做?1<<(dayOfMonth-1),用1(二进制...0000001)左移6(7-1)位,变为:...001000000,这步操作相当于定位了第七天,最后将element与...001000000取或操作。什么是取或,即两个bit位只要有一个是1结果即为1,全部是0则为0,完全等同于两个Boolean做“||”运算,现在...001000000在第七位已经是1了,其他位都是0,故取或后element的第七位也会变成1,其他位保持不变,这样我们就成功签到了。
2.获取第n天的签到状态与上述类似,先通过1<<(dayOfMonth-1)定位到第dayOfMonth天,然后与element取与操作,如果未签到,第dayOfMonth位是0,取与后变为0,说明未签到,反之说明已经签到了。
3.其实redis已经为我们实现了类似的功能。redis中有个redis.setBit(key offset value)的方法,签到可以这样做:redis.setBit(sign:user_id,7,1),这就代表user_id的用户第七天签到了,这个方法的好处就是极大的减少占用的内存,因为我们只需要一个bit位就能代表一个签到状态。继续发散,使用bit操作还能实现海量数据排重,怎么说?假如一篇文章需要实时显示阅读量,而同一个用户多次阅读只增加一次阅读量,这样你是不是要维护一个已经阅读过该文章的用户集合呢?用户量小时尚且没问题,一旦数据量大了问题就棘手了,一个可行的方案是使用bit操作对已阅读用户进行状态标记,具体细节就不赘述了。