zoukankan      html  css  js  c++  java
  • 二、Redis基本操作——String(实战篇)

    小喵万万没想到,上一篇博客,居然已经被阅读600次了!!!让小喵感觉压力颇大。万一有写错的地方,岂不是会误导很多筒子们。所以,恳请大家,如果看到小喵的博客有什么不对的地方,请尽快指正!谢谢!

    小喵的唠叨话:上一篇博客,我们详细的介绍了Redis 中String数据类型的底层实现(http://www.cnblogs.com/idiotgroup/p/5450157.html),相信大家已经在原理上掌握的相当不错了。这次,我们就介绍Redis的命令行操作。当然,我们实际开发的时候可能不会太经常直接用到Redis的命令,而是通过一些数据库的封装来操控(就像我们虽然会使用SQL,但是实际开发的时候,总是有一些特别好用的库,既安全又方便)。但是,学习最基础的Redis命令,是针对各种语言的以不变应万变的法宝,毕竟库是针对于语言的。同时,学会了Redis的基本命令,将来对各种库的掌握也能更迅速。

    小喵的个人博客地址: http://www.miaoerduo.com/ ,欢迎随时骚扰~

    该博客原地址: http://www.miaoerduo.com/redis/二、redis基本操作-string实战篇.html ,排版应该更精美一点。

    Redis基本操作——String(实战篇)

    关于Redis的命令,主要参考Redis的官方文档(http://redis.io/commands#string)。小喵这里主要像是翻译一下,并且给出一些实用的例子。排版上,小喵按功能重新组织了一下指令,并将比较常用的指令放在了最前面。同时也增加了锚点,方便大家查阅。小喵这里的Redis版本是3.0.7,应该是目前的最新版。

    注:redis的命令(SET,GET等)是不区分大小写的(KEY和VALUE区分的),为了方便所以小喵的操作可能都是小写的。

    指令清单:

    一,SET的相关操作

    1,SET key value [EX seconds] [PX milliseconds] [NX|XX]
    Set the string value of a key

    将键key设置成指定的字符串value。

    我们知道Redis是一种key-value数据库(当然这么说可能不够严谨,因为Redis支持很多高级的数据结构)。因此所有的数据都是通过key来访问,key就是键。

    SET指令可以将字符串的value和key绑定在一起。

    如果不加额外的参数。当key中已经保存了一个值的话,这个值会被覆盖成新的值,并且忽略掉原始类型(一个key对应的不一定是一个字符串,也可以是List等其他的数据结构,这些结构在后面的文章里面会陆续介绍);如果key不存在,那么则会在数据库中新增一个key,对应的值就是你刚刚设置的。

    例子:

     1 redis> get name
     2 (nil)
     3 redis> set name miaoerduo
     4 OK
     5 redis> get name
     6 "miaoerduo"
     7 redis> set name miao
     8 OK
     9 redis> get name
    10 "miao"

    GET命令用于查看key对应的值,我们下面会讲到。

    第一个get,由于name这个key不存在,因此我们得到的结果是nil,也就是说空。

    之后我们设置一次,然后就get到我们的结果了。之后再设置一次,会发现结果已经被覆盖。

    这就是set命令的基本用法。

    我们看到文档的后面有一些可选的参数。下面依次来介绍:

    EX seconds:设置key的过时时间,单位为秒。

    PX milliseconds:设置key的过期时间,单位为毫秒。

    以ex为例:

    1 redis> set name miaoerduo ex 10
    2 OK
    3 redis> get name
    4 "miaoerduo"
    5 redis> get name
    6 (nil)

    我们设置了name的过期时间为10秒。

    在设置完name之后,立刻获取name的值,可以得到正确的输出。但是过了10s之后,发现只能得到一个nil。说明这个key已经被清空了。

    那么这个设置有什么作用呢?

    我们说几个常见的应用场景:

    a,验证码

    我们经常在登录一些网站或是进行付款等操作的时候,都会收到一些验证码,并且说10min后失效。

    实际上就可以通过下面一条指令来实现:

    set phone_num code ex 600

    用手机号作为key,验证码作为值,超时6min。这样当你输入好验证码,提交的时候,后台就可以了先get phone_num,再比较你的输入和数据库里面存的值,从而完成身份的验证。

    b,session

    早前,网站通过cookie来保存用户的用户名和密码,之后出现了很多的安全隐患,因此就提出了session的机制。

    用户登陆成功之后,本地的cookie会保留一个较长的随机码,而网站后的后台则存储了这个随机码和你的用户id的对应关系。在你第二次登录的时候,cookie会传输到后台,而后台则根据你的随机码,获取你的用户信息,如果符合,则自动登录。这样,即使网站上有不法分子获取了你的cookie,也得不到你的任何信息,因为你的真实的有用的信息都存储在网站的后台。

    我们在登录邮箱的时候,通常都会有一个选项,7天内自动登录。这其实就是给后台存的session设置了一个超时。聪明的你是不是已经会自己实现了呢?

    NX:(if Not eXist)只有键key不存在的时候才会设置key的值

    XX:只有键key存在的时候才会设置key的值

    NX通常用于实现锁机制,XX的功能,小喵暂时木有头绪。。。想到应用场景的筒子们可以留言告诉小喵,小喵看到了一定会更新的。

    举个例子:

     1 redis> del name
     2 (integer) 0
     3 redis> get name
     4 (nil)
     5 redis> set name miaoerduo nx
     6 OK
     7 redis> set name miaoerduo nx
     8 (nil)
     9 redis> get name
    10 "miaoerduo"
    11 redis> set name miao xx
    12 OK
    13 redis> get name
    14 "miao"
    15 redis> del name
    16 (integer) 1
    17 redis> set name miao xx
    18 (nil)

    首先我们使用del指令删除了name(这个指令后面会介绍)。之后使用nx模式设置name,第一次成功了,而第二次就失败了,因为name已经存在了。之后使用xx模式修改,发现确实可以。del name之后,再使用xx模式,就失败了,因为此时的name已经不存在了。

    以上,就是set的所有的用法。

    2,SETRANGE key offset value
    Overwrite part of a string at key starting at the specified offset

    这个命令可以覆盖掉key对应的string的一部分。offset表示需要覆盖的字符串的起始位置,之后会用value的值,覆盖掉原始string的对应位置的数据。

    这里有一些比较有意思的操作:如果原始key不存在,则默认为一个长度为0的字符串。如果offset超过原始string的长度,那么会在之前的string后面补充0以达到offset。如果value的长度超过了原始string后面可以覆盖的部分,则Redis内部会重新申请内存,完成数据的追加(还记得上一章的内容吗?),这时候数据库由于需要分配内存,可能会出现阻塞(需要分配的内存越大,阻塞时间越长)。

    例子:

     1 redis> set str "hello world"
     2 OK
     3 redis> setrange str 6 redis
     4 (integer) 11
     5 redis> get str
     6 "hello redis"
     7 redis> setrange str 15 aha
     8 (integer) 18
     9 redis> get str
    10 "hello redis\x00\x00\x00\x00aha"
    11 redis> del str
    12 (integer) 1
    13 redis> setrange str 5 "hello miao"
    14 (integer) 15
    15 redis> get str
    16 "\x00\x00\x00\x00\x00hello miao"

    在这个实验中,我们先新建了一个str,内容是"hello world",之后,从位置6开始写入字符串"redis",则得到了"hello redis"。之后我们在15的位置,写入"aha",这是offset已经比字符串的长度要大了,则Redis会默认填充0(\x00是0的16进制表达),之后再追加字符串。最后,我们给一个不存在的key使用setrange设置了一个值,结果表现得和空字符串一样。

    3,MSET key value [key value ...]
    Set multiple keys to multiple values

    一次性设置多个key-value。如果key的值已存在,则会直接覆盖。

    相当与同时调用多次SET命令。不过要注意的是,这个操作是原子的。也就是说,所有的keys都一次性设置的。如果你同时运行两个MSET来设置相同的keys,那么操作的结果也只会是两次MSET中的后一次的结果,而不会是混杂的结果。

    例子:

    1 redis> mset name1 miaoerduo name2 miao name3 love
    2 OK
    3 redis> get name1
    4 "miaoerduo"
    5 redis> get name2
    6 "miao"
    7 redis> get name3
    8 "love"

    4,MSETNX key value [key value ...]

    Set multiple keys to multiple values, only if none of the keys exist

    一次性设置多个key-value。如果存在任何一个key已经存在,那么这个操作都不会执行。所以,当使用MSETNX的时候,要么全部key都被修改,要么全部都不被修改。

    当然这个操作也是原子的。

     1 redis> get name3
     2 "love"
     3 redis> msetnx name3 miaoerduo name4 miao
     4 (integer) 0
     5 redis> get name4
     6 (nil)
     7 redis> msetnx name4 miaoerduo name5 miao
     8 (integer) 1
     9 redis> get name4
    10 "miaoerduo"
    11 redis> get name5
    12 "miao"

    当name3已经存在的时候,我们设置name3和name4,发现,连name4都没有创建。当设置name4和name5的时候,由于这两个key之前都不存在,因此设置成功了。

    5,SETEX key seconds value
    Set the value and expiration of a key
    设置key的值和超时。和前面的set key value ex seconds一样。

    6,PSETEX key milliseconds value
    Set the value and expiration in milliseconds of a key
    设置key的值和超时。和前面的set key value px milliseconds一样。

    7,SETNX key value
    Set the value of a key, only if the key does not exist

    当key不存在的时候,设置key的值。和set key value nx一样。

    二、GET的相关操作

    1,GET key
    Get the value of a key

    根据给定的key,获取value值。这个操作,我们之前已经使用了很多次。

    如果key不存在,会返回nil。如果key对应的值不是string(List,Set等),则会报错,因为GET只能处理string类型的value。

    演示如下:

     1 redis> get name
     2 (nil)
     3 redis> set name "miaoerduo"
     4 OK
     5 redis> get name
     6 "miaoerduo"
     7 redis> lpush arr 1 2 3 4 5
     8 (integer) 5
     9 redis> get arr
    10 (error) WRONGTYPE Operation against a key holding the wrong kind of value

    注意,lpush是List部分的内容,arr的值是一个List的结构。这里只需要知道key不是string类型的时候,get的时候就会报错。

    2,MGET key [key ...]
    Get the values of all the given keys

    还记得我们之前可以同时设置多个key-value吗?其实我们也可以一次性获取多个key的值。如果key不存在,则对应的地方返回nil。

    那么一次性获取多个值和单独一次一次GET有什么区别呢?

    小喵认为,主要有两点,原子操作和查询效率。

    比如博客上会统计评论数和访问数。如果我们依次读取这两部分的值,那么可能得到的两个值并不是同一时刻的。而如果使用MGET,则得到的一定是同一时刻的。这就是原子操作的威力(注,官方文档只介绍了SET和GET的操作是原子的,并没有说MGET是不是,这里小喵果断的说MGET是原子操作也是不合理的。但考虑到使用MGET的时候,是把查询指令一次性传输到后台来执行,所以应该原子操作。无论如何,在上面的例子中,使用MGET总是比两次GET要合理的)。

    另外,如果数据库的查询,都分为三个过程,传输查询指令,执行指令,输出结果。

    如果分多次GET的话,在传输指令和输出结果的这两个部分就要重复很多次,效率大打折扣。

    反派汪:分多次查询,需要传输的指令数目,也不会比单次多很多,为什么一定会影响效率呢?
    喵太:其实不然,传输数据需要很多的准备工作,不仅仅是数据的具体传输,有时候需要考虑连接的创建和关闭、设置锁等的开销。
    反派汪:说的好像有道理,我再回去研究研究。

    1 redis> set key1 a
    2 OK
    3 redis> set key2 b
    4 OK
    5 redis> mget key1 key2 key3
    6 1) "a"
    7 2) "b"
    8 3) (nil)

    我们只创建了key1和key2,使用MGET获取的时候,由于没有key3,对应的位置返回了nil。

    3,GETRANGE key start end
    Get a substring of the string stored at a key

    该指令只要用于获取字符串的字串,在Redis2.0版本之前,叫做SUBSTR。strat和end是字串的起始和结束的位置,可以用负数来表示距离string尾部的未知的下标。-1是最后一个字符,-2是底数第二个字符。这个表达方式和Python的获取list的子list非常相似。

    需要注意的有两点:

    字串包括了start和end这两个位置的字符。在Python中是不包含end的。

    当给出的start和end超出了string的范围时,指令只会返回在string内的结果。

     1 redis> set str "hello world"
     2 OK
     3 redis> getrange str 6 -1
     4 "world"
     5 redis> getrange str -5 -1
     6 "world"
     7 redis> getrange str 0 4
     8 "hello"
     9 redis> getrange str 0 100
    10 "hello world"

    上述例子是几种情况下的输出。

    4,GETSET key value
    Set the string value of a key and return its old value

    设置key的值,并返回之前的值。如果之前key的值不是string,则会报错。

    这个指令相当于先GET,再SET。

    这个指令可以用来配合INCR指令一起使用支持重置的技术功能(INCR我们后面会讲到)。先设置count为0,每次INCR使得count加1。等到需要获取计数的时候,使用GETSET count 0,就能获取计数的值,并且把计数器重置了。

    1 redis> set str "hello"
    2 OK
    3 redis> getset str "world"
    4 "hello"
    5 redis> get str
    6 "world"

    第一行设置str为"hello"

    第二行获取了str原先的值,并把str设置成"world"。

    第三行GET的时候,就是修改之后的值了。

    三、string的修改操作

    1,STRLEN key
    Get the length of the value stored in a key

    返回key对应的string的长度,如果key对应的不是string,则报错。如果key不存在,则返回0(还是把key对应的看成空字符串)。

    redis> set str "hello world"
    OK
    redis> strlen str
    (integer) 11
    redis> get nokey
    (nil)
    redis> strlen nokey
    (integer) 0

    2,APPEND key value

    Append a value to a key

    如果key已经存在,且值为string,则将value追加到值的后面,如果key不存在,则会创建一个空的字符串的key,然后执行追加操作。

     1 redis> set str "hello"
     2 OK
     3 redis> append str " world"
     4 (integer) 11
     5 redis> get str
     6 "hello world"
     7 redis> del str
     8 (integer) 1
     9 redis> append str "hello"
    10 (integer) 5
    11 redis> get str
    12 "hello"

    这个例子中,我们先向已有值的str中append了一个字符串。然后向不存在的key,也添加了字符串。

    3,INCR key
    Increment the integer value of a key by one

    对存储在key的整数值进行原子的加1操作。

    如果key不存在,则会设置默认值0,再加1。

    如果key存在,但是存储的值不是字符串,或者存储的字符串不能表示整数,则执行该操作时,会报错。

    这个操作仅限于64位的有符号的整型数据。

    比较有意思的是,虽然这个key存储的值是个字符串,但是该操作的效果和对相应的数字进行操作一样。而且,Redis中,在存储这类字符串的时候,底层上其实存储的就是一个整数,因此不存在存储上的浪费。

     1 redis> set count 0
     2 OK
     3 redis> incr count
     4 (integer) 1
     5 redis> incr count
     6 (integer) 2
     7 redis> del count
     8 (integer) 1
     9 redis> get count
    10 (nil)
    11 redis> incr count
    12 (integer) 1
    13 redis> incr count
    14 (integer) 2

    值得注意的是,该操作是原子操作,即使有多个请求传输到Redis,count执行的结果都不会错误,所以我们可以大胆放心的用这个功能实现多线程的计数功能。

    4,DECR key
    Decrement the integer value of a key by one

    对存储在key的整数值进行原子的减1操作。

    注意事项和INCR一样。

    5,INCRBY key increment
    Increment the integer value of a key by the given amount

    对存储在key的整数值进行原子的加操作,加increment。

    如果key不存在,操作之前,key就会被置为0。如果key的value类型错误或者是个不能表示成数字的字符串,就返回错误。这个操作最多支持64位有符号的整型数字。基本上和INCR一样。

    6,DECRBY key decrement
    Decrement the integer value of a key by the given number

    对存储在key的整数值进行原子的减操作,减increment。

    其他和INCR一样。

    7,INCRBYFLOAT key increment
    Increment the float value of a key by the given amount

    对存储造key中的浮点数进行原子的加操作,加increment。

    如果key不存在,操作之前,key就会被置为0。如果key的value类型错误或者是个不能表示成浮点数的字符串,就返回错误。

    我们并没有DECRBYFLOAT这个操作,因此想要实现减操作,只需要把increment设成负的就可以。

     1 redis> set a 1.5
     2 OK
     3 redis> incrbyfloat a 10.1
     4 "11.6"
     5 redis> incrbyfloat a 10.1
     6 "21.7"
     7 redis> incrbyfloat a -10.1
     8 "11.6"
     9 redis> incrbyfloat a -1.5e2
    10 "-138.39999999999999999"

    浮点数可以用一般的小数和科学计数法表示。

    四、二进制操作

    1,SETBIT key offset value
    Sets or clears the bit at offset in the string value stored at key

    设置或者清空key的value(字符串)在offset处的bit值。这里将string看成由bit组成的数组。

    指定位置的bit可以被设置,或者被清空,这个由value(只能是0或者1)来决定。当key不存在的时候,就创建一个新的字符串value。要确保这个字符串足够长到在offset处有bit值。参数offset需要大于等于0,并且小于2^32(限制bitmap大小为512)。当key对应的字符串增大的时候,新增的部分bit值都是设置为0。

    该操作返回value原来的offset位置的bit值。

     1 redis> setbit a 0 1
     2 (integer) 0
     3 redis> setbit a 1 1
     4 (integer) 0
     5 redis> setbit a 2 1
     6 (integer) 0
     7 redis> setbit a 3 1
     8 (integer) 0
     9 redis> get a
    10 "\xf0"

    a最开始不存在,使用setbit操作,将a的前4位都设置成1。最终就得到了\xf0,这是16进制表示的结果,前4位都是1,其他都是0。

    2,GETBIT key offset
    Returns the bit value at offset in the string value stored at key

    获取key对应的string在offset处的bit值。当offset超出了字符串长度的时候,这个字符串就被假定为由0比特填充的连续空间。当key不存在的时候,它就认为是一个空字符串,所以offset总是超出范围,然后value也被认为是由0比特填充的连续空间。

    1 redis> setbit a 7 1
    2 (integer) 0
    3 redis> getbit a 0
    4 (integer) 0
    5 redis> getbit a 7
    6 (integer) 1
    7 redis> getbit a 100
    8 (integer) 0

    3,BITCOUNT key [start end]

     Count set bits in a string

    统计key的string的二进制中1的个数。

    start和end分别表示string的起始和结束位置,含义和GETRANGE中一样。

    1 redis> setbit mykey 0 1
    2 (integer) 0
    3 redis> setbit mykey 10 1
    4 (integer) 0
    5 redis> setbit mykey 5 1
    6 (integer) 0
    7 redis> bitcount mykey
    8 (integer) 3

    4,BITOP operation destkey key [key ...]

    Perform bitwise operations between strings

    对string进行bit级别的操作。具体操作有4种。AND,OR,XOR,NOT。

    用法如下:

    BITOP AND destkey srckey1 srckey2 srckey3 ... srckeyN
    BITOP OR destkey srckey1 srckey2 srckey3 ... srckeyN
    BITOP XOR destkey srckey1 srckey2 srckey3 ... srckeyN
    BITOP NOT destkey srckey

    NOT操作后面只有一个目标key和srckey,是因为NOT操作是一元的。

    对于AND,OR和XOR操作,Redis会将srckey1,srckey2,...,srckeyN这些字符串对位进行相关操作,之后将结果存入destkey中。

    如果srckey的length不相等的话,Redis内部会将短的字符串补齐,并填充上0。

     1 redis> set key1 "\xf0\xf0"
     2 OK
     3 redis> set key2 "\x0f\x0f"
     4 OK
     5 redis> set key3 "\xff\xff"
     6 OK
     7 redis> bitop and destkey key1 key2 key3
     8 (integer) 2
     9 redis> get destkey
    10 "\x00\x00"
    11 redis> bitop or destkey key1 key2 key3
    12 (integer) 2
    13 redis> get destkey
    14 "\xff\xff"
    15 redis> bitop xor destkey key1 key2 key3
    16 (integer) 2
    17 redis> get destkey
    18 "\x00\x00"
    19 redis> bitop not destkey key1
    20 (integer) 2
    21 redis> get destkey
    22 "\x0f\x0f"

    5,BITPOS key bit [start] [end]

    Find first bit set or clear in a string

    返回string的二进制中第一个0或1的位置。

    这里将string看成一个有许多bit组成的数组。其中start和end表示string的一个间隔,如果指定了start和end,则BITPOS只会查询这个区间。注意,start和end表示的字符的位置,不是bit的位置。

    即使指定了start和end,BITPOS这个操作也只会这个目标bit的绝对地址。

    有几点需要注意:

    在没有指定查询区间或只指定start的时候,查询bit位为0的位置时,如果string中没有该位,则会返回string的bit位的总数。比如在\xff\xff\xff直接查询bit为0的位置,Redis默认该字符串后面都是0,因此,返回的结果就是12(下标从0开始数的)。

    如果指定了查询区间,无论查询0或是1,在没查询到的时候只会返回-1。

    在没有指定查询区间时,查询bit位为1的位置时,如果string中没有该位,则会返回-1,表示未查询到。

    redis> set bits "\x00\xff\x00\xff"
    OK
    redis> strlen bits
    (integer) 4
    redis> bitpos bits 0
    (integer) 0
    redis> bitpos bits 1
    (integer) 8
    redis> bitpos bits 0 1 
    (integer) 16
    redis> bitpos bits 0 1 -1
    (integer) 16
    redis> bitpos bits 1 1 -1
    (integer) 8
    redis> bitpos bits 0 3
    (integer) 32
    redis> bitpos bits 0 3 3
    (integer) -1

    看上面的例子,bits的初始设置的二进制表示为:

    00000000 11111111 00000000 11111111

    直接获取0(bitpos bits 0)的位置为0,获取1(bitpos bits 1)的位置为8。

    指定开始位置为1(start = 1)的时候,0第一次出现(bitpos bits 1)的位置为16。

    指定起始位置为3(start = 3)的时候,0第一次出现(bitpos bits 0 3)的位置为32。这是因为Redis在查询字符串的时候查询到了字符串的末尾,之后默认末尾的后面都是0,因此得到了32这的个位置。

    最后,当指定必须在第3个字节(从0开始数)查询0的时候,由于查不到0,因此只返回了-1。

    这篇博客的内容,小喵自己也感觉有点多,每个指令小喵都是参考了官方的文档,然后都自己动手做了实验,再加入了自己的一点理解。可能有些地方,小喵没有理解的很好,这时就需要大家多多指教了。

    转载请注明出处~~喵呜~~

  • 相关阅读:
    Java通过stream将list转换成map
    Java 标记接口
    Java的LinkedList
    Java中的数组
    Java的异常
    初识lambda
    Combining a Collection of Predicates
    重载
    svm资料收集
    向量空间及其他相关数学结构
  • 原文地址:https://www.cnblogs.com/idiotgroup/p/5455282.html
Copyright © 2011-2022 走看看