單一字段存儲多狀態(tài)思路

最近在做一個類似百度知道類的系統(tǒng),針對每個回答以及問題都有可能有多重組合狀態(tài):

  • 是否過期
  • 是否重復
  • 是否優(yōu)質
  • ...
    這些狀態(tài)有個共同的特點锦亦,就是都是Yes Or Not忧侧,如果拍平博其,每個狀態(tài)都用一個獨立的字段進行存儲尝盼,字段存儲信息過于簡單先不說,將來如果增加狀態(tài)的話漠趁,數據庫結構也需要同步修改扁凛。

正在研究怎么處理的時候,感謝同事的提點闯传,給我了一個單一字段存儲這些值的思路谨朝。經過實踐后,發(fā)覺用的挺好甥绿,于是總結一下叠必。

基本原理

首先我們已知這些狀態(tài)都是Yes Or Not,且互斥的關系妹窖,不相交纬朝。因此我們可以將這些狀態(tài)都看做是一個二進制數的一位,例如過期看做是0001骄呼,重復是0010共苛,優(yōu)質是0100判没,這樣,如果數據庫中字段值是3=(0b0011)隅茎,就表示既過期澄峰,又重復。
這樣如果我們將來狀態(tài)不止這幾種辟犀,只需要擴充二進制的位數即可俏竞。

計算

我們思路有了,如何提取結果就是目前的重要問題堂竟。
即:我給一個數據庫值3=(0b0011)魂毁,如何反推出是優(yōu)質和過期。
這就涉及到三個按位操作符——&出嘹、|席楚、^。

&(按位與)

逐位進行與運算

A B 結果
0 0 0
0 1 0
1 0 0
1 1 1

例如:
0011 & 0001 = 0001
0010 & 0001 = 0000

|(按位或)

逐位進行與運算

A B 結果
0 0 0
0 1 1
1 0 1
1 1 1

例如:
0011 | 0001 = 0011
0010 | 0001 = 0011

^(按位異或)

逐位進行與運算税稼,相同為0烦秩,不相同為1

A B 結果
0 0 0
0 1 1
1 0 1
1 1 0

例如:
0011 ^ 0001 = 0010
0010 & 0001 = 0000

狀態(tài)值計算

有個這三個工具后,就可以進行相應值的計算了
首先計算狀態(tài)組合郎仆,用|運算只祠,
例如:
一個問題是重復,不過期扰肌,不優(yōu)質抛寝,就用0001 | 0000 | 0000 = 0001 = 1(int)
一個問題是重復,過期狡耻,不優(yōu)質,就用0001 | 0010 | 0000 = 0011 =3(int)
每一種組合都對應唯一的一個二進制結果猴凹,這樣將結果保存后夷狰,就一個用一個字段存儲所有組合了。

然后我們有了這個計算結果后郊霎,還可以進行反推狀態(tài)沼头,這里用&操作
例如,剛才我們的一個計算結果是3书劝,0b0011
我們首先用0011與“重復”對應的0001進行&操作
0011 & 0001 = 0001 ≠ 0进倍,表示這個計算結果是包含“重復”狀態(tài)的。
我們在用0011與“過期”對應的0010進行&操作
0011 & 0010 = 0010 ≠ 0购对,表示這個計算結果是包含“過期”狀態(tài)的猾昆。
我們在用0011與“優(yōu)質”對應的0100進行&操作
0011 & 0100 = 0000 = 0,表示這個計算結果是不包含“優(yōu)質”狀態(tài)的骡苞。
通過與相應二進制碼進行&操作后結果是否等于0垂蜗,可以判斷狀態(tài)楷扬。

除了進行反推,還需要個清除狀態(tài)贴见,可以去掉某個狀態(tài)值烘苹。這一用到^操作
例如已知0011是包含重復狀態(tài),去除重復的話
0011 ^ 0001 = 0010 片部,0010表示只過期狀態(tài)镣衡。

一句話總結下:
添加狀態(tài)用|,去掉狀態(tài)用^(這里有個小坑档悠,之后說)廊鸥,判斷狀態(tài)用&

具體代碼

public enum Status {

  DEFAULT(0b00000000, "默認", "default"),
  REPEAT(0b00000001, "重復", "repeat"),
  EXPIRE(0b00000010, "過期", "expire");

  private int code;
  private String desc;
  private String property;


  public static int getMark(Status... status) {
    int init = 0;
    for (AnswerStatus s : status) {
      init = init | (s.getCode());
    }
    return init;
  }

