前言
最近博主收到了一個排序需求,需要我們按照用戶權重、狀態、性別和在線離線狀態對用戶進行排序,按照我們傳統的資料庫里也不行,因為如果要排序我需要取出所有的資料進行排序,假如說我這時候有很大的資料量該怎么辦?我不可能全部取出來然后通過java排序,這樣的性能是非常低的,那么有小伙伴肯定就會說,用zset直接排序,那么zset我們都知道是通過score排序的,但是我們怎么辦把多權重排序放到zset里呢?
核心技術
首先按照我的用戶權重、狀態、性別和在線狀態這4個維度的角度我們先按照需求分一下他們的排序優先級,首先狀態肯定不用說 優先級最高的,其次就是性別,然后是在房間在線離線狀態、最后是用戶權重,
狀態>性別>在線離線在房間狀態>用戶權重,
那么我們就有兩種做法,1:十進制拆分法,2:二進制拆分法,
十進制拆分
這個非常好理解,就是通過十進制的整數把我們的條件拆分成不通的位數,最后優先級越高的位數越大,比如我的用戶權重最小,但是他有10個權重,那么我就可以將我們的10放在十分位上,這樣取出來的時候就是按照權重排好序的,
例如

我們第一個條件已經好了,這里提個醒,我們再做排序之前一定要先思考好條件優先級最后自己對優先級做個排序,我們按照優先級最小的先做,這樣最后算出來的score才是正確的排好序的,
那么第二個條件是用戶在線狀態,那么在線狀態只有三個,由于我的十分位已經被占用了,那么我就用百分位來代替,100是離線,200是在線,300是在房間,最后我們的分數也變成了如下這樣:

以此類推,其他的條件值也可以這么玩,那么當我們按照條件優先級算好分數存盤進zset里就已經是幫我們排好序的一個集合了,我們只需要拿來用既可,第一種非常簡單,也非常好理解,那么接下來我要說一下第二種進階版,
二進制拆分法
首先,我們先了解一下zset的跳表結構:
typedef struct zskiplistNode {
// 成員物件
robj *obj;
// 分值
double score;
// 后退指標
struct zskiplistNode *backward;
// 層
struct zskiplistLevel {
// 前進指標
struct zskiplistNode *forward;
// 跨度
unsigned int span;
} level[];
} zskiplistNode;
我們可以看到,他的score實際上是一個double型別的一個資料,那么我們先了解一下它的double,
首先他的double型別是一個IEEE 754 標準的 double 浮點數,由此我們可以知道它的結構

S:符號位 1為負數,0為正數
Exp: 指數字,第二段,占11位,移碼方式表示正負數,排除全0和全1表示零和無窮大的特殊情況,指數部分是?1022~+1023加上1023,指數值的大小從1~2046
Fraction:有效數字,占52位,定點數,小數點放在尾數最前面
知道了這個以后,我們根據其條件不同,可以用不同的位數去存取資料,那么由于博主的條件比較少,就用了int型別來存盤我們的資料,
首先我需要先構思每個條件需要多少位
- 用戶權重:由于用戶權重只有10個,那么我將10換算成二進制的時候,它就是1010,也就是占了4位,所以我們將用戶權重存盤在int型別數值的低4位如下圖所示:

這時候我們雖然用二進制表示,但是在redis里面他會給你顯示十進制數值,也就是10,那么再回歸到我們剛剛講的十進制拆分,實際上,二進制拆分也跟十進制差不多,只不過我們是將10進制改變成為二進制而已,但是大體思路還是10進制的思路, - 在線狀態:我們的在線狀態條件有三個:1.在房間、2.在線、3.離線,那么由此我們可以得知我們只需要2位就可以算出用戶的在線狀態,那么我們可以把0當成離線,1當作在線,2當作在房間,換算成二進制就是離線:00 、在線:01、在房間:10,由于我們之前還有用戶權重,所以在線狀態我們需要存盤到第四位和第五位,此時我們的bit就變成了如下所示:我以在線狀態為在房間狀態所示:

