一. Set类型基础

1. 类型说明

  1个key→多个value,value的值不重复!

  Set一种无序且元素内容不重复的集合,不用做重复性判断了,和我们数学中的集合概念相同,可以对多个集合求交集、并集、差集,key可以理解成集合的名字。

注:set 用哈希表来保持字符串的唯一性,没有先后顺序,是按照自己的一个存储方式来遍历,因为没有保存插入顺序。

2. 常用指令Api说明

3. 常用Api说明

(1).SetAdd:添加数据,可以单独1个key-1个value,也可以1个key-多个value添加

(2).SetLength:求key集合的数量

(3).SetContains:判断key集合中是否包含指定值

(4).SetRandomMember:随机获取指定key集合中的一个值或n个值

(5).SetMembers:获取key中的所有值,数据类型要一致,便于存储

(6).SetRemove:删除key集合中的指定value(1个或多个)

(7).SetPop:随机删除指定key集合中的一个值或n个值,并返回这些值。

(8).SetCombine:求多个元素的交并差

  a.SetOperation.Intersect:交集

  b.SetOperation.Union:并集

  c.SetOperation.Difference:差集

(9).SetCombineAndStore:把多个元素的交并差,放到一个新的元素集合中

代码分享:

             //1.添加,value不重复,添加多个Marren1也只有一个
//单个
db.SetAdd("Keen", "Marren1");
db.SetAdd("Keen", "Marren2");
db.SetAdd("Keen", "Marren3");
db.SetAdd("Keen", "Marren2");
db.SetAdd("Keen", "Marren1");
//多个
string[] arryList = { "Marren4", "Marren5", "Marren6", "Marren3" };
RedisValue[] valueList = arryList.Select(u => (RedisValue)u).ToArray();
db.SetAdd("Keen", valueList); //2. 获取key集合值的数量
long d1 = db.SetLength("Keen"); //3. 判断key集合中是否包含指定值
bool d2 = db.SetContains("Keen", "Marren2"); //4. 随机获取key集合中的一个值
var d3 = db.SetRandomMember("Keen");
//随机获取key集合中的3个值
var d33 = db.SetRandomMembers("Keen", ).Select(u => (string)u).ToList(); //5. 获取key中的所有值,数据类型要一致
var rValue = db.SetMembers("Keen");
List<string> d4 = new List<string>();
foreach (var item in rValue)
{
d4.Add(item);
} //6. 删除key集合中的指定value
//单个value
bool d5 = db.SetRemove("Keen", "Marren1");
//多个value
string[] dDelList = { "Marren2", "Marren3", };
RedisValue[] DelList = dDelList.Select(u => (RedisValue)u).ToArray();
long d6 = db.SetRemove("Keen", DelList); //7. 随机删除key集合中的一个值,并返回该值
var d7 = db.SetPop("Keen");
//随机删除key集合中的2个值,并返回这2个值
var d77 = db.SetPop("Keen", ).Select(u => (string)u).ToList(); //8. 获取几个集合的交集、并集、差集(重点)
//准备数据
db.SetAdd("ypf", "h1");
db.SetAdd("ypf", "h2");
db.SetAdd("ypf", "h3");
db.SetAdd("ypf", "h4");
db.SetAdd("maru", "h4");
db.SetAdd("maru", "h5");
db.SetAdd("maru", "h6");
//下面求两个元素的交并差,也可以求多个
string[] arry = { "maru", "ypf" };
RedisKey[] keyList = arry.Select(u => (RedisKey)u).ToArray();
//交集(共同的部分 h4)
var d8 = db.SetCombine(SetOperation.Intersect, keyList).Select(u => (string)u).ToList();
//并集(加到一起,去重, h1-h6)
var d9 = db.SetCombine(SetOperation.Union, keyList).Select(u => (string)u).ToList();
//差集(差集有两个,上面的是h5 h6, 如果颠倒maru和ypf顺序,差集是h1 h2 h3)
var d10 = db.SetCombine(SetOperation.Difference, keyList).Select(u => (string)u).ToList(); //获取交集并存到key=ypf1,返回集合元素的个数
long d11 = db.SetCombineAndStore(SetOperation.Intersect, "ypf1", keyList);
//获取并集并存到key=ypf2 ,返回集合元素的个数
long d12 = db.SetCombineAndStore(SetOperation.Union, "ypf2", keyList);
//获取差集并存到key=ypf3 ,返回集合元素的个数
long d13 = db.SetCombineAndStore(SetOperation.Union, "ypf3", keyList);