  public static boolean status(int mark, Status status) {
    return (mark & status.getCode()) == status.getCode();//判斷是否相等也行,判斷是否不等于0也可以
  }

  public static void handle(Consumer<Status> consumer) {
    Arrays.stream(values()).forEach(consumer::accept);
  }
}

生成狀態(tài)值時站粟,代碼如下:

//保存mark
        setMark(Status.getMark(
            answer.isRepeat() ? Status.REPEAT : Status.DEFAULT,
            answer.isExpire() ? Status.EXPIRE : Status.DEFAULT
        ))
//獲取狀態(tài)
        repeat(AnswerStatus.status(answer.getMark(), AnswerStatus.REPEAT))
        expire(AnswerStatus.status(answer.getMark(), AnswerStatus.EXPIRE))

這里生成狀態(tài)值時黍图,只能給出所有組合,進行保存奴烙,如果想根據當前狀態(tài)與給定狀態(tài)助被,進行組合更新,需要像下面這樣調用:
例如目前切诀,已經是0011了揩环,我們給定expire=false,需要結果更新為0001

  1. 0011 & 0010 = 0010 ≠0幅虑,表示目前是過期丰滑,去掉過期通過處理,00110010 = 0001
  2. 0001 & 0010 = 0000 =0,表示原來就不是過期倒庵,所以不需要進行操作
    這種單獨修改褒墨,需要判斷狀態(tài)的轉化,所以一般分為兩步:
    1擎宝、獲取當前狀態(tài)與目標狀態(tài)
    2郁妈、是-》是,否-》否绍申,不需要操作噩咪;是到否,異或方式去掉狀態(tài)极阅;否到是胃碾,用或操作,將狀態(tài)加上筋搏。
//mark:{repeat:true/false,expire:true/false}
    Status.handle(element -> {
      if (mark.containsKey(element.getProperty())) {
        //需要進行調整
        if ((pojo.getMark() & element.getCode()) != 0 && !mark.getBoolean(element.getProperty())) {
          pojo.setMark(pojo.getMark() ^ (element.getCode()));
        } else if ((pojo.getMark() & element.getCode()) == 0 && mark
            .getBoolean(element.getProperty())) {
          pojo.setMark(pojo.getMark() | (element.getCode()));
        }
      }
    });

后續(xù)

這樣就可以用一個字段進行存儲了仆百。其實我目前的方法不是最優(yōu)解,實際上安卓系統(tǒng)在處理這種多狀態(tài)時奔脐,已經在用這種二進制方式了儒旬,后續(xù)在研讀安卓后栏账,會嘗試寫一篇后續(xù)文章,優(yōu)化目前的代碼

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末栈源,一起剝皮案震驚了整個濱河市挡爵,隨后出現的幾起案子,更是在濱河造成了極大的恐慌甚垦,老刑警劉巖茶鹃,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異艰亮,居然都是意外死亡闭翩,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門迄埃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來疗韵,“玉大人,你說我怎么就攤上這事侄非〗锻簦” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵逞怨,是天一觀的道長者疤。 經常有香客問我,道長叠赦,這世上最難降的妖魔是什么驹马? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮除秀,結果婚禮上糯累,老公的妹妹穿的比我還像新娘。我一直安慰自己册踩,他們只是感情好泳姐,可當我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著棍好,像睡著了一般仗岸。 火紅的嫁衣襯著肌膚如雪允耿。 梳的紋絲不亂的頭發(fā)上借笙,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天,我揣著相機與錄音较锡,去河邊找鬼业稼。 笑死,一個胖子當著我的面吹牛蚂蕴,可吹牛的內容都是我干的低散。 我是一名探鬼主播俯邓,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼熔号!你這毒婦竟也來了稽鞭?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤引镊,失蹤者是張志新(化名)和其女友劉穎朦蕴,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體弟头,經...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡吩抓,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了赴恨。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片疹娶。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖伦连,靈堂內的尸體忽然破棺而出雨饺,到底是詐尸還是另有隱情,我是刑警寧澤除师,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布沛膳,位于F島的核電站,受9級特大地震影響汛聚,放射性物質發(fā)生泄漏锹安。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一倚舀、第九天 我趴在偏房一處隱蔽的房頂上張望叹哭。 院中可真熱鬧,春花似錦痕貌、人聲如沸风罩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽超升。三九已至,卻和暖如春哺徊,著一層夾襖步出監(jiān)牢的瞬間室琢,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工落追, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留盈滴,地道東北人。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓轿钠,卻偏偏與公主長得像巢钓,于是被迫代替她去往敵國和親病苗。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內容