最近在做一個類似百度知道類的系統(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
- 0011 & 0010 = 0010 ≠0幅虑,表示目前是過期丰滑,去掉過期通過處理,00110010 = 0001
- 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)化目前的代碼