二. Set类型案例

1.抽奖

(1) 背景:用户参与抽奖,抽奖大致分两类:

 A:只抽1次,1次抽n个人。

 B:抽多次,比如三等奖抽3名,二等奖抽2名,一等奖抽1名。

(2) 技术分析:

主要利用Set结构元素的不重复性和获取随机数的方法来实现,以“specialPrize”为key,参与用户的id当做value

A:用户点击参与抽奖则执行SetAdd方法。

B:可以获取所有参与用户SetMembers 和 判断某个用户是否参与抽奖了SetContains

C:随机抽一次奖用SetRandomMember或SetRandomMembers,因为不需要删除

多次抽奖用SetPop,因为抽完要删掉。

(3) 代码分析

           //1.模拟用户参与抽奖
for (int i = ; i <= ; i++)
{
db.SetAdd("specialPrize", i.ToString());
}
//2. 获取所有的参与抽奖的用户
var data = db.SetMembers("specialPrize").Select(u => (string)u).ToList();
string idList = "";
foreach (var item in data)
{
idList = idList + "," + item;
}
Console.WriteLine($"参与抽奖的用户有:{idList}");
//3. 判断用户103是否参与抽奖了
var data2 = db.SetContains("specialPrize", "");
if (data2 == true)
{
Console.WriteLine($"用户103参与了抽奖");
}
else
{
Console.WriteLine($"用户103没有参与抽奖");
}
//4. 抽奖
//4.1 只抽一次奖,抽奖人数为两名
{
var d1 = db.SetRandomMembers("specialPrize", ).Select(u => (string)u).ToList();
foreach (var item in d1)
{
Console.WriteLine($"获奖用户为:{item}");
}
}
//4.2 抽三次奖
{
var d1 = db.SetPop("specialPrize", ).Select(u => (string)u).ToList();
foreach (var item in d1)
{
Console.WriteLine($"三等奖用户为:{item}");
}
var d2 = db.SetPop("specialPrize", ).Select(u => (string)u).ToList();
foreach (var item in d2)
{
Console.WriteLine($"二等奖用户为:{item}");
}
var d3 = db.SetPop("specialPrize", ).Select(u => (string)u).ToList();
foreach (var item in d3)
{
Console.WriteLine($"一等奖用户为:{item}");
}
}

2. 微信或微博中消息的点赞(或者某篇文章的收藏)

(1). 背景

  微信朋友圈用户A的某条消息的点赞功能,要实现点赞、取消点赞、获取点赞列表、获取点赞用户数量、判断某用户是否点赞过。

(2). 技术分析

  利用Set结构, 以用户Id-消息id作为key,点赞过该消息的用户id作为value。

  A:点赞 SetAdd方法

  B:取消点赞 SetRemove方法

  C:获取点赞列表 SetMembers方法

  D:获取点赞用户数量 SetLength方法

  E:判断某用户是否点赞过 SetContains方法

  该案例容易理解,此处不写代码了。

3.关注模型

(1).背景

  比如微博关注或者共同好友的问题,以微博关注为例,要实现:同时关注、关注的和、关注A的用户中也关注B的、当A进入B页面,求可能认识的人。

(2). 技术分析

利用Set结构,一个博主对应一个Set结构,博主的id作为key,关注该博主的用户id作为value。

A:关注和取消关注: SetAdd方法 和 SetRemove方法

B:同时关注:求交集

C:关注的和:求并集

D:关注A的用户中也关注B的:遍历A中的用户,利用SetContains判断是否B中也存在

E:当A进入B页面,求可能认识的人:这里指的是关注B中的用户 扣去 里面也关注A的用户,就是A可能认识的人。

  求差集:B-A

(3). 代码分享

             //关注lmr的用户有:
