1. 程式人生 > >Redis 學習筆記(篇一):字串和連結串列

Redis 學習筆記(篇一):字串和連結串列

本次學習除了基本內容之外主要思考三個問題:why(為什麼)、what(原理是什麼)、which(同類中還有哪些類似的東西,相比有什麼區別)。

由於我對 java 比較熟悉,並且 java 中也有字串和連結串列。所以本篇暫拿 redis 中的字串和連結串列與 java 進行對比。

字串

先看幾個問題:

  • redis 中有沒有使用 C 語言的字元陣列作為字串? 答案:沒有
  • 那麼 C 語言的字元陣列有著什麼侷限性以至於java和redis都重新定義了自己的字串呢?
  • redis 定義的字串結構是什麼?java的呢?
  • java 和 redis 的字串結構有什麼區別?為什麼會形成這樣的區別?兩者哪一個更好呢?

C 語言陣列的侷限性

  1. 取字串長度不方便,c語言的字元陣列沒有儲存字串的長度,所以需要遍歷。Time(時間複雜度):O(n)
  2. 不安全,兩個字元陣列進行拼接時可能會造成快取區溢位,導致別的資料受損。
  3. 每次增刪字串都會導致重新分配或者回收記憶體,影響效率。
  4. 以\0判斷是否結尾,只能儲存文字資料。
  5. 比較兩個字串是否相等,需要迴圈比較每個字元,Time:O(n)。

由於 C 語言的字元陣列有著以上的侷限性,所以 redis 和 java 都重新定義了自己的字串結構。

redis 定義的字串結構是什麼?java 的呢?

redis 中的字串是自己構建了一種叫做簡單動態字串(SDS)的抽象型別標識的。它的具體結構如下:

struct sdshdr {
    //字串長度
    int len;
    // buf中未使用的位元組數
    int free;
    // 位元組陣列,用於儲存字串
    char buf[];
}

redis 的這個資料結構,很好的解決了 C 語言字元陣列的第1、2、3、4點侷限性。而且值得注意的是 sdshdr 中的 buf 陣列,也是以“\0”結尾的,所以buf的總長度為:len + free + 1。那麼為什麼要浪費這一個位元組呢?主要是想重用 C 語言對於字串的一些函式,比如連線、輸出等等。

這裡有一個點需要注意一下,就是 redis 不會對儲存的字串進行編碼,儲存和獲取都是直接通過二進位制進行傳輸的,所以理論上來說只要客戶端的編碼和解碼方式一樣,是不會出現亂碼的。這也是上面 buf 的註釋裡寫位元組陣列的原因。

java 的字串呢?則是直接把字串搞成了一個常量。如下:

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];

    /** Cache the hash code for the string */
    private int hash; // Default to 0
    ......
}

java 的字串也解決了c語言字元陣列的第 1/2/3/4 點。

  • 因為 java 中陣列直接儲存了長度,所以規避了 C 語言陣列的第 1/4 點侷限性。
  • 因為 String 是常量,所以c語言字元陣列的第 2/3 點侷限性不存在。
  • 為了解決第 5 點侷限性,所以java中的字串引入了 hashCode 的概念。
    正是因為 hash 直接儲存在字串中,所以才把字串設定成常量(否則,每改一次字串的值,都需要重新計算 hashcode 的值,太浪費效率)。正因為字串是常量,所以才有了 StringBuilder、StringBuffer 的可變字串。。。。。。

現在我們再回去看一下 redis 中字串的結構,有一個數組,有陣列中儲存資料的長度。想到了什麼?Java中 StringBuilder、StringBuffer、ArrayList 是不是都是這個結構?他們的共同點呢?是不是都是可變的陣列( StringBuilder、StringBuffer 可以看成可變的字元陣列)?

java 和 redis 的字串比較

  1. redis 字串中的字元陣列(buf)是以“\0”結尾的,Java 中的不是。為什麼呢?
    因為 redis 的設計之初就是效率高、執行快的快取。所以才會選擇以 C 語言實現(因為c是所有結構化語言中執行最快的),並且和c的字元陣列一樣的結尾,以便可以直接使用c的函式而不重複造輪子。
    java 的設計之初就是一門跨平臺的語言,所以字串的所有函式都是自己實現的,所以不需要額外浪費一個位元組的空間。
  2. redis 定義了一個 sds 的字串,但也是基於 C 的字元陣列。而java直接把c的陣列都重新定義了( C 的陣列不能直接獲得陣列長度)。
  3. 關於字串比較是否相等的問題:
    java 中有 hashcode 的概念來提升字串比較是否相等的速度。redis 中又是如何做的呢?比如 get key 時,如何定位的一個具體的 key,而得到的 value 呢? 這個問題,暫時還不知道。

造成這些差異的原因,主要就是其定位不一樣。redis 的定位是快取、要求快。java 的定位是語言,最重要的是跨平臺及擴充套件性。

連結串列

也同樣看三個問題:

  1. 連結串列為什麼產生?
  2. 原理是什麼,和別的資料結構再來個對比。
  3. 和 java 的連結串列有什麼不一樣?

連結串列為什麼產生?

連結串列的產生主要是因為陣列的侷限性。
比如:插入、刪除慢。不能動態增加元素。

原理是什麼

redis 的連結串列結構如下:

typedef struct listNode {
    struct listNode *prev; //前驅節點,如果是list的頭結點,則prev指向NULL
    struct listNode *next;//後繼節點,如果是list尾部結點,則next指向NULL
    void *value;            //萬能指標,能夠存放任何資訊
} listNode;

typedef struct list {
    listNode *head;     //連結串列頭結點指標
    listNode *tail;     //連結串列尾結點指標

    //下面的三個函式指標就像類中的成員函式一樣
    void *(*dup)(void *ptr);    //複製連結串列節點儲存的值
    void (*free)(void *ptr);    //釋放連結串列節點儲存的值
    int (*match)(void *ptr, void *key); //比較連結串列節點所儲存的節點值和另一個輸入的值是否相等
    unsigned long len;      //連結串列長度計數器
} list;

java 的連結串列結構如下:

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable{
    transient int size = 0;

    /**
    * Pointer to first node.
    * Invariant: (first == null && last == null) ||
    *            (first.prev == null && first.item != null)
    */
    transient Node<E> first;

    /**
    * Pointer to last node.
    * Invariant: (first == null && last == null) ||
    *            (last.next == null && last.item != null)
    */
    transient Node<E> last;
    .......
}

redis 的連結串列和 java 的連結串列有什麼不一樣?

基本一致,都是雙向不迴圈連結串列。

雙向的優點就是可以向前遍歷,也可以向後遍歷。缺點就是每個節點都需要浪費一個指標去指向前一個節點