網(wǎng)上有很多關(guān)于pos機簽到255,使用位圖算法來優(yōu)化簽到歷史數(shù)據(jù)的空間占用的知識,也有很多人為大家解答關(guān)于pos機簽到255的問題,今天pos機之家(www.tjfsxbj.com)為大家整理了關(guān)于這方面的知識,讓我們一起來看下吧!
本文目錄一覽:
pos機簽到255
前言
實際開發(fā)中有這樣的場景,用戶每日簽到,可獲取相對應(yīng)的積分贈送,如果連續(xù)簽到,則可獲得額外的積分贈送。
本文主要講解使用位圖算法來優(yōu)化簽到歷史記錄的空間占用。當(dāng)然如果業(yè)務(wù)中僅僅是獲取連續(xù)簽到的最大天數(shù),使用一個計數(shù)器即可記錄。
需求:
1.記錄一年的簽到歷史
2.獲取某月的簽到歷史
3.獲取過去幾天連續(xù)簽到的最大天數(shù)
位圖算法實現(xiàn)思路
一天的簽到狀態(tài)只有兩種,簽到和未簽到。如果使用一個字節(jié)來表示,就需要最多366個字節(jié)。如果只用一位來表示僅需要46(366/8 = 45.75)個字節(jié)。
位圖算法最關(guān)鍵的地方在于定位。 也就是說數(shù)組中的第n bit表示的是哪一天。給出第n天,如何查找到第n天的bit位置。
這里使用除法和求余來定位。
比如上圖
第1天,index = 1/8 = 0, offset = 1 % 8 = 1 ,也就是第0個數(shù)組的第1個位置(從0開始算起)。
第11天,index = 11/8 = 1, offset = 11 % 8 = 3 ,也就是第1個數(shù)組的第3個位置(從0開始算起)。
byte[] decodeResult = signHistoryToByte(signHistory); //index 該天所在的數(shù)組字節(jié)位置 int index = dayOfYear / 8; //該天在字節(jié)中的偏移量 int offset = dayOfYear % 8; //設(shè)置該天所在的bit為1 byte data = decodeResult[index]; data = (byte)(data|(1 << (7-offset))); decodeResult[index] = data ;//獲取該天所在的bit的值 int flag = data[index] & (1 << (7-offset));
編碼問題
應(yīng)用中使用的字節(jié)數(shù)組,而存到數(shù)據(jù)庫的是字符串。
由于ASCII表中有很多不可打印的ASCII值,并且每一個簽到記錄的字節(jié)都是-128~127,如果使用String 來進行轉(zhuǎn)碼,會造成亂碼出現(xiàn),
亂碼
public static void main(String args[]){ byte[] data = new byte[1]; for(int i = 0; i< 127; i++){ data[0] = (byte)i; String str = new String(data); System.out.println(data[0] + "---" + str); } data[0] = -13; String str = new String(data); System.out.println(data[0] + "---" + str + "----"); }/////////////////////////0--- 1---2---3---4---5---6---7---8--9--- 10---11---12---
為了解決編碼亂碼問題,
本文使用BASE64編碼來實現(xiàn)。參看
Base64 的那些事兒LocalDate
Date類并不能為我們提供獲取某一天是該年的第幾天功能,JDK8為我們提供了LocalDate類,該類可以替換Date類,相比Date提供了更多豐富的功能。更多使用方法參考源碼。
//獲取2018/6/11 位于該年第幾天 LocalDate localDate = LocalDate.of(2018,6,11); localDate.getDayOfYear(); //獲取今天 位于當(dāng)年第幾天 LocalDate localDate1 = LocalDate.now(); localDate.getDayOfYear();
數(shù)據(jù)表
原始數(shù)組長度僅需要46個字節(jié),經(jīng)過BASE64編碼后的字符串長度為64,所以這里的sign_history長度最大為64.
DROP TABLE IF EXISTS `sign`;CREATE TABLE `sign`( `id` BIGINT AUTO_INCREMENT COMMENT "ID", `user_id` BIGINT DEFAULT NULL COMMENT "用戶ID", `sign_history` VARCHAR(64) DEFAULT NULL COMMENT "簽到歷史", `sign_count` INT DEFAULT 0 COMMENT "連續(xù)簽到次數(shù)" , `last_sign_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT "最后簽到時間", PRIMARY KEY (`id`), UNIQUE user_id_index (`user_id`))ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT="簽到表";
簽到
由于每一天在簽到歷史記錄的字節(jié)數(shù)組中的位置都是固定好的。因此可以通過對該天進行除法和求余,即可輕易計算出該天所在的bit.
對該天簽到僅需將該bit置1即可。之后對字節(jié)數(shù)組進行重新BASE64編碼即可
/** *功能描述 * @author lgj * @Description 簽到 * @date 6/27/19 * @param: signHistory: 原始簽到字符串 * dayOfYear: 需要簽到的那一天(那年的第幾天) * @return: 最新生成的簽到歷史字符串 * */ public static String sign(String signHistory,int dayOfYear) throws Exception { if(signHistory == null){ throw new SignException("SignHistory can not be null!"); } checkOutofDay(dayOfYear); byte[] decodeResult = signHistoryToByte(signHistory); //index 該天所在的數(shù)組字節(jié)位置 int index = dayOfYear / 8; //該天在字節(jié)中的偏移量 int offset = dayOfYear % 8; byte data = decodeResult[index]; data = (byte)(data|(1 << (7-offset))); decodeResult[index] = data ; String encodeResult = new BASE64Encoder().encode(decodeResult); return encodeResult; }
獲取某年某月的簽到數(shù)據(jù)
該功能實現(xiàn)先求出當(dāng)月第一天和最后一天屬于當(dāng)年的第幾天,然后遍歷該范圍內(nèi)的簽到情況。
/** *功能描述 * @author lgj * @Description 獲取某年某月的簽到數(shù)據(jù) * @date 6/27/19 * @param: List<Integer>,如果當(dāng)月的第一天和第三天簽到,返回[1,3] * @return: * */ public static List<Integer> getSignHistoryByMonth(String signHistory, int year, int month)throws Exception{ if(signHistory == null){ throw new SignException("SignHistory can not be null!"); } checkOutofMonth(month); //start 本月第一天屬于當(dāng)年的第幾天 LocalDate localDate = LocalDate.of(year,month,1); int start = localDate.getDayOfYear(); //end 本月的最后一天屬于當(dāng)年的第幾天 int dayOfMonth = localDate.lengthOfMonth(); //log.info("dayOfMonth = {}",dayOfMonth); localDate = localDate.withDayOfMonth(dayOfMonth); int end = localDate.getDayOfYear(); //log.info("start={},end={}",start,end); Integer result = 0; byte[] data = signHistoryToByte(signHistory); List<Integer> signDayOfMonths = new ArrayList<>(); int signDay = 0;//遍歷 for(int i = start; i< end ; i++){ signDay++; if(isSign(data,i)){ signDayOfMonths.add(signDay); } } return signDayOfMonths; }
獲取過去幾天的連續(xù)簽到的次數(shù)
先定位當(dāng)天的bit所在的bit位置,再往前遍歷,直到碰到?jīng)]有簽到的一天。
/** *功能描述 * @author lgj * @Description 獲取過去幾天的連續(xù)簽到的次數(shù) * @date 6/27/19 * @param: * @return: 今天 6.27 簽到, 同時 6.26 ,6.25 也簽到 ,6.24 未簽到 ,返回 3 * 今天 6.27 未簽到, 同時 6.26 ,6.25 也簽到 ,6.24 未簽到 ,返回 2 * */ public static int getMaxContinuitySignDay(String signHistory) throws Exception{ int maxContinuitySignDay = 0; if(signHistory == null){ throw new SignException("SignHistory can not be null!"); } //獲取當(dāng)天所在的年偏移量 LocalDate localDate =LocalDate.now(); int curDayOfYear = localDate.getDayOfYear(); byte[] data = signHistoryToByte(signHistory); //開始遍歷,從昨天往前遍歷 int checkDayOfYear = curDayOfYear-1; while (checkDayOfYear > 0){ if(isSign(data,checkDayOfYear)){ checkDayOfYear--; maxContinuitySignDay++; } else { break; } } //檢測今天是否已經(jīng)簽到,簽到則+1 if(isSign(data,curDayOfYear)){ maxContinuitySignDay +=1; } return maxContinuitySignDay; }
測試某年的第n天是否簽到
和上面一樣先定位當(dāng)天的bit所在的位置,再獲取該bit的值,如果為1則說明已經(jīng)簽到,否則為0說明沒簽到。
/** *功能描述 * @author lgj * @Description 測試某年的第n天是否簽到 * @date 6/27/19 * @param: true: 該天簽到 false:沒有簽到 * @return: * */ public static boolean isSign(byte[] data,int dayOfYear) throws Exception{ checkOutofDay(dayOfYear); int index = dayOfYear / 8; int offset = dayOfYear % 8; //System.out.print(index+"-"); int flag = data[index] & (1 << (7-offset)); return flag == 0?false:true; }
其他代碼
//獲取默認(rèn)值,所有的bit都為0,也就是沒有任何的簽到數(shù)據(jù)public static String defaultsignHistory(){ byte[] encodeData = new byte[46]; return new BASE64Encoder().encode(encodeData); }//簽到歷史字符串轉(zhuǎn)字節(jié)數(shù)組public static byte[] signHistoryToByte(String signHistory) throws Exception { if(signHistory == null){ throw new SignException("SignHistory can not be null!"); } return new BASE64Decoder().decodeBuffer(signHistory); } //校驗天是否超出范圍 0- 365|366 private static void checkOutofDay(int dayOfYear) throws Exception{ LocalDate localDate =LocalDate.now(); int maxDay = localDate.isLeapYear()?366:365; if((dayOfYear <= 0)&&( dayOfYear > maxDay)){ throw new SignException("The param dayOfYear["+dayOfYear+"] is out of [0-"+ maxDay+"]"); } }//校驗月數(shù)是否超出范圍 private static void checkOutofMonth(int month) throws Exception{ if((month <= 0)&&( month > 12)){ throw new SignException("The param month["+month+"] is out of [0-"+ 12+"]"); } }
測試
測試1
@Testpublic void sign() throws Exception{ String signHistory = SignHistoryUtil.defaultsignHistory(); int signMonth = 8; int signDay = 13; int dayOfYear0 = LocalDate.of(2019,signMonth,signDay).getDayOfYear(); log.info("對2019-"+ signMonth + "-"+signDay+",第[" + dayOfYear0 + "]天簽到!"); signHistory = SignHistoryUtil.sign(signHistory,dayOfYear0); signMonth = 8; signDay = 24; int dayOfYear1 = LocalDate.of(2019,signMonth,signDay).getDayOfYear(); log.info("對2019-"+ signMonth + "-"+signDay+",第[" + dayOfYear1 + "]天簽到!"); signHistory = SignHistoryUtil.sign(signHistory,dayOfYear1); byte[] data = SignHistoryUtil.signHistoryToByte(signHistory); System.out.println(); log.info("第[{}]天是否簽到:{}",dayOfYear0,SignHistoryUtil.isSign(data,dayOfYear0)); log.info("第[{}]天是否簽到:{}",dayOfYear1,SignHistoryUtil.isSign(data,dayOfYear1)); log.info("第[{}]天是否簽到:{}",15,SignHistoryUtil.isSign(data,16)); log.info("簽到結(jié)果:"); log.info("數(shù)組長度 = " + data.length); for(int i = 0; i< data.length; i++){ System.out.print(data[i]); } System.out.println(); log.info("signHistory 長度:[{}],VALUE=[{}]",signHistory.length(),signHistory); List<Integer> signDayOfMonths = SignHistoryUtil.getSignHistoryByMonth(signHistory,2019,signMonth); log.info("第[{}]月簽到記錄[{}]",signMonth,signDayOfMonths);}
輸出
14:09:23.493 [main] INFO com.microblog.points.service.strategy.SignHistoryUtilTest - 對2019-8-13,第[225]天簽到!14:09:23.529 [main] INFO com.microblog.points.service.strategy.SignHistoryUtilTest - 對2019-8-24,第[236]天簽到!14:09:23.531 [main] INFO com.microblog.points.service.strategy.SignHistoryUtilTest - 第[225]天是否簽到:true14:09:23.535 [main] INFO com.microblog.points.service.strategy.SignHistoryUtilTest - 第[236]天是否簽到:true14:09:23.535 [main] INFO com.microblog.points.service.strategy.SignHistoryUtilTest - 第[15]天是否簽到:false14:09:23.535 [main] INFO com.microblog.points.service.strategy.SignHistoryUtilTest - 簽到結(jié)果:14:09:23.536 [main] INFO com.microblog.points.service.strategy.SignHistoryUtilTest - 數(shù)組長度 = 460000000000000000000000000000648000000000000000014:09:23.542 [main] INFO com.microblog.points.service.strategy.SignHistoryUtilTest - signHistory 長度:[64],VALUE=[AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAIAAAAAAAAAAAAAAAAAAAAAA==]14:09:23.545 [main] INFO com.microblog.points.service.strategy.SignHistoryUtilTest - 第[8]月簽到記錄[[13, 24]]Process finished with exit code 0
測試2
@Test public void getMaxContinuitySignDay()throws Exception { String signHistory = SignHistoryUtil.defaultsignHistory(); int curMonth = LocalDate.now().getMonth().getValue(); int curDay = LocalDate.now().getDayOfMonth(); int signDayCount = 0; int maxCount = 5; while(signDayCount < maxCount){ LocalDate localDate = LocalDate.of(2019,curMonth,curDay-signDayCount); log.info("[{}]簽到",localDate); signHistory = SignHistoryUtil.sign(signHistory,localDate.getDayOfYear()); signDayCount++; } LocalDate localDate = LocalDate.of(2019,curMonth,curDay-signDayCount-1); log.info("[{}]簽到",localDate); signHistory = SignHistoryUtil.sign(signHistory,localDate.getDayOfYear()); int maxContinuitySignDay = SignHistoryUtil.getMaxContinuitySignDay(signHistory); log.info("連續(xù)簽到[{}]天!",maxContinuitySignDay); }
輸出
14:11:02.340 [main] INFO com.microblog.points.service.strategy.SignHistoryUtilTest - [2019-06-27]簽到14:11:02.351 [main] INFO com.microblog.points.service.strategy.SignHistoryUtilTest - [2019-06-26]簽到14:11:02.352 [main] INFO com.microblog.points.service.strategy.SignHistoryUtilTest - [2019-06-25]簽到14:11:02.353 [main] INFO com.microblog.points.service.strategy.SignHistoryUtilTest - [2019-06-24]簽到14:11:02.354 [main] INFO com.microblog.points.service.strategy.SignHistoryUtilTest - [2019-06-23]簽到14:11:02.355 [main] INFO com.microblog.points.service.strategy.SignHistoryUtilTest - [2019-06-21]簽到14:11:02.355 [main] INFO com.microblog.points.service.strategy.SignHistoryUtilTest - 連續(xù)簽到[5]天!
注意: 本文實例代碼中并未考慮跨年度的情況,sign_history字段僅支持保存當(dāng)年(1月1號--12月31號)的日簽到數(shù)據(jù),如果需要跨年度需求,在數(shù)據(jù)表中添加year字段進行區(qū)分。
以上就是關(guān)于pos機簽到255,使用位圖算法來優(yōu)化簽到歷史數(shù)據(jù)的空間占用的知識,后面我們會繼續(xù)為大家整理關(guān)于pos機簽到255的知識,希望能夠幫助到大家!
