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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章