天天看點

來啊!快看---redis存儲結構、基本資料類型、基本指令redis的基本資料類型redis的簡單操作語句

先送圖,先看看redis在哪裡

來啊!快看---redis存儲結構、基本資料類型、基本指令redis的基本資料類型redis的簡單操作語句

要問我看這個圖檔有啥用,我也不知道,送給你的愛要不要

Redis現在是比較流行的緩存資料庫,一般剛接觸的時候都會發現其可以存儲字元串(string)、哈希表(hash)、清單(list)、集合(set)、有序集合(sorted set)等。redis是一個key-value存儲,value可以包含上面列出的多種結構,但是key都是字元串。也就是說key是string類型,value為上面類型的一種。

redis的基本資料類型

一般來說,redis有5大基本資料類型:
  • 字元串string
  • 哈希hash
  • 字元串清單list
  • 字元串集合set 不重複,無序
  • 有序集合sortedset ,不重複,有序

可能有的人還會加上這麼幾種

HyperLogLog,bitMap,GeoHash,BloomFilter

我們就來說說基本的5種

Redis中每個對象都有一個redisObject結構,該結構中和儲存資料相關的三種屬性分别是存儲資料的類型type、值的編碼屬性encoding和指針ptr屬性:

typedef struct redisObject{
//類型
unsigned type:4;
 
//編碼
unsigned encoding:4;
 
//指向底層實作資料結構的指針
void *ptr
 
//虛拟記憶體和其他資訊等.....
}robj;
           

上面的type是指不同的基本資料結構類型

#define REDIS_STRING 0

#define REDIS_LIST 1

#define REDIS_SET 2

#define REDIS_ZSET 3

#define REDIS_HASH 4

下面是encoding屬性的值
編碼常量 對象的名稱 type值
REDIS_ENCODING_INT 整數 int
REDIS_ENCODING_EMBSTR embstr編碼的簡單動态字元串(SDS) list
REDIS_ENCODING_RAW 簡單動态字元串 raw
REDIS_ENCODING_HT 字典 hashtable
REDIS_ENCODING_LINKEDLIST 雙端連結清單 linkedlist
REDIS_ENCODING_ZIPLIST 壓縮清單 ziplist
REDIS_ENCODING_INTSET 整數集合 intset
REDIS_ENCODING_SKIPLIST 跳躍表和字典 skiplist

1、簡單字元集合SDS

Redis是C語言開發的,C語言自己就有字元類型,但是Redis卻沒直接采用C語言的字元串類型,而是自己建構了動态字元串(SDS)的抽象類型。

來啊!快看---redis存儲結構、基本資料類型、基本指令redis的基本資料類型redis的簡單操作語句

這樣一句話,看似建立了一個下,其實是建立了兩個SDS對象,一個key對象一個value對象,

就算是字元類型的List,也是由很多的SDS構成的Key和Value罷了。

SDS結構:

struct sdshdr{
 int len;
 int free;
 char buf[];
}
           

一張圖讓key浮出水面

來啊!快看---redis存儲結構、基本資料類型、基本指令redis的基本資料類型redis的簡單操作語句

SDS與C字元串的差別

  1. 計數方式不同:C字元串的長度計算是通過周遊之後得到的資料複雜度為O(n),SDS有一個專門存儲字元串長度的位置len
  2. 杜絕緩沖區溢出:C字元串中兩字元串拼接是直接在第一字元串後拼接上另一個字元串
    來啊!快看---redis存儲結構、基本資料類型、基本指令redis的基本資料類型redis的簡單操作語句

    在不計算記憶體空間的情況下有可能會發生空間溢出的情況

    而SDS會先比較free空間大小是否能放得下新的字元串,如果可以再進行拼接,如果不夠的話則進行擴容

  3. 減少修改字元串時帶來的記憶體重配置設定次數
  • 空間預配置設定(進行拼接添加的時候),在第一次建立字元串的時候,會配置設定合适的空間用來存放SDS對象,不過并不會配置設定多餘的free空間(防止空間浪費),當需要進行拼接的時候會配置設定free空間來用,這裡配置設定的free空間會比要拼接的字元串長度大(這是為了防止緊接着還會有拼接操作)
    來啊!快看---redis存儲結構、基本資料類型、基本指令redis的基本資料類型redis的簡單操作語句
  • 惰性空間釋放(進行裁剪的時候),在進行字元串裁剪的時候,其空餘出來的空間并不會馬上被回收,而是會在free屬性上進行記錄長度,防止緊接着會有添加操作,如果這個空間不需要啦,會調用方法進行删除使free變為0
    來啊!快看---redis存儲結構、基本資料類型、基本指令redis的基本資料類型redis的簡單操作語句
  1. 二進制安全