db.SetAdd("lmr", "小1");
db.SetAdd("lmr", "小2");
db.SetAdd("lmr", "小3");
db.SetAdd("lmr", "小4");
db.SetAdd("lmr", "小5");
db.SetAdd("lmr", "rbp"); //关注ypf的用户有:
db.SetAdd("ypf", "小4");
db.SetAdd("ypf", "小5");
db.SetAdd("ypf", "小6");
db.SetAdd("ypf", "小7");
db.SetAdd("ypf", "小8");
db.SetAdd("ypf", "rbp"); //同时关注lmr和ypf的用户有:
string[] arry1 = { "lmr", "ypf" };
RedisKey[] keyList1 = arry1.Select(u => (RedisKey)u).ToArray();
var d1 = db.SetCombine(SetOperation.Intersect, keyList1).Select(u => (string)u).ToList(); //交集
foreach (var item in d1)
{
Console.WriteLine("同时关注lmr和ypf的用户有:" + item);
} //关注lmr和ypf的用户有(需要去重):
string[] arry2 = { "lmr", "ypf" };
RedisKey[] keyList2 = arry2.Select(u => (RedisKey)u).ToArray();
var d2 = db.SetCombine(SetOperation.Union, keyList2).Select(u => (string)u).ToList(); //并集
foreach (var item in d2)
{
Console.WriteLine("关注lmr和ypf的用户有:" + item);
} //关注lmr的人中也关注ypf的有:
var d3 = db.SetMembers("lmr").Select(u => (string)u).ToList();
foreach (var item in d3)
{
var isExist = db.SetContains("ypf", item);
if (isExist)
{
Console.WriteLine("关注lmr的人中也关注ypf的有:" + item);
}
} //当ypf进入lmr的页面,显示可能认识的人(应该显示:小1,小2,小3)
string[] arry4 = { "lmr", "ypf" }; // lmr-ypf
RedisKey[] keyList4 = arry4.Select(u => (RedisKey)u).ToArray();
var d4 = db.SetCombine(SetOperation.Difference, keyList4).Select(u => (string)u).ToList(); //差集 lmr-ypf
foreach (var item in d4)
{
Console.WriteLine("当ypf进入lmr的页面,显示可能认识的人:" + item);
}

4. 利用唯一性,可以统计访问网站的所有IP

三. SortedSet类型基础

1. 类型说明

  将Set中的元素增加了一个权重参数 score,使得集合中的元素能够按 score 进行有序排列, 同样元素名称也不能重复, 三个字段:key-member-score, key键,member值,score:权重或者打分值

2. 常用Api

(1).SortedSetAdd:增加,可以一次增加一个member,也可以一次增加多个member

(2).SortedSetIncrement 和 SortedSetDecrement:Score值自增或自减,如果不存在这member值,则执行增加操作,并返回当前Score值。

(3).获取相关

  SortedSetRangeByRank:根据索引获取member值,默认是升序,可以获取指定索引内的member值

  SortedSetRangeByScore:根据score获取member值,默认是升序,可以获取指定score开始和结束的member值,后面的skip和take用于分页

  SortedSetRangeByValue:根据member获取member值,默认是升序,可以获取指定member开始和结束的值,后面的skip和take用于分页

  SortedSetRangeByRankWithScores:获取member和score值,可以只返回 start-stop 这个索引排序内的值(默认升序),后面的skip和take用于分页

  SortedSetScore:获取指定key指定member的score值

  SortedSetLength:获取集合的数量

(4).删除相关

  SortedSetRemove:删除指定key和指定member,member可以是1个或多个

  SortedSetRemoveRangeByRank:删除指定索引开始到结束

  SortedSetRemoveRangeByScore:删除指定分开始到结束 (5分-8分)

  SortedSetRemoveRangeByValue::删除指定起始值和结束值(这里指定是member)

代码分享:

             //1.增加
