從 Bit 位操作學封裝
對於業務開發人員,bit 位這個概念既熟悉又陌生,熟悉是因爲整個計算機就是建立在 bit 基礎之上,同時任何一門語言都對 bit 位提供了支持;陌生是因爲工作過程中基本沒有使用過,說起具體的操作語法估計也需要好好思考一下。
不像底層研發人員,比如驅動開發、網絡協議開發等,成天與 bit 混在一起。業務開發人員對於 bit 位就陌生了太多。但,在一些特殊場景,bit 位還真的是一個非常好的解決方案。
1. 業務人員眼中的 bit 位
在業務開發人員眼裏,bit 位就是特殊的 一對多 關係,可以通過一個數值來表達多個 boolean 語義!
既然大家對 bit 不太熟悉,那就請出封裝利器,自己動手打造一套面向對象且更好用的工具。
2. 單一 Bit 位操作
對於單一 Bit 位,操作非常簡單,只有:
-
設置 bit 位爲 1(true)
-
設置 bit 位爲 0 (false)
-
驗證 bit 位 是否爲 1
操作代碼如下:
@Data
public class LongMaskOp{
private final long mask;
public LongMaskOp(long mask ) {
this.mask = mask;
}
public boolean isSet(long code){
return (code & this.mask) == this.mask;
}
public boolean match(long value) {
return isSet(value);
}
public long set(long value, boolean isSet){
if (isSet){
return set(value);
}else {
return unset(value);
}
}
public long set(long value){
return value | this.mask;
}
public long unset(long value){
return value & ~this.mask;
}
}
使用也非常簡單,獲取 LongMaskOp 之後便可以使用各種方法進行位操作,示例如下:
long value = ?;
LongMaskOp longMaskOp = ?;
value = longMaskOp.set(value);
Assertions.assertTrue(longMaskOp.isSet(value));
value = longMaskOp.unset(value);
Assertions.assertFalse(longMaskOp.isSet(value));
代碼非常簡單,在此不在贅述,有了 LongMaskOp 類之後,如何獲取對應的實例呢?
當然,可以通過 new 關鍵字直接實例化對象,但每個位上的 MaskOp 是完全一樣的,這就讓我們想到了單例模式。
對於 Long 類型最多也就 64 個對象,我們可以一次性將其創建出來,然後通過傳入的參數返回對應位置上的值,具體操作步驟如下:
-
將 LongMaskOp 構造函數設置爲 private,禁止從外部創建對象
-
LongMaskOp 中定義 64 個 靜態變量,每一位對於一個 LongMaskOp 實例
-
將 64 個 LongMaskOp 實例統一放在 List 集合,方便按位數進行查找
-
提供靜態方法,根據傳入的位數返回對應的 LongMaskOp 實例
核心代碼如下:
@Data
public class LongMaskOp{
// 1. 私有構造函數,禁止從外部創建對象
private LongMaskOp(long mask ) {
this.mask = mask;
}
// 2. 定義 64 個靜態變量
public static final LongMaskOp MASK_1 = new LongMaskOp(1L << 0);
public static final LongMaskOp MASK_2 = new LongMaskOp(1L << 1);
...... 省略部分代碼
public static final LongMaskOp MASK_64 = new LongMaskOp(1L << 63);
// 3. 將 64 個變量放入 List 集合
MASK_OPS = Arrays.asList(LongMaskOp.MASK_1, LongMaskOp.MASK_2, .... 省略部分代碼, LongMaskOp.MASK_64);
// 4. 提供靜態方法獲取對應位置的 LongMaskOp 對象
/**
* 根據 bit 位的位置,獲取對應的封裝實例 <br />
* Index 從 1 開始
* @param index
* @return
*/
public static LongMaskOp getByBitIndex(int index){
if (index < 1 || index > MASK_OPS.size()){
throw new IndexOutOfBoundsException();
}
return MASK_OPS.get(index - 1);
}
}
單例模式改造完成,通過以下代碼可以方便的獲取 Bit 操作類:
LongMaskOp bitOp1 = LongMaskOp.getByBitIndex(1);
整體結構如圖所示:
3. Bit 位邏輯運算
Bit 位支持 and、or、not 等邏輯運算,我們的 LongMaskOp 也需要提供對應的支持,可以思考一下,哪種模式可以解決這個問題?
對,這就是組合模式的應用場景。要想應用組合模式,需要:
-
抽取公共接口
-
構建葉子節點
-
構建組合節點
目前,LongMaskOp 實現了真正的業務邏輯,是我們的葉子節點。我們需要抽取公共接口,然後爲每個邏輯運算構建 “組合節點”。
首先,抽取公共接口,接口中只有一個 match 方法,用於判斷是否匹配,LongMaskOp 實現該接口並實現 match 方法:
public interface LongBitOp {
boolean match(long value);
}
@Data
public class LongMaskOp implements LongBitOp {
@Override
public boolean match(long value) {
return isSet(value);
}
}
接下來,可以構建對 And、Or、Not 的支持。核心代碼如下:
public class LongBitAndOp implements LongBitOp {
private final LongBitOp[] longBitOps;
LongBitAndOp(LongBitOp... longBitOps) {
this.longBitOps = longBitOps;
}
@Override
public boolean match(long value) {
if (this.longBitOps == null || this.longBitOps.length == 0){
return true;
}
return Stream.of(longBitOps)
.allMatch(longMaskOp -> longMaskOp.match(value));
}
}
public class LongBitOrOp implements LongBitOp {
private final LongBitOp[] longBitOps;
LongBitOrOp(LongBitOp... longBitOps) {
this.longBitOps = longBitOps;
}
@Override
public boolean match(long value) {
if (this.longBitOps == null || this.longBitOps.length == 0){
return true;
}
return Stream.of(this.longBitOps)
.anyMatch(intBitOp -> intBitOp.match(value));
}
}
public class LongBitNotOp implements LongBitOp {
private final LongBitOp longBitOp;
LongBitNotOp(LongBitOp longBitOp) {
this.longBitOp = longBitOp;
}
@Override
public boolean match(long value) {
return !this.longBitOp.match(value);
}
}
邏輯非常簡單:
-
LongBitAndOp 只有在所有的條件全部匹配時才返回 true
-
LongBitOrOp 只要有一個條件匹配便返回 true
-
LongBitNotOp 將條件判斷結果取反並返回
萬事具備,如何更好的暴露 API 呢?
首先,And、Or、Not 操作每個 LongBitOp 都應該支持,那放在 LongBitOp 接口最爲合適,放在接口中所有的子類都需要實現,哪豈不是更麻煩?
Java 8 引入一個新特性:默認方法,可以爲接口添加默認實現,一起瞧瞧:
public interface LongBitOp {
boolean match(long value);
// 默認方法
default LongBitOp or(LongBitOp other){
return new LongBitOrOp(this, other);
}
// 默認方法
default LongBitOp and(LongBitOp other){
return new LongBitAndOp(this, other);
}
// 默認方法
default LongBitOp not(){
return new LongBitNotOp(this);
}
}
這樣每個 LongBitOp 子類都具備了 And、Or、Not 能力,新 API 使用起來也非常簡單:
LongMaskOp bitOp1 = LongMaskOp.getByBitIndex(1);
LongMaskOp bitOp2 = LongMaskOp.getByBitIndex(2);
LongBitOp orBitOp = bitOp1.or(bitOp2);
LongBitOp andBitOp = bitOp1.and(bitOp2);
LongBitOp notBitOp = bitOp1.not();
4. 數據庫過濾
Bit 位操作僅停留在 內存 嗎?不一定哈,MySQL 的 sql 語句便提供了 bit 位操作,可以在數據庫層對數據進行過濾。接下來,繼續對 LongBitOp 進行擴展,使其能產生語義相同的 sql 片段,輔助我們完成數據過濾。
首先,在 LongBitOp 接口中增加生成 SQL 片段的方法:
public interface LongBitOp {
String toSqlFilter(String fieldName);
}
接下來,讓 LongMaskOp 實現 toSqlFilter 方案:
@Data
public class LongMaskOp implements LongBitOp {
@Override
public String toSqlFilter(String fieldName) {
return new StringBuilder()
.append("(")
.append(fieldName)
.append(" & ")
.append(getMask())
.append(")")
.append("=")
.append(getMask())
.toString();
}
}
沒什麼複雜的,主要就是 字符串 拼接,下一步對 LongBitAndOp、LongBitOrOp、LongBitNotOp 進行完善:
public class LongBitAndOp implements LongBitOp {
// 使用 and 連接多個語句
@Override
public String toSqlFilter(String fieldName) {
if (this.longBitOps == null || this.longBitOps.length == 0){
return "";
}
return Stream.of(longBitOps)
.map(intBitOp -> intBitOp.toSqlFilter(fieldName))
.collect(Collectors.joining(" and ","(",")"));
}
}
public class LongBitOrOp implements LongBitOp {
// 使用 or 連接多個語句
@Override
public String toSqlFilter(String fieldName) {
if (this.longBitOps == null || this.longBitOps.length == 0){
return "";
}
return Stream.of(this.longBitOps)
.map(intBitOp -> intBitOp.toSqlFilter(fieldName))
.collect(Collectors.joining(" or ","(",")"));
}
}
public class LongBitNotOp implements LongBitOp {
// 拼接 <> 語句
@Override
public String toSqlFilter(String fieldName) {
LongMaskOp longMaskOp = (LongMaskOp) longBitOp;
return new StringBuilder()
.append("(")
.append(fieldName)
.append(" & ")
.append(longMaskOp.getMask())
.append(")")
.append("<>")
.append(longMaskOp.getMask())
.toString();
}
}
整體調整完成,一起看下實際效果:
LongBitOp bitOp1 = LongMaskOp.getByBitIndex(1);
LongBitOp bitOp2 = LongMaskOp.getByBitIndex(2);
LongBitOp bitOp3 = LongMaskOp.getByBitIndex(3);
// 直接過濾
Assertions.assertEquals("(type & 1)=1", bitOp1.toSqlFilter("type"));
Assertions.assertEquals("(type & 2)=2", bitOp2.toSqlFilter("type"));
Assertions.assertEquals("(type & 4)=4", bitOp3.toSqlFilter("type"));
// not 條件過濾
Assertions.assertEquals("(type & 1)<>1", bitOp1.not().toSqlFilter("type"));
Assertions.assertEquals("(type & 2)<>2", bitOp2.not().toSqlFilter("type"));
Assertions.assertEquals("(type & 4)<>4", bitOp3.not().toSqlFilter("type"));
// and 條件過濾
Assertions.assertEquals("((type & 1)=1 and (type & 2)=2)", bitOp1.and(bitOp2).toSqlFilter("type"));
Assertions.assertEquals("((type & 2)=2 and (type & 4)=4)", bitOp2.and(bitOp3).toSqlFilter("type"));
Assertions.assertEquals("((type & 1)=1 and (type & 4)=4)", bitOp1.and(bitOp3).toSqlFilter("type"));
// or 條件過濾
Assertions.assertEquals("((type & 1)=1 or (type & 2)=2)", bitOp1.or(bitOp2).toSqlFilter("type"));
Assertions.assertEquals("((type & 2)=2 or (type & 4)=4)", bitOp2.or(bitOp3).toSqlFilter("type"));
Assertions.assertEquals("((type & 1)=1 or (type & 4)=4)", bitOp1.or(bitOp3).toSqlFilter("type"));
5. 整體設計
打完收工,至此,我們擁有了一個功能強大的 Bit 位操作工具,不僅能夠在內存數據中進行運算,還可以生成 sql 片段,在數據庫中完成數據過濾。
最後,一起看下整體設計:
這就是封裝的藝術,不知道你能 get 多少。
- 項目信息
項目倉庫地址:https://gitee.com/litao851025/lego
項目文檔地址:https://gitee.com/litao851025/lego/wikis/support/bitop
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/6aKNJVkNj-scoQuWfSrInA