1. 程式人生 > >JAVA 點陣圖資料結構處理

JAVA 點陣圖資料結構處理

JAVA 點陣圖資料結構處理

概述

        以前在做專案開發過程中,曾遇到過一些布林型資料需要存取,此類資料的值要麼是false,要麼是true,但資料量大,比如終端每小時的線上狀態記錄,使用者每日簽到記錄等,一般採用點陣圖資料結構來儲存;
        以使用者一年的簽到記錄為例,簽了是true,沒簽是 false,要記錄 365 天。如果使用普通的 key/value資料結構,每個使用者要記錄 365 個,當用戶量很大的時候,儲存空間是巨大的。為了解決這個問題,運用了點陣圖這樣的資料結構,這樣每天的簽到記錄只佔據一個位,365 天就是 365 個位,一位元組8位,這樣46 個位元組就可以完全容納下,這就大大節約了儲存空間和效率。
        以下就是點陣圖儲存的工具類程式碼

工具類程式碼

package com.kenny.utils;
/**
 * 
 * @類名: BitStoreUtils
 * @說明: 點陣圖結構資料工具類
 *
 * @author: kenny
 * @Date	2018年11月13日下午7:34:48
 * 修改記錄:
 *
 * @see
 */
public class BitStoreUtils {
	/**
	 * 最多位數
	 */
	final static Integer MAX_LEN = 65536;
	

