需求
有個需求,要儲存當月的所有打卡記錄,日曆
比如以下是我的打卡記錄
對應到JSON的資料是這樣的
{
"day1":1,
"day2":1,
"day3":0,
"day4":1,
"day5":0,
"day6":0,
"day7":1,
"day8":0,
"day9":0,
"day10":1,
"day11":0,
"day12":0,
"day13":1,
"day14":0,
"day15":1,
"day16":0,
"day17":1,
"day18":0,
"day19":0,
"day20":0,
"day21":0,
"day22":0,
"day23":0,
"day24":0,
"day25":0,
"day26":0,
"day27":0,
"day28":1,
"day29":0,
"day30":0,
"day31":0
}
1代表打卡,0代表沒打卡
常規思路
建立31個欄位,儲存每天的打卡記錄,在資料庫中是這樣的,打卡的天就儲存為1。
比如如下的表
這樣的實現主要有以下缺點
- 浪費空間。只是為了儲存一個0和1,建立了31個欄位
- 不利於擴充套件。如果要追加打卡記錄,得追加欄位
高階思路
我們知道二進位制只有0和1,非常適合這樣的場景,只用一個32位的整型的數字,就可以儲存上面這個31天的所有打卡
用二進位制數表現是這樣的
1101001001001010100000000001000
對應到二進位制的整數是1764048904
也就是說,其實我們只需要用一個二進位制的數字,就可以表示31天的打卡狀態
所以,我們需要一個這樣的功能
- 傳遞一個月的打卡狀態,返回一個對應的整數
- 傳遞一個整數,返回一個月的打卡狀態
Go語言實現
//簽到列表-->整數
func GetNumByList(list []int, max int) int {
n := 0
for i := 0; i < max; i++ {
if list[i] == 1 {
//將指定的二進位制位設定為1
n = n | (1 << i)
}
}
return n
}
//整數-->簽到列表
func GetListByNum(n int, max int) []int {
res := make([]int, max)
for i := 0; i < max; i++ {
if n&(1<<i) == (1 << i) {
//二進位制位為1的設定已打卡
res[i] = 1
}
}
return res
}
我們來測試一下
func main() {
max := 31
list := []int{1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0}
fmt.Println("原始打卡記錄:")
fmt.Println(list)
fmt.Println("打卡記錄轉數字:")
num := GetNumByList(list, max)
fmt.Println(num)
fmt.Println("數字還原打卡記錄:")
fmt.Println(GetListByNum(num, max))
}
執行後的結果輸出
這樣,我們就非常地方便用一個欄位儲存了所有的打卡狀態。
這裡主要用到了位運算,下面貼上GO語言的位操作
<< [ 左移 ]
1 << 2 == 4
輸出 0100 ,相比右移更常見,移位後空缺的部分全部填0
>> [ 右移 ]
10 >> 2 == 2
輸出 0010
x ^ y [ 異或 ]
10 ^ 2 == 8
操作的結果是如果某位不同則該位為1, 否則該位為0
x | y [ 或 ]
10 | 2 == 10
兩個相應的二進位中只要有一個為1, 該位的結果值為1
x & y [ 與 ]
10 & 2 == 2
兩個相應的二進位都為1, 該位的結果值才為1,否則為0
^x [ 取反 ]
^2 == -3
減1取反 補碼
思路延伸
這樣的思路可以應用在需要儲存選中狀態的需求中
比如我剛接到一個這樣的需求,直播抽獎功能,需要儲存獎品型別
像這樣的場景就非常適合用一個欄位儲存所有的選中狀態,並且當獎品型別新增的時候,還有很好的擴充套件性。