On the way day day up...

redis源码学习——字符串&双端链表

字符串(SDS)

简单动态字符串 simple dynamic string ,SDS

sds结构体

struct sds{
	int len; //记录buf数组中已使用字节的数目,即sds所保存字符串的长度
	int free; //记录buf数组中未使用字节数
	char buf[];//长度为0,不占用内存空间
};//sizeof(struct sds) = 8B,即 int len + int free的长度

c零长数组 c内存分配方式

sds源码

源文件——sds.h
typedef char *sds;//定义一种数据类型,名叫sds,它是一种指向char类型的指针
//sds.h文件
//返回sds字符串长度
static inline size_t sdslen(const sds s) {
    struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
    return sh->len;
}

解释说明:

  • sizeof(struct sdshdr)) 值为8B,即len + free的长度;
  • sds是指向char类型的指针类型,因此s是指向sds结构中buf数组的指针
  • s-(sizeof(struct sdshdr)))就是说s指针向上移动8B,即指向了sds结构体的首地址
  • 因此sh->len就能得到sds结构体中len的值

这里用到了零长数组的特性。

sds特点

1. 空间预分配

当增加sds字符串长度时:

  • 如果扩展后,sds字符串的长度<1M(即len值),则分配相同长度的free空间(eg. len长度变为50B,则分配同样长度50B的空间给free字段,即50B+50B+1B,1B用来保存字符串结束符’\0’)
  • 如果扩展后,sds字符串长度>=1M,则分配1M的空间给free字段

2. 惰性空间释放

当缩短sds字符串长度时,释放的空间不会立即由内存收回,而是放到free属性中,以备将来使用(如果需要真正释放,sds也提供了相应的api)。

3. 二进制安全

当字符串中有空字符时,如果用c字符串来保存,空字符会被当做字符串的结束符,导致空字符后面的字符被截断。 sds字符串,通过len属性保存字符串长度,不会根据空字符截取,读取字符串时,会根据len长度读取相应长度的字符串,因此是二进制安全的。

4. 兼容部分c字符串函数

由于sds保留了c字符串的结束符’\0’,因此可以重用一部分string.h库定义的c字符串函数。

5. 常数复杂度获取字符串长度

sds记录了字符长度len,因此获取字符串长度的复杂度为O(1)

6. 杜绝缓冲区溢出

sds api在对字符串进行修改时,会先检查sds的空间是否满足需求,如果不满足,会自动分配相应的空间,因此不会出现缓冲区溢出的问题。

链表

链表数据结构

//adlist.h文件
//双端链表节点
typedef struct listNode{
	struct listNode *prev;
	struct listNode *next;
	void *value;//链表节点可以用来保存各种类型的值
}listNode;
//链表迭代器
typedef struct listIter{
    listNode *next;
    int direction;
}listIter;
typedef struct list{
	listNode *header;
	listNode *tail;	 
    void *(*dup)(void *ptr);  // 节点值复制函数  
    void (*free)(void *ptr);  // 节点值释放函数   
    int (*match)(void *ptr, void *key);// 节点值对比函数
	unsigned long len;	
}list;
//根据listNode中value的不同类型,实现相应的dum,free,match,从而实现“多态”

当列表的元素较多或每个元素对应的字符串长度较长时,使用list进行存储,否则使用ziplist

linux文本处理命令——sed/grep/find/xargs

sed、grep、find、xargs

sed

1. 替换文件内容

sed -i "s/test01/test02/g" `grep -rl test01 /home/forum`

说明:在/home/forum目录下,grep出含有test01字符串的文件,然后把test01替换成test02

2. sed替换换行符

参考:http://my.oschina.net/shelllife/blog/118337

sed命令从文本流中读取一行命令到模式空间进行相应处理,因此在处理换行时,有些特殊。

echo "a,b,c,d" | sed 's/,/\n/g' #将字符串a,b,c,d中的逗号替换成换行符

echo "a,b,c,d" | sed 's/,/\n/g' | sed 's/\n/,/g' # 后面这个sed没有生效

说明:sed读取一行时,会先把换行符去掉,命令处理完后,再加上。所以无法使用上面这个命令(sed ‘s/\n/,/g’)进行换行符替换。

