http://blog.csdn.net/shamohua/article/category/933347
redis:string数据类型与操作
redis的基本数据类型之一:string。
- 类型说明
定义:src/sds.h
- 39 struct sdshdr {
- 40 int len;
- 41 int free;
- 42 char buf[];
- 43 };
sds是Simple Dynamic Strings的缩写,即简单的动态字符串。其中,
len:buf数组的长度。
free:数组中剩余可用字节数,由此可以理解为什么string类型是二进制安全的了,因为它本质上就是个byte数组,当然可以包含任何数据了
buf:个char数组用于存贮实际的字符串内容,其实char和c#中的byte是等价的,都是一个字节。
- 基本操作
- Command Parameters Description
- SET key value Set a key to a string value
- GET key Return the string value of the key
- GETSET key value Set a key to a string returning the old value of the key
- MGET key1 key2 ... keyN Multi-get, return the strings values of the keys
- SETNX key value Set a key to a string value if the key does not exist
- SETEX key time value Set+Expire combo command
- MSET key1 value1 key2 value2 ... keyN valueN Set multiple keys to multiple values in a single atomic operation
- MSETNX key1 value1 key2 value2 ... keyN valueN Set multiple keys to multiple values in a single atomic operation if none of the keys already exist
- INCR key Increment the integer value of key
- INCRBY key integer Increment the integer value of key by integer
- DECR key Decrement the integer value of key
- DECRBY key integer Decrement the integer value of key by integer
- APPEND key value Append the specified string to the string stored at key
- SUBSTR key start end Return a substring of a larger string
详细说明请参考:
中文: http://redis.readthedocs.org/en/2.4/string.html#setbit
英文: http://redis.io/commands#string
下面是利用mrpi-redis-cplusplus-client,对上述基本操作加以验证:
- <pre name="code" class="cpp">#include "redisclient.h"
- #include "tests/functions.h"
- #include <iostream>
- #include <boost/date_time.hpp>
- #define OUT(x) std::cout<<#x<<" = "<<x<<std::endl;
- boost::shared_ptr<redis::client> init_non_cluster_client();
- void test_strings(redis::client & c);
- int main(int argv, char* argc[])
- {
- boost::shared_ptr<redis::client> shared_c;
- shared_c = init_non_cluster_client();
- redis::client& c = *shared_c;
- test_strings(c);
- return 0;
- }
- void test_strings(redis::client & c)
- {
- std::cout<<"test save keys: "<<std::endl;
- std::string key = "key";
- std::string value = "value string for test ";
- std::string response;
- test("set & get");
- {
- c.set(key, value);
- response = c.get(key);
- OUT(response);
- }
- test("append");
- {
- // append the key
- value += "1 ";
- c.append(key, value);
- response = c.get(key);
- OUT(response);
- }
- test("substr");
- {
- // get the substring
- // params : key start index
- // end index: -1 the end
- // 获得区间start,end之间的字串
- string second_half = c.substr(key, value.size(), -1);
- OUT(second_half);
- second_half = c.substr(key, value.size(), 2*value.size());
- OUT(second_half);
- }
- test("exists");
- {
- std::string key2 = "key2";
- std::string value2 = "value2";
- c.set(key2, value2);
- std::cout<<"check the key whether exist."<<std::endl;
- bool bExist = c.exists(key2);
- OUT(bExist);
- OUT( c.exists("key_exist") );
- if(!bExist) {
- c.set("key2", "value2");
- }
- }
- test("keys");
- {
- // 获得关键字以key开头的集合
- redis::client::string_vector keys;
- OUT(c.keys("key*", keys));
- }
- // infos
- redis::server_info info;
- // std::cout<<"the server info:"<<std::endl;
- // c.info(info);
- test("getset");
- {
- // get the key value, and set the new value
- response = c.getset(key, "key_value_new");
- OUT(response);
- response = c.get(key);
- OUT(response);
- }
- test("mset & mget");
- {
- redis::client::string_vector vkeys;
- redis::client::string_vector vvalues;
- vkeys.push_back("k1");
- vkeys.push_back("k2");
- vvalues.push_back("v1");
- vvalues.push_back("v2");
- c.mset(vkeys, vvalues);
- redis::client::string_vector vvls;
- c.mget(vkeys, vvls);
- OUT(vvls.size());
- }
- test("setnx");
- {
- OUT(c.setnx(key, value));
- OUT(c.setnx("key_setnx", "value_setnx"));
- }
- test("setex");
- {
- // 将值value关联到key,并将key的生存时间设为seconds(以秒为单位)
- int sec = 5;
- c.setex("key_setex", "value_setex", sec);
- OUT(c.get("key_setex"));
- sleep(2*sec);
- OUT(c.get("key_setex"));
- }
- test("incr & inrcby");
- {
- // inrc,key对应的value。步长为1的自加运算
- c.set("key_age", "20");
- OUT(c.get("key_age"));
- int step = 5;
- OUT(c.incr("key_age"));
- OUT(c.get("key_age"));
- // inrc,key对应的value。步长为step的自加运算
- OUT(c.incrby("key_age", step));
- OUT(c.get("key_age"));
- }
- test("derc & dercby");
- {
- int step = 3;
- OUT(c.get("key_age"));
- OUT(c.decr("key_age"));
- OUT(c.get("key_age"));
- OUT(c.decrby("key_age", step));
- OUT(c.get("key_age"));
- }
- }</pre><br>
- <br>
- <pre></pre>
- <br>
- <br>
- <p></p>
Redis hash是一个string类型的field和value的映射表.一个key可对应多个field,一个field对应一个value。将一个对象存储为hash类型,较于每个字段都存储成string类型更能节省内存。新建一个hash对象时开始是用zipmap(又称为small hash)来存储的。这个zipmap其实并不是hash table,但是zipmap相比正常的hash实现可以节省不少hash本身需要的一些元数据存储开销。尽管zipmap的添加,删除,查找都是O(n),但是由于一般对象的field数量都不太多。所以使用zipmap也是很快的,也就是说添加删除平均还是O(1)。如果field或者value的大小超出一定限制后,Redis会在内部自动将zipmap替换成正常的hash实现.。这个限制在redis.conf中配置如下:
- 421 # Hashes are encoded in a special way (much more memory efficient) when they
- 422 # have at max a given numer of elements, and the biggest element does not
- 423 # exceed a given threshold. You can configure this limits with the following
- 424 # configuration directives.
- 425 hash-max-zipmap-entries 512
- 426 hash-max-zipmap-value 64
- 操作
1. hset
-
HSET key field value
将哈希表 key中的域 field的值设为 value。如果 key不存在,一个新的哈希表被创建并进行hset操作。如果域 field已经存在于哈希表中,旧值将被覆盖。
2. hget
HGET key field
返回哈希表key中指定的field的值。
3. hsetnx
-
HSETNX key field value
将哈希表 key中的域 field的值设置为 value,当且仅当域 field不存在。若域 field已经存在,该操作无效。如果 key不存在,一个新哈希表被创建并执行hsetnx命令。
4. hmset
-
HMSET key field value [field value ...]
同时将多个field - value(域-值)对设置到哈希表key中。此命令会覆盖哈希表中已存在的域。如果key不存在,一个空哈希表被创建并执行hmset操作。
5. hmget
HMGET key field [field ...]
返回哈希表key中,一个或多个给定域的值。如果给定的域不存在于哈希表,那么返回一个nil值。因为不存在的key被当作一个空哈希表来处理,所以对一个不存在的key进行hmget操作将返回一个只带有nil值的表。
6. hgetall
-
HGETALL key
返回哈希表
key中,所有的域和值。在返回值里,紧跟每个域名(field name)之后是域的值(value),所以返回值的长度是哈希表大小的两倍。
7. hdel
-
HDEL key field [field ...]
删除哈希表
key中的一个或多个指定域,不存在的域将被忽略。
8. hlen
HLEN key
返回哈希表key对应的field的数量。
9. hexists
-
HEXISTS key field
查看哈希表
key中,给定域
field是否存在。
10. hkeys
HKEYS key
获得哈希表中key对应的所有field。
11. hvals
HVALS key
获得哈希表中key对应的所有values。
12. hincrby
为哈希表key中的域field的值加上增量increment。增量也可以为负数,相当于对给定域进行减法操作。如果key不存在,一个新的哈希表被创建并执行hincrby命令。如果域field不存在,那么在执行命令前,域的值被初始化为0。对一个储存字符串值的域field执行hincrby命令将造成一个错误。本操作的值限制在64位(bit)有符号数字表示之内。
更多详细信息请参照:http://redis.readthedocs.org/en/2.4/hash.html
下面是利用redis c++客户端编写的测试程序:
- #include "redisclient.h"
- #include "tests/functions.h"
- #include <iostream>
- #include <boost/date_time.hpp>
- #define OUT(x) std::cout<<#x<<" = "<<x<<std::endl;
- boost::shared_ptr<redis::client> init_non_cluster_client();
- void test_hash(redis::client & c);
- int main(int argv, char* argc[])
- {
- boost::shared_ptr<redis::client> shared_c;
- shared_c = init_non_cluster_client();
- redis::client& c = *shared_c;
- test_hash(c);
- return 0;
- }
- void test_hash(redis::client & c)
- {
- test("test hash type");
- test("hset & hget & hsetnx");
- {
- // hset: key, field, value
- OUT(c.hset("favorites", "taobao", "www.taobao.com"));
- OUT(c.hset("favorites", "taobao", "www.taobao.com#"));
- OUT(c.hget("favorites", "taobao"));
- OUT(c.hsetnx("favorites", "taobao", "www.taobao.com"));
- OUT(c.hget("favorites", "taobao"));
- }
- test("hmset & hmget & hgetall & hdel & hexists");
- {
- redis::client::string_vector fields, values, getvalues;
- fields.push_back("tmall");
- fields.push_back("alibaba");
- values.push_back("www.tmall.com");
- values.push_back("www.1688.com");
- c.hmset("favorites", fields, values);
- // 类型错误,报错
- c.set("string_key", "string_value");
- //c.hmset("string_key", fields, values);
- fields.push_back("etao");
- c.hmget("favorites", fields, getvalues);
- OUT(getvalues.size());
- for(int i=0; i<getvalues.size(); ++i) {
- OUT(getvalues[i]);
- }
- redis::client::string_pair_vector pairs;
- c.hgetall("favorites", pairs);
- OUT(pairs.size());
- for(int i=0; i<pairs.size(); ++i) {
- OUT(pairs[i].first);
- OUT(pairs[i].second);
- }
- OUT(c.hset("favorites", "etao", "www.etao.com"));
- OUT(c.hlen("favorites"));
- pairs.clear();
- c.hdel("favorites", "etao");
- c.hdel("favorites", "koubei");
- c.hgetall("favorites", pairs);
- OUT(pairs.size());
- for(int i=0; i<pairs.size(); ++i) {
- OUT(pairs[i].first);
- OUT(pairs[i].second);
- }
- OUT(c.hexists("favorites", "taobao"));
- OUT(c.hexists("favorites", "koubei"));
- }
- test("hincrby & hkeys & hvals");
- {
- OUT(c.hset("lists", "age", "20"));
- OUT(c.hincrby("lists", "age", 5));
- redis::client::string_vector keys;
- c.hkeys("lists", keys);
- for(size_t i=0; i<keys.size(); ++i) {
- OUT(keys[i]);
- }
- redis::client::string_vector vals;
- c.hvals("lists", vals);
- for(size_t i=0; i<keys.size(); ++i) {
- OUT(vals[i]);
- }
- }
- }
redis数据类型之list:redis list数据类型是一个双向循环链表。redis.conf中的相关配置如下:
- 428 # Similarly to hashes, small lists are also encoded in a special way in order
- 429 # to save a lot of space. The special representation is only used when
- 430 # you are under the following limits:
- 431 list-max-ziplist-entries 512
- 432 list-max-ziplist-value 64
对于list的操作详见: http://redis.readthedocs.org/en/2.4/list.html
redis c++接口的调用:
- #include "redisclient.h"
- #include "tests/functions.h"
- #include <iostream>
- #include <boost/date_time.hpp>
- #define OUT(x) std::cout<<#x<<" = "<<x<<std::endl;
- boost::shared_ptr<redis::client> init_non_cluster_client();
- void test_list(redis::client & c);
- int main(int argv, char* argc[])
- {
- boost::shared_ptr<redis::client> shared_c;
- shared_c = init_non_cluster_client();
- redis::client& c = *shared_c;
- test_list(c);
- return 0;
- }
- void test_list(redis::client & c)
- {
- test("test redis list type.");
- while(c.llen("list")>0) {
- OUT(c.lpop("list"));
- }
- test("lpush & rpush & lpop & rpop & blpop & brpop");
- {
- OUT( c.lpush("list", "lpush1") );
- OUT( c.lpush("list", "lpush2") );
- OUT( c.rpush("list", "rpush1") );
- OUT( c.rpush("list", "rpush2") );
- OUT( c.llen("list") );
- redis::client::string_vector out;
- OUT( c.lrange("list", 0, 10, out) );
- for(size_t i=0; i<out.size(); ++ i) {
- OUT(out[i]);
- }
- OUT( c.lpop("list") );
- OUT( c.rpop("list") );
- OUT( c.llen("list") );
- OUT( c.lpush("list", "lpush2") );
- OUT( c.rpush("list", "rpush2") );
- OUT( c.llen("list") );
- OUT( c.blpop("list1", 1) );
- OUT( c.blpop("list", 1) );
- OUT( c.brpop("list1", 1) );
- OUT( c.brpop("list", 1) );
- OUT( c.llen("list") );
- }
- test("llen & lrange & ltrim");
- {
- OUT( c.llen("list") );
- redis::client::string_vector out;
- OUT( c.lrange("list", 0, 10, out) );
- for(size_t i=0; i<out.size(); ++ i) {
- OUT(out[i]);
- }
- c.ltrim("list", 0, 2);
- OUT( c.lrange("list", 0, 10, out) );
- for(size_t i=0; i<out.size(); ++ i) {
- OUT(out[i]);
- }
- OUT( c.llen("list") );
- }
- test("lrem");
- {
- OUT( c.llen("list") );
- OUT( c.lrem("list", 1, "lpush1") );
- OUT( c.lrem("list", -1, "lpush2") );
- OUT( c.llen("list") );
- }
- test("lset & lindex");
- {
- OUT( c.lindex("list", 0) );
- c.lset("list", 0, "set new value of index 0");
- OUT( c.lindex("list", 0) );
- }
- }
redis:set数据类型与操作
set是集合。我们可以向一个集合中“插入”,“删除”元素,也可以计算两个集合的“交集”,“并集”,及“作差”。如:
假设有集合A,B。
其中,A={1,2,3,4,5},B={4,5,6,7,8}。
那么,
交集:inter(A,B)= {4,5}
并集:union(A, B)= {1,2,3,4,5,6,7,8}
差集:diff(A,B)= {1,2,3},即属于集合A,但不属于集合B的元素
set的是通过hash table实现的,所以添加、删除和查找的复杂度都是O(1)。hash table会随着添加或者删除自动的调整大小。需要注意的是调整hash table大小时候需要同步(获取写锁)会阻塞其他读写操作,可能不久后就会改用跳表(skip list)来实现,跳表已经在sorted set中使用了。(引自:http://hb.qq.com/a/20111001/000022.htm)
关于set数据类型的操作详见:http://redis.readthedocs.org/en/2.4/set.html
下面结合redis c++接口,了解一下这些基本操作。
- #include "redisclient.h"
- #include "tests/functions.h"
- #include <iostream>
- #include <boost/date_time.hpp>
- #define OUT(x) std::cout<<#x<<" = "<<x<<std::endl;
- boost::shared_ptr<redis::client> init_non_cluster_client();
- void output_set(redis::client::string_set & sset);
- void test_set(redis::client & c);
- int main(int argv, char* argc[])
- {
- boost::shared_ptr<redis::client> shared_c;
- shared_c = init_non_cluster_client();
- redis::client& c = *shared_c;
- test_set(c);
- return 0;
- }
- void test_set(redis::client & c)
- {
- test("test redis set type.");
- test("sadd");
- {
- std::vector<std::string> vals;
- for(size_t i=0; i<10; ++i) {
- char ch = (char)('A'+i);
- std::string tmp ="";
- tmp += ch;
- vals.push_back(tmp);
- }
- std::vector<std::string>::iterator it;
- c.sadd("set_a", vals.begin(), vals.end());
- }
- test("smembers");
- {
- redis::client::string_set out;
- OUT(c.smembers("set_a", out));
- output_set( out );
- }
- test("srem");
- {
- c.srem("set_a", "A");
- redis::client::string_set out;
- OUT(c.smembers("set_a", out));
- output_set( out );
- }
- test("sismember");
- {
- OUT( c.sismember("set_a", "A") );
- OUT( c.sismember("set_a", "B") );
- }
- test("scard");
- {
- OUT( c.scard("set_a") );
- }
- test("smove");
- {
- c.smove("set_a", "set_b", "B");
- redis::client::string_set out;
- OUT(c.smembers("set_a", out));
- output_set(out);
- out.clear();
- OUT(c.smembers("set_b", out));
- output_set( out );
- }
- test("spop");
- {
- OUT( c.scard("set_a") );
- OUT( c.spop("set_a") );
- OUT( c.scard("set_a") );
- }
- test("srandmember");
- {
- OUT( c.srandmember("set_a") );
- }
- test("sinter");
- {
- c.sadd("set_b", "C");
- c.sadd("set_b", "F");
- redis::client::string_vector keys;
- keys.push_back("set_a");
- keys.push_back("set_b");
- redis::client::string_set out;
- c.smembers("set_a", out);
- std::cout<<"set a: *******************"<<std::endl;
- output_set(out);
- out.clear();
- c.smembers("set_b", out);
- std::cout<<"set b: *******************"<<std::endl;
- output_set(out);
- c.sinter(keys, out);
- output_set(out);
- }
- test("sinterstore");
- {
- redis::client::string_vector keys;
- redis::client::string_set out;
- keys.push_back("set_a");
- keys.push_back("set_b");
- c.sinterstore("set_c", keys);
- c.smembers("set_c", out);
- output_set(out);
- }
- test("sunion & sunionstore");
- {
- redis::client::string_vector keys;
- redis::client::string_set out;
- keys.push_back("set_a");
- keys.push_back("set_b");
- c.sunion(keys, out);
- output_set(out);
- out.clear();
- c.sunionstore("set_d", keys);
- c.smembers("set_d", out);
- std::cout<<"set d: ****************"<<std::endl;
- output_set(out);
- }
- test("sdiff * sdiffstore");
- {
- redis::client::string_vector keys;
- redis::client::string_set out;
- keys.push_back("set_a");
- keys.push_back("set_b");
- c.sdiff(keys, out);
- output_set(out);
- out.clear();
- c.sdiffstore("set_e", keys);
- c.smembers("set_e", out);
- std::cout<<"set e: ****************"<<std::endl;
- output_set(out);
- }
- }
- void output_set(redis::client::string_set & sset)
- {
- size_t size = sset.size();
- OUT(size);
- redis::client::string_set::iterator it;
- for(it=sset.begin(); it!=sset.end(); ++it) {
- OUT(*it);
- }
- }
redis: sorted set数据类型与操作
redis支持有序集合,即sorted sets数据类型。基本格式为:
key --> member -- score
| --> member -- score
sorted set类型的实现采用了两个数据结构:hash table 和 skip list(跳跃表),其中hash table是具体使用redis中的dict来实现的,主要是为了保证查询效率为O(1) ,而skip list(跳跃表)主要是保证元素有序并能够保证INSERT和REMOVE操作是O(logn)的复杂度。
sorted set的基本操作详见:http://redis.readthedocs.org/en/2.4/sorted_set.html
利用redis c++接口,验证zset的基本操作:
- #include "redisclient.h"
- #include "tests/functions.h"
- #include <iostream>
- #include <boost/date_time.hpp>
- #define OUT(x) std::cout<<#x<<" = "<<x<<std::endl;
- boost::shared_ptr<redis::client> init_non_cluster_client();
- void output_set(redis::client::string_set & sset);
- void output_sort_set(redis::client & c, std::string & key);
- void test_sorted_set(redis::client & c);
- int main(int argv, char* argc[])
- {
- boost::shared_ptr<redis::client> shared_c;
- shared_c = init_non_cluster_client();
- redis::client& c = *shared_c;
- test_sorted_set(c);
- return 0;
- }
- void test_sorted_set(redis::client & c)
- {
- test("test sorted set:");
- std::string key = "page_rank";
- test("zadd");
- {
- c.zadd(key, 10, "taobao.com");
- c.zadd(key, 9, "tmall.com");
- c.zadd(key, make_pair("tmall.com", 11.1) );
- output_sort_set(c, key);
- }
- test("zrem");
- {
- c.zrem(key, "taobao.com");
- output_sort_set(c, key);
- }
- test("zcount");
- {
- OUT(c.zcount(key, 0, 5));
- c.zadd(key, 3.6, "etao.com");
- OUT(c.zcount(key, 0, 5));
- }
- test("zincrby");
- {
- c.zincrby(key,"etao.com", 3);
- c.zincrby(key,"koubei.com", 5);
- output_sort_set(c, key);
- }
- test("zrevrange");
- {
- redis::client::string_vector out;
- c.zrevrange(key, 0, -1, out);
- for(size_t i=0; i<out.size(); ++i) {
- OUT(out[i]);
- }
- }
- test("zrangebyscore");
- {
- redis::client::string_score_vector out;
- c.zrangebyscore(key, 0, 20, out, 0, 2);
- for(size_t i=0; i<out.size(); i++) {
- OUT(out[i].first);
- OUT(out[i].second);
- }
- }
- test("zrank & zrevrank");
- {
- OUT(c.zrank(key, "etao.com"));
- OUT(c.zrevrank(key, "etao.com"));
- }
- test("zremrangebyrank & zremrangebyscore");
- {
- c.zremrangebyrank(key, 0, 0);
- c.zremrangebyscore(key, 0, 10);
- output_sort_set(c, key);
- }
- test("zinterstore & zunionstore");
- {
- c.zadd("website", 10, "taobao.com");
- c.zadd("website", 8, "google.com");
- c.zadd("website", 0, "baidu.com");
- c.zadd("website", 7, "tmall.com");
- redis::client::string_vector keys;
- keys.push_back(key);
- keys.push_back("website");
- std::string sinter("inter");
- std::string sunion("union");
- c.zinterstore(sinter, keys);
- c.zunionstore(sunion, keys);
- std::cout<<"inter :"<<std::endl;
- output_sort_set(c, sinter);
- std::cout<<"union :"<<std::endl;
- output_sort_set(c, sunion);
- }
- }
- void output_sort_set(redis::client & c, std::string & key)
- {
- OUT( c.zcard(key) );
- redis::client::string_vector out;
- c.zrange(key, 0, -1, out);
- for(size_t i=0; i<out.size(); ++i) {
- OUT(out[i]);
- OUT( c.zscore(key, out[i]) );
- }
- }
PS:
redis c++接口在实现zset操作时,有两个小bug:
1. void recv_int_ok_reply_(int socket)
原来实现,当recv_int_reply_(socket) != 1时,就认为有错误,从而抛出异常:throw protocol_error("expecting int reply of 1");
但实际上,如zadd操作:
如果某个member已经是有序集的成员,那么更新这个member的score值,并通过重新插入这个member元素,来保证该member在正确的位置上,并返回0; add成功返回1;如果key不存在,则创建一个空的有序集并执行zadd操作;当key存在但不是有序集类型时,返回一个错误。
修改:
- void recv_int_ok_reply_(int socket)
- {
- if (recv_int_reply_(socket) < 0)
- throw protocol_error("expecting int reply of <0");
- }
2. void zrangebyscore(const string_type & key, double min, double max, string_score_vector & out, int_type offset = 0, int_type max_count = -1, int range_modification = 0)
调用,zrangebyscore_base(true, key, min, max, res, offset, max_count, range_modification);
第一个参数值等于true表明,需要返回score值。但是,zrangebyscore_base在实现时并未考虑这一参数。
修改:在zrangebyscore_base函数实现时,加上
- if(withscores) {
- m << "WITHSCORES";
- }