我們將它換算成十進制就是:42.那么存盤在redis里的分數也會是42,
- 性別:由于性別是一個很中間性的優先級,那么我可以存盤在整數bit的中間位,此時我們就可以寫成:

那么我們再取資料的時候,假如這時候我要取出全部男性資料,那么我就可以將我的分數范圍設定在
0 ~ 1 << 16 的范圍之間,那么這個范圍的資料就是男性資料,而高于 1<< 16的就是女性資料,
那么位運算的符號是什么意思,一會我會詳細跟大家解釋,但是左移符號我覺得應該都能看懂~
- 狀態:在我的認知里,應該沒有什么比狀態優先級更大的了,因為我們狀態通常都是開啟、關閉等等,那么由于我這里只有兩種狀態,同樣我只需要用0和1來表示不同的狀態就可以,比如我用1表示關閉狀態,0表示開啟狀態,那么我就可以寫成這樣:

那么我在取資料的時候,我只需要將score區間設定成0~1<<30范圍內的資料,就可以將全部狀態為開啟的用戶取出,那么知道了這些后,我們如何計算分數呢?
二進制拆分計算score
首先我們先定義一下一個全域的靜態常量:
/**
* 用戶在線狀態bit長度
*/
public static final int onlineStatusBits = 2;
/**
* 用戶權重bit長度
*/
public static final int WEIGHT_BITS= 4;
/**
* 用于保存用戶性別 1女 0男
*/
public static final int SCORE_WOMAN = 1 << 16;
/**
* 用戶狀態
*/
public static final int SCORE_STATUS = 1 << 30;
那么再計算分數的時候,以我現在的狀態是在房間狀態,并且我的用戶權重是5為例,我們可以寫成如下方式:
int score = (3 << WEIGHT_BITS) | 5;
將三向左移動weightBits的目的是為了讓出低位的空間,讓我們的用戶權重有地方放,那么再通過按位或就可以得出score,| 這個符號的含義是:兩個數值進行二進制比較的時候,兩個數值的位數都是0才為0,如果有一個為1,就是1.
我以上面的代碼為例子:
我們3 << weightBits 換算成二進制:
0011 0000
5的二進制:
0000 0101
那么此時我們通過按位或計算出來的結果就是:
0011 0101
我們發現最終的這個結果高4位是我們的在線狀態的值,第四位就是我們的權重,
如果我們要取出來資料怎么辦呢?
如何從score中取出對應的值
其實這個也很簡單,我們以剛剛的結果 0011 0101來距離,首先我們先說一下我們如何取出位數上的資料呢?
(1 << bit) - 1 & score
就可以,首先我們說一下& 按位與 這個符號的含義,那么他是這樣的,兩個二進制數值進行對比,如果都為1,我才為1,否則就是0.
那么通過這個,我們要計算用戶權重就很簡單,我們只需要這樣寫既可:
int weight = score & ((1 << WEIGHT_BITS) - 1);
首先我們的1左移4位之后它的二進制變成如下表示:
0001 0000
此時我們肯定是不能進行&運算的這樣肯定會出錯,那么只需要-1,就可以變成
0000 1111
此時我們再計算分數
0011 0101
通過按位與獲得的結果
0000 0101
我們也就取出了用戶的權重值,
而我們想要去其他高位的數值怎么辦?我們還是以剛剛的例子距離,我們只需要這樣寫
int onlineStatus = (score >> WEIGHT_BITS) & ((1 << ONLINE_STATUS_BITS) - 1);
將分數右移到權重上,例如 0011 0101 >> 4位
就會變成
0000 0011
這時候在通過按位與運算
0000 0011
結果就是我們的權重值的分數了,其他的演算法也同理,
好啦,今天的文章就到這里了,喜歡的話記得評論點贊轉發收藏一下哦~
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/198789.html
標籤:其他
上一篇:軟考中級軟體設計師做題筆記(四)
下一篇:C程式設計(第五版)例4.4