我們看到上面字元串中會有很多‘\o’的空字元串穿插的中間,在C字元串中其是結束的标志,那麼遇到這個‘\o’豈不是就代表這個字元串沒有後面啦,

但是當我們存儲一個二進制資料(圖檔、視訊等)的時候會有很多的‘\o’存在,是以用C字元串就很不安全,而redis則不用考慮這個問題

2、list

看一下代碼有助于了解結構:

//list的節點結構
typedef struct listNode { /*節點*/
struct listNode *prev;
struct listNode *next;
void *value; /*value用函數指針類型,決定了value可以是sds,list,set,dict等類型*/
} listNode;


//下面是list結構的代碼
typedef struct list { /*連結清單結構*/
listNode *head; /*頭節點*/
listNode *tail; /*尾節點*/
/*類似java類裡的的方法,友善調用*/
void *(*dup)(void *ptr); /*複制節點*/ //說實話,我不是很懂這個函數指針的意思,如有清楚地可以給我留言,謝謝。
void (*free)(void *ptr); /*釋放節點*/
int (*match)(void *ptr, void *key); /*比對節點,傳回key值得index,但是我不清楚他在那裡實作的*/
unsigned long len; /*記錄連結清單的長度*/
} list;
           

什麼?代碼沒看懂,沒事,看一下下面的圖檔一目了然

Redis中,list的實作是一個雙端連結清單,這樣可以友善的擷取其前後的節點值,友善之後對節點的查找;Redis通過list來對listNode進行持有,分别記錄list的頭尾節點和list長度,可在O(1)的時間複雜度上進行查找;

來啊!快看---redis存儲結構、基本資料類型、基本指令redis的基本資料類型redis的簡單操作語句
另外,list還實作了疊代器對連結清單進行周遊,可正向可反向,非常友善,代碼如下;
typedef struct listIter {
listNode *next;
int direction; //标注疊代器的運作方向
} listIter;
           
list在Redis中運用相當廣泛,除了實作清單外,釋出和訂閱、慢查詢、螢幕等功能也使用了連結清單來擷取,另外,Redis伺服器還使用連結清單來持有 多個用戶端的狀态資訊,以及用連結清單來建構用戶端輸出緩沖區。

3、dict字典

字典結構是整個Redis的核心資料結構,基本上是其内部結構的縮影。

對于其結構下面給出代碼和圖檔

這裡簡單說一下,dict持有兩個dictht結構,一個用來存儲資料,一個用來在rehash時使用,而dictht 數組是dictEntry 節點的持有者,并且在dictht 下仿佛是一個hashMap結構(不熟悉hashmap的話,可以看一下這個)

//dictEntry是最核心的字典結構的節點結構,它儲存了key和value的内容;另外,next指針是為了解決hash沖突,字典結構的hash沖突解決方法是拉鍊法,對于hashcode重複的節點以連結清單的形式存儲。
typedef struct dictEntry {
void *key;
union {
void *val;
uint64_t u64;
int64_t s64;
double d;
} v;
struct dictEntry *next;
} dictEntry;


//dictht是節點dictEntry的持有者,将dictEntry結構串起來,table就是hash表,其實dictEntry *table[]這樣的書寫方式更容易了解些,size就是table數組的長度,used标志已有節點的數目。
typedef struct dictht {
dictEntry **table;
unsigned long size;
unsigned long sizemask; /*hash表的掩碼,總是size-1,用于計算hash表的索引值*/
unsigned long used;
} dictht;


