JAVA 點陣圖資料結構處理
阿新 • • 發佈:2018-11-18
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 日簽到前=false
第 17 日簽到前=false
第 5 日簽到後=true
第 17 日簽到後=true
簽到總次數=2
前 10 日簽到次數=1
前 20 日簽到次數=2
前 10 日未簽到次數=9
前 20 日未簽到次數=18
首次簽到是第 5 日