目錄
前言:
string的API使用
set get:
expire:
NX XX:
mset,mget:
getrange setrange:
incr decr
前言:
在前文,我們已經學習了Redis的定制化客戶端怎么來的,以及如何配置好Redis定制化客戶端,并且簡單學習了一下RESP,其實也就是了解了一下為什么Redis可以定制化客戶端而已,那么,我們現在有了Redis定制化客戶端的條件,我們自然就可以使用大佬們封裝好的Redis的API了。
那么在本文呢,我們涉及到的是string,list和hash的多組API,因為我們是在命令行敲過的,所以學習起來也是非常快的了。
廢話不多說,進入主題吧!
string的API使用
對于string的API使用,我們大致分為了 set get expire mset mget getrange setrange incr decr,大致就這么多命令,因為其他命令其實也大差不差,我們就使用過這些也都清楚了。
set get:
我們創建好了redis對象之后,啥也不管,直接set,我們可以看到它的參數還是挺多的,但是當我們一看,第一個參數是key,第二個參數是val,第三個參數是chrno的時間,這不就是存活時間嗎,第四個參數是一些設置,比如NX,XX等。
這樣一看是非常清楚的了。
但是我們發現set的參數是Stringview類型的,這個類型和string類型來說最大的區別就是這個類型是只讀的,我們可以通過閱讀源碼簡單了解:
begin() const noexcept{ return this->_M_str; }constexpr const_iteratorend() const noexcept{ return this->_M_str + this->_M_len; }constexpr const_iteratorcbegin() const noexcept{ return this->_M_str; }constexpr const_iteratorcend() const noexcept{ return this->_M_str + this->_M_len; }
這里是它經過重重封裝后的源碼,我們可以發現它的迭代器都是const類型,也就是不允許我們修改*this了,它即只讀的。
因為是只讀的,所以針對只讀的這個操作做了很多優化,效率就比普通的std::string快了。當然了,在C++17中,標準庫也提供了std::string_view,操作和string類似,只是包括了一些只讀的方法。
那么我們既然set了,我們肯定要get,我們先看看get的返回值:
通過返回值和備注,我們看到了它的返回值是OptionalString,如果key是不存在的,那么會返回的就是通過nullop構造的OptionalString對象。那么OptionalString是什么呢~
實際上,它的出現是因為為了更好的處理key不存在的這個情況,在我們學習命令行的時候,我們如果使用get查看一個不存在的key,返回值是nil,也就是空的意思,那么如果它的返回值是string,我們如何表達nil?
如果返回空串,我們并不排除key對應的value就是空串,如果我們再復雜一點,返回一個string*,指向的是空就代表nil,可是這樣又會引發出內存安全的問題。
所以,Redis官方給出的操作是給了一個類型:OptionalString,主要是為了處理nil的情況。
但是作為C++學習者我們不能不知道boost在C++14的時候就已經引入了optional<std::string>的概念了,后來在C++17的時候我們直接就包含optional的頭文件就行了。
我們先來看一段正確的使用:
void test_1(Redis& redis)
{redis.flushall();redis.set("key","111");auto value = redis.get("key");if(value)std::cout << value.value() << std::endl;else std::cout << "值不存在" << std::endl;}
我們一共發現了三處左右較為陌生的點,一個是我們使用auto來接收,一個是我們用if來判斷value,一個是我們使用的value.value()打印。
首先,我們最能夠理解的點是,<<運算符并不支持OptionalString的打印,所以我們需要通過value()方法來返回string類型,其實我們也能通過*value來解決,它們的返回值都是string,*是重載了解引用操作符,.value()就是顯示的調用了對應的成員函數。
if(value)std::cout << *value << std::endl;else std::cout << "值不存在" << std::endl;
其次,我們還能理解的是,如果我們get的是一個不存在的鍵值,那么OptionalString返回值就是類似于nil的結果,所以我們最好是做一個類型檢查。
最后,我們使用auto來接收,其實我們使用optional<string>接收也不是不行,但是為了方便嘛,我們可以直接使用auto接收。不過就需要你包含對應的頭文件了。
對應value()方法有一個特點就是如果key不存在,就會拋異常,像這樣
從上面這個代碼的例子,我們認識了OptionalString,stringView。
那么為了測試方便,我們最好在對Redis進行操作的時候flushall一下,以下是第一個測試的代碼:
void test_1(Redis &redis)
{redis.flushall();redis.set("key", "111");auto value = redis.get("key");// std::optional<std::string> value = redis.get("key");// if(value)// std::cout << *value << std::endl;// else// std::cout << "值不存在" << std::endl;std::cout << value.value() << std::endl;
}
int main()
{Redis redis("tcp://127.0.0.1:6379");test_1(redis);return 0;
}
expire:
然后我們來測試以下超時時間,在set的構造函數中,我們直接加就可以了:
void test_2(Redis &redis)
{redis.flushall();redis.set("key","111",std::chrono::seconds(10));using namespace std::chrono_literals;redis.set("key1","222",10s);std::this_thread::sleep_for(3s);long long time = redis.ttl("key");std::cout << "剩余時間為:" << time << std::endl;}
這里的代碼就比較簡單了,第一個點是設置過期時間有兩種方式,一種是使用命令空間chrono的seconds函數或者millonseconds函數,第二種方式是我們引入命名空間chrono_literals,這樣我們就可以直接使用字面值常量了。
然后我們就正常根據ttl的返回值,來接收以下time就可以了:
至于為什么使用chrono等,因為C++不像py可以直接傳10表示時間,C++沒有內置時間單位,所以需要一個庫專門用來表示時間的,就像上面的兩個。
NX XX:
接著我們來測試NX和XX:
一般默認的是ALWAYS,XX對應的是EXIST,NX對應的是NOT_EXIST:
void test_3(Redis &redis)
{redis.flushall();redis.set("key", "111", 0s,sw::redis::UpdateType::NOT_EXIST);auto value = redis.get("key");if(value)std::cout << value.value() << std::endl;else std::cout << "not exist"<< std::endl;
}
mset,mget:
對于mset和mget來說就是今天的難點了算是,當然僅僅相對而言,它不過是需要我們引入迭代器的概念而已。
由于傳鍵值對的時候,get和mget的鍵值對都是pair類型的,所以我們使用mget的時候,官方給我們提供了兩個角度,一個是使用initailizer_list,一個是使用容器的迭代器。
void test_4(Redis &redis)
{redis.flushall();// redis.mset({"key1","111","key2","222"}); // errorredis.mset({std::make_pair("key","111"),std::make_pair("key1","222")});std::vector<std::pair<std::string, std::string>> vec{std::make_pair("key2","222"),std::make_pair("key3","333")};std::vector<std::pair<std::string, std::string>> vec2{{"key4","444"},{"key5","555"}};redis.mset(vec.begin(), vec.end());redis.mset(vec2.begin(), vec2.end());std::vector<std::string> result;auto iter = std::back_inserter(result); redis.mget({"key","key2","key4"},iter);for(auto ele:result){std::cout << ele << ' ';}std::cout << std::endl;
}
對于mset和mget來說,較為特殊,使用mset的時候,我們有兩種方式,一種,我們可以直接使用initailizer_list初始化,就像構建key 和 key1的時候,我們也可以通過重載迭代器的方式,即我們先定義一個容器,可以是list可以是set可以是Vector,構造好了之后,mset直接傳入兩個的迭代器的就可以了。
然后是mget,使用mget的時候,我們可以看看參數:
它的第二個參數是back_insert_iterator,其實就是尾插迭代器,在STL中,迭代器分為了五種類型,分別是輸入迭代器,輸出迭代器,前向迭代器,雙向迭代器和隨機訪問迭代器,其中對于back_inserter來說,它就是輸出迭代器的一種,mget獲取到value之后,就把輸出給到這個迭代器對應的容器里面。
那么給的方式,就是看位置了,比如back_inserter對應的位置是尾,那么就相當于給這個容器一直尾插。
它們對應了不同的迭代器,比如back_insert_iterator,insert_iterator,但是我們一般不會直接構造出這幾個對象,我們一般會走一些輔助的函數來構造,比如back_inserter。
getrange setrange:
這兩個我們如果有了C++string函數的理解,那就非常簡單了,無非是我們要確定位置,給個偏移量,然后給上對應的字符串就可以了。
void test_5(Redis &redis)
{redis.flushall();redis.set("key","abcdefghijk");redis.setrange("key",2,"11111111");std::string result = redis.getrange("key",2,-1);std::cout << result << std::endl;
}
incr decr
同理,非常簡單,我們直接給代碼:
void test_6(Redis &redis)
{redis.flushall();redis.set("key", "20");redis.incr("key");auto value = redis.get("key");if (value)std::cout << value.value() << std::endl;redis.decr("key");value = redis.get("key");if (value)std::cout << value.value() << std::endl;
}
也是非常簡單了。
其實文本篇幅較多僅是因為第一次使用Redis對應的API,那么在之后介紹list和hash的時候節奏就會快了。
本文的難點是在mset和mget的時候,如何正確使用迭代器的問題,其他難度相對來說是比較低的。
感謝閱讀!