bitfield 概述
bitfields(位域):位域是计算机中的术语,也称 位段,它是指信息在存储时,并不需要占用一个完整的字节,而只需占用一个或几个二进制位,这样做的目的是为了节约存储空间。
在 C 编程语言中,除了基本的数据类型(char、short、int、、long、float、double)外,还有一些特殊的数据类型,如下图所示:
Q:结构体是什么?
因为基本的数据类型没办法和现实生活中的对象一 一对应。比如说 student 这个对象,在没有结构体之前,你需要这样定义这个对象的属性:
char name[20];
int age;
float score;
在代码层面,无法知道它们之间的关联性,于是,C 语言提供了一种解决办法——用户自定义需要的任何类型并结合起来的一个整体,即结构体,定义结构体的代码如下:
struct Student
{
char name[20];
int age;
float score;
};
struct 是关键字;Student 是标志(标签),可自定义;包含的则是结构体的三个成员。
数组:相同数据类型的元素按一定顺序排列的集合。在内存当中,数组的元素被保存在一块连续的内存当中。
结构体:不同数据类型的成员组合成的一个整体。理论上来说,结构体的成员被保存在一块连续的内存当中,但是由于编译器的实现不同,为了内存对齐(字节对齐),各个成员之间在内存当中可能会存在「缝隙」。
位域属于一种特殊的结构体,在成员名称后面加上冒号,代码层面形如这样的:
struct Bit
{
char a:2;
char b:4;
char c:2;
};
冒号后面定义成员所占的 bit 数。
众所周知,char 在 C 语言当中需要占用 1 Byte,而在位域当中,成员只需要占用一个或几个 bit。
|<----- 1Byte ----->|
--- --- --- --- --- --- --- ---
| | | | | | | | |
--- --- --- --- --- --- --- ---
|<- a ->|
| <---- b ----> |
|<- c ->|
Redis 中的位域
这种数据类型是 string 数据类型的拓展,你可以将一个字符串看作是由 bit 组成的数组,并对数组中的任意偏移进行访问,从而高效地使用内存。
# 比如说,使用 string 数据类型:
192.168.100.3:6379> select 7
OK
192.168.100.3:6379[7]> keys *
(empty array)
192.168.100.3:6379[7]> set k1 hello
Q:如果我要修改 k1 的 value,应该怎么操作?
很简单啊!直接用 set
命令更新已经存在的 key 的值;或者使用 setrange
命令。
192.168.100.3:6379[7]> exists k1
(integer) 1
192.168.100.3:6379[7]> set k1 HELLO XX
OK
192.168.100.3:6379[7]> get k1
"HELLO"
192.168.100.3:6379[7]> setrange k1 0 hello
(integer) 5
192.168.100.3:6379[7]> get k1
"hello"
string 数据类型的修改是基于字符串或字符这个级别,但是 bitfield 可以将这个级别下调到 bit 级别。
字符 | ASCII 码(二进制) | ASCII 码(十进制) |
---|---|---|
h | 01101000 | 104 |
e | 01100101 | 101 |
l | 01101100 | 108 |
l | 01101100 | 108 |
o | 01101111 | 111 |
比如我要将 hello 字符串变更为 HELLO,只需要修改相应的 bit 即可:
字符 | ASCII 码(二进制) | ASCII 码(十进制) |
---|---|---|
H | 01001000 | 72 |
E | 01000101 | 69 |
L | 01001100 | 76 |
L | 01001100 | 76 |
O | 01001111 | 79 |
相关命令
只有一个 bitfiled
命令,但是相关的参数非常多,用法为:
查看
192.168.100.3:6379[7]> get k1
"hello"
# i8 表示有符号的 8 bit,偏移量为 0
## 返回104, 即字符 h
192.168.100.3:6379[7]> bitfield k1 get i8 0
1) (integer) 104
# 返回101, 即字符 e
192.168.100.3:6379[7]> bitfield k1 get i8 8
1) (integer) 101
修改单个字符
# 将 e 字符修改为大写的 E
## 69 即 ASCII 的 E
## 返回的是旧 ASCII
192.168.100.3:6379[7]> bitfield k1 set i8 8 69
1) (integer) 101
192.168.100.3:6379[7]> get k1
"hEllo"
自增/自减
# 从第二个 bit 开始,兑接下来的 3 个 bit 自增 1,u 表示是无符号的
## 3 bit 无符号,意味着它的范围为 [0,7]
192.168.100.3:6379[7]> bitfield k1 incrby u3 2 1
1) (integer) 6
192.168.100.3:6379[7]> bitfield k1 incrby u3 2 1
1) (integer) 7
# 超过了范围并返回 0,即碰到了向上溢出,默认执行 WARP 行为
192.168.100.3:6379[7]> bitfield k1 incrby u3 2 1
1) (integer) 0
192.168.100.3:6379[7]> get k1
"@Ello"
# 还原为 h
192.168.100.3:6379[7]> bitfield k1 set i8 0 104
1) (integer) 64
192.168.100.3:6379[7]> get k1
"hEllo"
当碰到向上溢出(overflow)或者向下溢出(underflow)情况时会采取一些行为,也就是所谓的 溢出控制:
- WARP(默认)
- SAT
- FAIL
这个 bitfield 基本了解就可以了,因为实际情况下也使用得极少。