Redis 学习笔记(篇一):字符串和链表

本次学习除了基本内容以外主要思考三个问题:why(为何)、what(原理是什么)、which(同类中还有哪些相似的东西,相比有什么区别)。html

因为我对 java 比较熟悉,而且 java 中也有字符串和链表。因此本篇暂拿 redis 中的字符串和链表与 java 进行对比。java

字符串

先看几个问题:node

  • 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

redis 定义的字符串结构是什么?java 的呢?

redis 中的字符串是本身构建了一种叫作简单动态字符串(SDS)的抽象类型标识的。它的具体结构以下:数组

struct sdshdr {
    //字符串长度
    int len;
    // buf中未使用的字节数
    int free;
    // 字节数组,用于保存字符串
    char buf[];
}

redis 的这个数据结构,很好的解决了 C 语言字符数组的第一、二、三、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 能够当作可变的字符数组)?说到可变,这里遗留一个问题:redis中的SDS、Java中的 (StringBuilder、StringBuffer、ArrayList ),他们的扩容规则分别是什么呢?学习

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 的链表有什么不同?

基本一致,都是双向不循环链表。

双向的优势就是能够向前遍历,也能够向后遍历。缺点就是每一个节点都须要浪费一个指针去指向前一个节点。

原文出处:https://www.cnblogs.com/wind-snow/p/11019260.html

相关文章
相关标签/搜索