使用下面的命令,可以将换行符进行替换:

sed ':label;N;s/\n/,/;b label' filename

sed ':label;N;s/\n/,/;t label' filename

说明:该命令可以实现将文本中的所有换行进行替换,上面例子中,将换行替换成逗号。

:label; 这是一个标签,用来实现跳转处理,名字可以随便取(label),后面的b label就是跳转指令

N; N是sed的一个处理命令,追加文本流中的下一行到模式空间进行合并处理,因此是换行符可见

s/\n/:/; s是sed的替换命令,将换行符替换为冒号

b label 或者 t label b 或t 是sed的跳转命令,跳转到指定的标签处

实际应用eg:

echo "a,b,c,d" | sed 's/,/\n/g' | sed ':x;N;s/\n/,/g;b x'  #后面这个sed可以将换行替换成','

grep

1. grep 参数

-l 表示只输出包含匹配字符的文件名

find

1.除查找某类文件

find . ! -name "*.svn-base" | xargs grep g6y8LQV9ZJgj --col

说明: 找出当前目录中后缀名不为.svn-base的文件,然后从这些文件中查找含有字符串g6y8LQV9ZJgj 的文件。

2.按指定目录深度查找

find . -maxdepth 1 -name '*' #只在当前目录查找

find . -naxdepth 1 type f #查找当前目录中的文件

说明:maxdepth后面的参数值,表示距当前目录指定的深度,1表示当前目录,2表示一级子目录,以此类推。如果没有指定maxdepth参数,find会查找当前目录及其所有子目录下的文件。

xargs

该命令主要功能是:从输入中构建和执行shell命令。 xargs默认以空白字符 (空格, TAB, 换行符) 来分割记录

1. 与find命令结合

find -name '*.log' | xargs rm #找到当前目录及其子目录中的.log文件,然后删除

find -name '*.log' -print0 | xargs -0 rm #作用同上

说明:-print0 表示 find命令在打印出一个文件名之后接着输出一个 NULL 字符 (‘’) 而不是换行符, 然后再告诉 xargs 也用 NULL 字符来作为记录的分隔符

2. xargs -i 参数

find . -name '*.bak' | xargs -i cp {} ../  #find找到后缀为.bak的文件,然后将这些文件cp到上一层目录

说明:xargs后面加上-i参数,直接用{}代替管道之前的标准输出的内容。

参考: http://www.cnblogs.com/mchina/archive/2012/07/02/2573313.html

linux文本处理命令——awk

awk入门基础命令:

awk [-F '分隔符']   '处理命令' 文件名

eg:

awk -F ' ' '{print $2}' test.txt

意思是,以空格为分隔符,将test.txt中的每行进行分隔,然后输出第2列。$0则表示所有域,$1表示第一个域,$n表示第n个域。

cat /etc/passwd | awk -F ':' 'BEGIN {print "name, shell"} {print $1 ", "$7} END {print "test, end"}'

awk工作流:先执行BEGIN,然后读取文件,按行读取(/n换行分隔符),然后执行对应的动作action,直到所有行读取完毕,最后再执行END操作。

awk正则匹配:

awk -F: '/root/' /etc/passwd

匹配pattern,这里匹配了root才会执行action,这里action为空,默认输出每行的内容

awk内置变量:

ARGC               命令行参数个数
ARGV               命令行参数排列
ENVIRON            支持队列中系统环境变量的使用
FILENAME           awk浏览的文件名
FNR                浏览文件的记录数
FS                 设置输入域分隔符,等价于命令行 -F选项
NF                 浏览记录的域的个数
NR                 已读的记录数
OFS                输出域分隔符
ORS                输出记录分隔符
RS                 控制记录分隔符
awk -F: '{printf("filename:%12s,linenumber:%3s\n",FILENAME,NR)}'  /etc/passwd
awk -F: '{print "filename: "FILENAME, "linenumber: "NR}' /etc/passwd

awk编程:

未完待续。。。

http://www.cnblogs.com/ggjucheng/archive/2013/01/13/2858470.html

https://www.ibm.com/developerworks/cn/education/aix/au-gawk/index.html

https://awk.readthedocs.io/en/latest/chapter-one.html