	/**
	 * 創點陣圖陣列
	 * @param len 點陣圖長度
	 * @return
	 * @throws Exception 
	 */
public static byte[] createBytes(int len) throws Exception{ if (len <= 0 || len > MAX_LEN) throw new Exception("超出範圍"); int pos = len%8==0?len/8:len/8+1; byte[] bm = new byte[pos]; return bm; } /** * 取得指定位的值 * @param bm 點陣圖結構資料 * @param position 須>0 * @return true/false */
public static boolean get(byte[] bm, int position){ int index = getIndex(position); int pos=getBitPos(position); byte b = bm[index]; b = (byte)((b >> (7-pos)) & 0x1); return b==1?true:false; } /** * 設定某位為true * @param bm 點陣圖結構資料 * @param position 位置 >0 * @param value 設定值 true/false */ public static void set(byte[] bm,int position,boolean value){ int index = getIndex(position); int pos=getBitPos(position); //轉換為8個二進位制表示字串 String bin = byte2Bin(bm[index]); //用valve替換原來的值 StringBuilder sb = new StringBuilder(bin); sb.replace(pos,pos+1,value==true?"1":"0"); //二進位制轉換為byte byte nb = bin2Byte(sb.toString()); //更新 bm[index]=nb; } /** * 統計全部true的個數 * @param bm 點陣圖結構資料 * @return */ public static int countTrue(byte[] bm){ return countTrue(bm,1,bm.length*8); } /** * 統計一定範圍內true的個數 * @param bm 點陣圖結構資料 * @param from 開始位置 >0 * @param to 結束位置 >0 && <=總位數 * @return */ public static int countTrue(byte[] bm,int from,int to){ int count = 0; //處理第一個byte int i1 = getIndex(from); int p1 = getBitPos(from); count += countBytes(bm[i1],p1,7); //處理中間的n個byte int i2 = getIndex(to); int p2=getBitPos(to); for(int i=i1+1;i<i2;i++){ count += countBytes(bm[i],0,7); } //處理最後一個byte count += countBytes(bm[i2],0,p2); return count; } /** * 統計全部false的個數 * @param bm 點陣圖結構資料 * @param from 開始位置 >0 * @param to 結束位置 >0 && <=總位數 * @return */ public static int countFalse(byte[] bm,int from,int to){ return to - countTrue(bm,from,to); } /** * 首個為true 位置 * @param bm * @return */ public static int firstTrue(byte[] bm){ int position = 0; boolean found = false; for(int i=0;i<bm.length;i++){ byte b=bm[i]; byte bits[] = new byte[8]; for (int j = 7; j >= 0; j--) { bits[j] = (byte)(b & 1); b = (byte) (b >> 1); } for (int k = 0; k <= 7; k++) { if (bits[k] == 1){ found = true; break; }else{ position++; } } if (found) break; } return found?position+1:0; } /** * 計算每一個byte中1的個數 * @param b * @param fromIndex * @param toIndex * @return */ private static int countBytes(byte b,int fromIndex, int toIndex) { int count = 0; for (int i = 7; i >= 0; i--) { //當前位等於1且在開始和結束 的範圍內,則計數 if (i >= fromIndex && i <= toIndex && (byte)(b & 1) == 1){ count++; } b = (byte) (b >> 1); } return count; } /** * 取得字元 的8位二進位制字串 * 如2,返回 00000010 * @param b * @return */ private static String byte2Bin(byte b) { String result = "" + (byte) ((b >> 7) & 0x1) + (byte) ((b >> 6) & 0x1) + (byte) ((b >> 5) & 0x1) + (byte) ((b >> 4) & 0x1) + (byte) ((b >> 3) & 0x1) + (byte) ((b >> 2) & 0x1) + (byte) ((b >> 1) & 0x1) + (byte) ((b >> 0) & 0x1); return result; } /** * 二進位制字元轉byte * @param bin * @return */ private static byte bin2Byte(String bin) { int result, len; if (null == bin) { return 0; } len = bin.length(); if (len == 8){ //第一位是0表示正數否則負數 result = Integer.parseInt(bin, 2); result = bin.charAt(0) == '0'?result:result - 256; }else if (len == 4){ result = Integer.parseInt(bin, 2); }else result = 0; return (byte) result; } /** * 根據位置取得第幾byte (陣列下標由0開始) * 如22則為1,24則為2 * @param position * @return */ private static int getIndex(Integer position){ return position%8==0?position/8-1:position/8; } /** * 根據位置取得byte中第幾位 (陣列下標由0開始) * @param position * @return */ private static Integer getBitPos(Integer position){ //沒有餘數,則位於前一個字元的第7位 return position%8==0?7:position%8-1; } }

測試程式碼:

package com.kenny.utils;

public class Test {
	public static void main(String[] args) throws InterruptedException {
		byte[] b;
		try {
			b = BitStoreUtils.createBytes(365);
			System.out.println(String.format("第  %s 日簽到前=%s",5,BitStoreUtils.get(b,5)));
			System.out.println(String.format("第  %s 日簽到前=%s",17,BitStoreUtils.get(b,17)));
			BitStoreUtils.set(b,5,true);
			BitStoreUtils.set(b,17,true);
			System.out.println(String.format("第  %s 日簽到後=%s",5,BitStoreUtils.get(b,5)));
			System.out.println(String.format("第  %s 日簽到後=%s",17,BitStoreUtils.get(b,17)));
			System.out.println(String.format("簽到總次數=%s",BitStoreUtils.countTrue(b)));
			System.out.println(String.format("前 %s 日簽到次數=%s",10,BitStoreUtils.countTrue(b,1,10)));
			System.out.println(String.format("前 %s 日簽到次數=%s",20,BitStoreUtils.countTrue(b,1,20)));
			System.out.println(String.format("前 %s 日未簽到次數=%s",10,BitStoreUtils.countFalse(b,1,10)));
			System.out.println(String.format("前 %s 日未簽到次數=%s",20,BitStoreUtils.countFalse(b,1,20)));
			System.out.println(String.format("首次簽到是第 %s 日",BitStoreUtils.firstTrue(b)));	
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

結果顯示:

5 日簽到前=false17 日簽到前=false5 日簽到後=true17 日簽到後=true
簽到總次數=210 日簽到次數=120 日簽到次數=210 日未簽到次數=920 日未簽到次數=18
首次簽到是第 5