//1.1 SortedSetAdd(RedisKey key, RedisValue member, double score),如果member之前存在,则会覆盖前面的score
db.SortedSetAdd("一年级", "ypf1", );
for (int i = ; i < ; i++)
{
db.SortedSetAdd("一年级", "ypf" + i, i);
}
//db.SortedSetAdd("一年级", "ypf1", 120); 会覆盖前面的score //1.2 score自增(刚开始如果没有这个member,会默认添加进去)
var dd1 = db.SortedSetIncrement("一年级", "ypf", );
//1.3 Score自减(刚开始如果没有这个member,会默认添加进去)
var dd2 = db.SortedSetDecrement("一年级", "ypf", ); //2.获取
{
//2.1 SortedSetRangeByRank:获取的是member值,可以只返回 start-stop 这个排序内的值
//2.1.1 默认是升序获取所有member值
var d1 = db.SortedSetRangeByRank("一年级");
string[] d1Arry1 = d1.Select(u => (string)u).ToArray(); //2.1.2 降序获取所有member值
var d2 = db.SortedSetRangeByRank("一年级", , -, Order.Descending);
string[] d1Arry2 = d2.Select(u => (string)u).ToArray(); //2.1.3 降序获取排名前4的member值
var d3 = db.SortedSetRangeByRank("一年级", , , Order.Descending);
string[] d1Arry3 = d3.Select(u => (string)u).ToArray();
}
{
//2.2 SortedSetRangeByScore:获取的是member值,可以只返回 start-stop 这个score内的值,后面的skip和take用于分页
//SortedSetRangeByScore(RedisKey key, double start = double.NegativeInfinity, double stop = double.PositiveInfinity, Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0, long take = -1, CommandFlags flags = CommandFlags.None);
//2.2.1 默认是升序获取所有member值
var d1 = db.SortedSetRangeByScore("一年级");
string[] d1Arry1 = d1.Select(u => (string)u).ToArray(); //2.2.2 降序获取所有member值
var d2 = db.SortedSetRangeByScore("一年级", double.NegativeInfinity, double.PositiveInfinity, Exclude.None, Order.Descending);
string[] d1Arry2 = d2.Select(u => (string)u).ToArray(); //2.2.3 降序获取score在【2,6】分内的值
var d3 = db.SortedSetRangeByScore("一年级", , , Exclude.None, Order.Descending);
string[] d1Arry3 = d3.Select(u => (string)u).ToArray();
}
{
//2.3 SortedSetRangeByValue:获取的是member值,可以只返回 member开始到结束的值,后面的skip和take用于分页
//2.3.1 默认是升序获取所有score值
var d1 = db.SortedSetRangeByValue("一年级");
string[] d1Arry1 = d1.Select(u => (string)u).ToArray(); //2.3.2 降序获取member在【ypf2,ypf6】分内的值
var d3 = db.SortedSetRangeByValue("一年级", "ypf2", "ypf6", Exclude.None, Order.Descending);
string[] d1Arry3 = d3.Select(u => (string)u).ToArray();
} {
//2.4 SortedSetRangeByRankWithScores:获取member和score值,可以只返回 start-stop 这个索引排序内的值,后面的skip和take用于分页
//2.4.1 默认是升序
SortedSetEntry[] d1 = db.SortedSetRangeByRankWithScores("一年级");
Dictionary<string, double> dic = new Dictionary<string, double>();
foreach (var item in d1)
{
dic.Add(item.Element, item.Score);
}
//2.4.2 降序获取所有
SortedSetEntry[] d2 = db.SortedSetRangeByRankWithScores("一年级", , -, Order.Descending);
Dictionary<string, double> dic2 = new Dictionary<string, double>();
foreach (var item in d2)
{
dic2.Add(item.Element, item.Score);
}
//2.4.3 降序获取排名前4的member和score值
SortedSetEntry[] d3 = db.SortedSetRangeByRankWithScores("一年级", , , Order.Descending);
Dictionary<string, double> dic3 = new Dictionary<string, double>();
foreach (var item in d3)
{
dic3.Add(item.Element, item.Score);
}
}
{
//2.5 获取指定key指定member的score值
var d1 = db.SortedSetScore("一年级", "ypf2");
}
//3. 获取集合的数量
long l1 = db.SortedSetLength("一年级"); //4. 删除
//{
// //4.1.SortedSetRemove:删除指定key和指定member,member可以是1个或多个
// bool num1 = db.SortedSetRemove("一年级", "ypf1");
// string[] arry1 = { "ypf2", "ypf3", "ypf4" };
// RedisValue[] newArry1 = arry1.Select(u => (RedisValue)u).ToArray();
// long num2 = db.SortedSetRemove("一年级", newArry1);
//} //{
// //4.2.SortedSetRemoveRangeByRank:删除指定索引开始到结束
// long num = db.SortedSetRemoveRangeByRank("一年级", 0, 2);
//} //{
// //4.3.SortedSetRemoveRangeByScore:删除指定分开始到结束 (5分-8分)
// long num = db.SortedSetRemoveRangeByScore("一年级", 5, 8);
//} //{
// //4.4.SortedSetRemoveRangeByValue:删除指定起始值和结束值(这里指定是member)
// long num = db.SortedSetRemoveRangeByValue("一年级", "ypf3", "ypf6");
//}