//dict是最外層的字典結構的接口形式,type标志類型,privdata标志其私有資料,
//dict持有兩個dictht結構,一個用來存儲資料,一個用來在rehash時使用,rehashidx标志是否正在rehash(因為Redis中rehash是一個漸近的過程,正在rehash的時候rehashidx記錄rehash的階段,否則為-1)。
//注:rehash是一個為了讓負載因子(load_factor=used/size)控制在一個合理的範圍内而重新配置設定記憶體和擴充結構的過程。
//iterators是一個疊代器,用于記錄目前疊代的數目
typedef struct dict {
dictType *type;
void *privdata;
dictht ht[2];
long rehashidx; /* rehashing not in progress if rehashidx == -1 */
int iterators; /* number of iterators currently running */
} dict;
           
來啊!快看---redis存儲結構、基本資料類型、基本指令redis的基本資料類型redis的簡單操作語句

4、intset整數集合

typedef struct intset { /*整數集合的資料結構*/
uint32_t encoding; //編碼方式
uint32_t length;
int8_t contents[];
} intset;
           

當一個集合元素隻有整數并且數量元素不多的時候,可以選擇用整數集合來作為其底層實作。整數集合的資料結構如上所示。

重點說一下這個contents數組,它存儲集合中的内容,并且以從小到大的順序排列,并保證其沒有重複的元素。

雖然定義中其類型為int8_t,但具體編碼方式還是取決于encoding。

來啊!快看---redis存儲結構、基本資料類型、基本指令redis的基本資料類型redis的簡單操作語句

當最大的數在以上取值範圍之内是便會更新到這個更大範圍的資料類型,但是如果移除了這個最大取值,不會降級。

分範圍定義其類型有兩個好處:提高其靈活性,節約記憶體。但是也增加了更新的開銷。

在Redis 中

整數集合的應用範圍不是很廣,隻在實作集合時用到。

5、zskiplist(跳躍表)實作有序連結清單zset

我們之前學習過“跳表”結構,這裡就到了時機運用的時刻

看一下令人頭疼的代碼:

//zskiplistNode是跳躍表的節點結構,obj指針指向存儲具體對象的位址,score标志分數。
typedef struct zskiplistNode {
robj *obj; //存儲對象的指針
double score; //分數
struct zskiplistNode *backward; //後退指針,每次隻能退一步
struct zskiplistLevel {
struct zskiplistNode *forward; //前進指針,每次可以跳躍好幾步
unsigned int span; //這個就是決定前進指針能跳躍幾步的跨度标志
} level[];
} zskiplistNode;

//zskiplist持有節點,并記錄頭結點和尾節點以及長度,level記錄層數最大的節點的層數,也就是zskiplistNode中最大的level.size。

typedef struct zskiplist {
struct zskiplistNode *header, *tail;
unsigned long length;
int level;
} zskiplist;
           

什麼是跳躍表?

它其實是一種随機化的資料結構,一個多層的有序連結清單,一種基于機率統計的插入算法。按照其score分數進行排序,分數越高越往後

redis中的zset為什麼不使用紅黑樹而使用跳躍表

  1. 首先,在做範圍查詢的時候,平衡樹的操作要比跳躍表複雜。因為平衡樹,在查詢到最小值的時候,還需要采用中序周遊去查詢最大值。而skipList隻需要在找到最小值後,對第一層的連結清單(也就是最底層的連結清單)進行若幹次周遊即可。
  2. 平衡樹的删除和插入,需要對子樹進行相應的調整,操作複雜。而skiplist隻需要修改相鄰的節點即可。
  3. 在做查詢操作的時候,skiplist和平衡樹都是O(logN)的時間複雜度。
  4. 從整體上來看,skiplist算法實作的難度要低于平衡樹。

你等待已久的圖檔來啦

來啊!快看---redis存儲結構、基本資料類型、基本指令redis的基本資料類型redis的簡單操作語句

6、ziplist(壓縮表)