四. SortedSet案例

1. 热词搜索

(1). 需求:

  统计某个网站热词搜索,并实时显示前5名的词及其搜索次数。

PS:实时显示无非就是每隔几秒查询一次,大部分情况我们不建议直接去Redis里查排名,可以把前五名的相关数据存储到Redis的String结构中, 设置5分钟过期。

下面的案例不考虑上面的PS情况,不考虑IP限制问题(前面访问量案例做过),仅仅做最简单的热词搜索,刷新显示

(2).技术分析:

  利用Redis中SortedSet(key-member-score)这一数据结构,利用SortedSetIncrement方法的原子性,每搜索一个词,Score值加1(member不存在则执行的是增加操作), 然后再利用SortedSetRangeByRankWithScores方法获取Score值前五的memeber和Score数据

代码分享:

        /// <summary>
/// 热词搜索页面
/// </summary>
/// <returns></returns>
public IActionResult Index()
{
//获取排名前5的数据
var data = _redis.SortedSetRangeByRankWithScores("hotWord", , , Order.Descending);
Dictionary<string, double> dic3 = new Dictionary<string, double>();
foreach (var item in data)
{
dic3.Add(item.Element, item.Score);
}
ViewBag.data = dic3; return View();
} /// <summary>
/// 查询接口
/// </summary>
/// <param name="word"></param>
/// <returns></returns>
public string HotSearch(string word)
{
try
{
_redis.SortedSetIncrement("hotWord", word, );
return "ok";
}
catch (Exception ex)
{
return "error";
}
}

2.宝宝投票

  分析:和上面的热词原理一样,利用SortedSet的1个key对应多个不重复的 member-score,key用来存储一个标记,比如叫做“AllChildren”,代表该标记下存储宝宝的投票情况,member可以存储

宝宝的标记id,score存储该宝宝的投票数量.

同样原理:利用SortedSetIncrement进行自增存储,利用SortedSetRangeByRankWithScores获取排名情况

3.高积分用户排行榜

  类似案例:主播-粉丝刷礼物的排行榜(同时看主播的排行和每个主播下面粉丝的排行), 原理和上面都一样

总结: 

  SortedSet和String利用其自增自减(并返回当前值)原子性均可以实现计数器的作用,String是针对单个,Sorted是针对某个类别下的多个或每一个,并且实现排序功能。 Hash类型也能实现某个类别下多个物品的计数,但它不具有排序功能。

!

  • 作       者 : Yaopengfei(姚鹏飞)
  • 博客地址 : http://www.cnblogs.com/yaopengfei/
  • 声     明1 : 本人才疏学浅,用郭德纲的话说“我是一个小学生”,如有错误,欢迎讨论,请勿谩骂^_^。
  • 声     明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
 

第二节: Redis之Set类型和SortedSet类型的介绍和案例应用的更多相关文章

  1. 第一节: Redis之String类型和Hash类型的介绍和案例应用

    一. String类型基础 1.类型介绍 典型的Key-Value集合,如果要存实体,需要序列化成字符串,获取的时候需要反序列化一下. 2. 指令Api说明 3.常用Api说明 (1).StringS ...

  2. 【redis】02string类型和hash类型

    Redis的数据类型   Redis主要分为五个数据类型,一个是string,最简单的一个数据类型,hash,list, 还有set集合,还有zset有序集合,这是咱们redis的五种基础类型, 接下 ...

  3. 【redis】04set类型和zset类型

    sets类型   sets类型及操作   Set类型是一个集合,他是string类型的无序集合,也就是说咱们的set是没有顺序的, Set是通过hash table实现的,添加.删除和查找的复杂度都是 ...

  4. 学JAVA第二十三天,List类型和Set类型

    数组,是我们最常用的,但是有时候,我们要用数组,但是又不知道数组的类的长度的时候, 我们java就有一个很好用的工具Collection,这都是java的爸爸的用心良苦,Collection中包含Li ...

  5. DATETIME类型和BIGINT 类型互相转换

    项目中使用BIGINT来存放时间,以下代码用来转换时间类型和BIGINT类型 SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO -- ========= ...

  6. Timestame类型和String 类型的转化

    Timestame类型和String 类型的转化 String转化为Timestamp: SimpleDateFormat df = new SimpleDateFormat("yyyy-M ...

  7. Python3.x中bytes类型和str类型深入分析

    Python 3最重要的新特性之一是对字符串和二进制数据流做了明确的区分.文本总是Unicode,由str类型表示,二进制数据则由bytes类型表示.Python 3不会以任意隐式的方式混用str和b ...

  8. Date类型和Long类型的相互转换

    Date类型和Long类型的相互转换: import java.text.SimpleDateFormat; import java.util.Date; public class T { publi ...

  9. java中XMLGregorianCalendar类型和Date类型之间的相互转换

    import java.text.SimpleDateFormat;import java.util.Date;import java.util.GregorianCalendar;import ja ...

随机推荐

  1. 五分钟搞定 HTTPS 配置,二哥手把手教

    01.关于 FreeSSL.cn FreeSSL.cn 是一个免费提供 HTTPS 证书申请.HTTPS 证书管理和 HTTPS 证书到期提醒服务的网站,旨在推进 HTTPS 证书的普及与应用,简化证 ...

  2. 前端之javascript2

    js组成和标签获取元素 javascript组成 1.ECMAscript javascript的语法(变量.函数.循环语句等语法)2.DOM 文档对象模型 操作html和css的方法(比如通过id或 ...

  3. MVC过滤器:自定义异常过滤器使用案例

    在上一篇文章中讲解了自定义异常过滤器,这篇文章会结合工作中的真实案例讲解一下如何使用自定义异常过滤器. 一.需求 本案例要实现的功能需求:在发生异常时记录日志,日志内容包括发生异常的Controlle ...

  4. MySQL学习——操作表

    MySQL学习——操作表 摘要:本文主要学习了使用DDL语句操作表的方法. 创建表 语法 create table 表名 [表定义选项] [表选项]; 表定义选项 用来创建定义表的结构,由列名(col ...

  5. PHP面试题2019年百度工程师面试题及答案解析

    一.单选题(共10题,每题5分) 1.以下代码输出的结果是? ​​​ A.[0,1,2,3] B.[1,3,5,7,5] C.[1,2,3,4,5] D.[0,1,2,3,5] 参考答案:D 答案解析 ...

  6. HTML常用标签一

    html文本格式化标签 在网页中,有时需要为文字设置粗体 .斜体 或下划线 效果,这是就需要用到HTML中的文本格式标签,是文字以特殊的方式显示 标签语义:突出重要性,比普通文字更重要 语义 标签 说 ...

  7. 初识HTML_表单

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...

  8. Typescript基础(4)——接口

    前言 今天继续typescript的学习,开始ts接口部分的学习. 接口 接口的理解 首先,我们谈论一下现实生活中的接口.比如生活中常用的插座接口,有些插头是三孔插座的,有些是两孔插座的.插座接口规定 ...

  9. python async

    理解 python 的 async:1. 协程后面的技术也是 IO 多路复用, Nginx 和 nodejs 都充分利用了这种机制. 2. Flask 线程中为什么不能直接使用标准库 asyncio ...

  10. Python—函数基础篇

    https://www.cnblogs.com/littlefivebolg/p/9017449.html Python 解释器的内置函数 内置函数 作用 实例 abs() 返回数字的绝对值. abs ...