ziplist是一個編碼後的清單,是由一系列特殊編碼的連續記憶體塊組成的順序型資料結構,特殊的設計使得記憶體操作非常有效率,此清單可以同時存放字元串和整數類型,清單可以在頭尾各邊支援推加和彈出操作在O(1)常量時間,但是,因為每次操作涉及到記憶體的重新配置設定釋放,是以加大了操作的複雜性
typedef struct zlentry {
//prevrawlen為上一個資料結點的長度,prevrawlensize為記錄該長度數值所需要的位元組數
unsigned int prevrawlensize, prevrawlen;
//len為目前資料結點的長度,lensize表示表示目前長度表示所需的位元組數
unsigned int lensize, len;
//資料結點的頭部資訊長度的位元組數
unsigned int headersize;
//編碼的方式
unsigned char encoding;
//資料結點的資料(已包含頭部等資訊),以字元串形式儲存
unsigned char *p;
} zlentry;
           

zlentry是實際存儲資料的節點。一個ziplist可以有多個zlentry節點,具體形式如下:

來啊!快看---redis存儲結構、基本資料類型、基本指令redis的基本資料類型redis的簡單操作語句
壓縮表之是以成為壓縮表,是因為它起到了一定的壓縮功能,對于其他的資料結構為了快速定位,使用了大量的指針結構,這樣對于長度較大的資料優勢明顯,但是對于長度非常小的資料,比如說一個表裡的每一個資料長度都很短,但是資料量并不小,這樣的話,就會出現大量的指針結構,造成記憶體浪費,而壓縮表則配置設定了一塊連續記憶體來存儲,就避免了大量的指針結構,節省了記憶體。另外,ziplist也使用了動态配置設定記憶體的方法,也一定程度上避免了記憶體的浪費。下圖(此圖來自書本)是記憶體的每塊代表的含義:
來啊!快看---redis存儲結構、基本資料類型、基本指令redis的基本資料類型redis的簡單操作語句

壓縮表在Redis中的應用隻存在于hash和list結構的實作中,為了在存儲時節省記憶體。

redis的簡單操作語句

1.String類型的資料存儲擷取

set key value:設定key的值為value,若存在則覆寫,不存在則自動建立decrby

get key:擷取key的值,不存在傳回nil表示為空,資料若不為String也回傳回錯誤資訊

getset key value:首先擷取key的值再對其進行修改 del key:删除key及其資料

incr key:對key的資料進行加一操作,隻能對滿足Integer的資料起作用。若值不存在,那麼初始化為0

decr key:對key的資料進行減一操作,隻能對滿足Integer的資料起作用

incrby key increment(具體數字):對key值增加

increment decrby key decrment(具體數字):對key值減少

decrement append key value:在末尾添加資料,若key不存在則建立

2.hash類型資料(即鍵值對形式)

hset key filed value:修改key下filed的value,若不存在則自動建立

hget key filed:擷取key下filed的值

hmget key filed1 filed2 filed3…:擷取key下的多個filed值 hincr

hgetall key:擷取所有key中filed的值,這裡不會顯示filed,隻有value

hdel key filed1 filed2 …:删除key下的filed,可同時多個删除

del key:删除整個key中内容

hincrby key filed incrment:增加數字

hexsit key filed:是否存在

hlen key:key中有幾個filed

hkeys key:顯示所有key

3.list類型

該資料結構是一個**雙向連結清單,**有頭插和尾插兩種方式。輸出的過程遵從棧的方式

lpush key value1 value2…:使用頭插法插入資料

rpush key value1 value2…:使用尾插法插入資料

lrange key start end:顯示list,從頭到尾,strat表示開始顯示位置最小0,end表示結束位置,-1表示末尾,-2表示末尾第二個

lpop key:從頭部彈出元素

rpop key:從尾部彈出元素

llen key:擷取list中的個數

4.set集合資料類型

set集合與list的最大差別是,set的無序的,取出資料的順序是不可知的,其次set集合中不允許出現相同的value

sadd key value1 value2 …:添加資料

srem key value1 value2…:移出指定的資料

sinter key1 key2 key3:集合的交集

sunion key1 key2 key3:集合的并集

繼續閱讀