
1、引言
在微信里,关注了某个公众号或者进了某个群,经常会显示你有几个共同的朋友。如图:

作为一个技术出身的人,必然要探究一下其背后技术。如果是自己去实现应该怎么实现呢?
2、抽丝剥茧
我们要探究两个圈子的共同的朋友,首先我们得有两个圈子,以上面的关注的公众号编程朝花夕拾为例。一个是我自己的朋友圈子,另一个去圈子则是关注这个公众号的圈子,共同的朋友就是两个圈子共同的朋友。
使用数学思想就可以将两个圈子抽象成集合(A,B),朋友抽象成集合中的元素。公共的朋友就是两个集合的交集(A ∩ B)。
至此,就演变成两个集合的运算了。
3、Java中的集合运算
集合的运算我们需要借助apache
的工具类,maven以来如下:
xml
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>${latest.version}</version>
</dependency>
集合的交集(A∩B)
两个集合都有的元素:
java
@Test
void contextLoads() {
// 我的朋友圈
List<String> myFriends = List.of(
"张君宝","杨过","郭靖",
"黄蓉","赵敏","周芷若",
"李莫愁","小龙女","令狐冲"
);
// 公众号朋友圈
List<String> gZHFriends = List.of(
"张君宝","黄蓉","赵敏",
"周芷若","令狐冲", "胡斐",
"欧阳锋", "李寻欢", "林平之",
"岳不群", "任我行"
);
// 共同的朋友
Collection<String> commonFriends = CollectionUtils.intersection(myFriends, gZHFriends);
// 输出结果:[赵敏, 张君宝, 令狐冲, 黄蓉, 周芷若]
System.out.println(commonFriends);
}
集合的并集(A∪B)
两个集合所有的元素
java
@Test
void union() {
// 我的朋友圈
List<String> myFriends = List.of(
"张君宝","杨过","郭靖",
"黄蓉","赵敏","周芷若",
"李莫愁","小龙女","令狐冲"
);
// 公众号朋友圈
List<String> gZHFriends = List.of(
"张君宝","黄蓉","赵敏",
"周芷若","令狐冲", "胡斐",
"欧阳锋", "李寻欢", "林平之",
"岳不群", "任我行"
);
// 两个所有的朋友
Collection<String> unionFriends = CollectionUtils.union(myFriends, gZHFriends);
// 输出结果:
// [任我行, 小龙女, 赵敏, 张君宝, 岳不群, 李莫愁, 令狐冲,
// 杨过, 黄蓉, 欧阳锋, 李寻欢, 郭靖, 周芷若, 胡斐, 林平之]
System.out.println(unionFriends);
}
集合的差集(A-B)
集合A中不包含集合B的所有元素
java
@Test
void subtract() {
// 我的朋友圈
List<String> myFriends = List.of(
"张君宝","杨过","郭靖",
"黄蓉","赵敏","周芷若",
"李莫愁","小龙女","令狐冲"
);
// 公众号朋友圈
List<String> gZHFriends = List.of(
"张君宝","黄蓉","赵敏",
"周芷若","令狐冲", "胡斐",
"欧阳锋", "李寻欢", "林平之",
"岳不群", "任我行"
);
// 共同的朋友
Collection<String> subtractFriends = CollectionUtils.subtract(myFriends, gZHFriends);
// 输出结果:[杨过, 郭靖, 李莫愁, 小龙女]
System.out.println(subtractFriends);
}
思考
这个基于内存的集合运算,如果是你,你会用在微信这个产品上么?
效果是可以实现的,但是最大的缺陷就是基于内存,如果遇到宕机或重启,数据造成数据的丢失。或者说每次启动都需要将各个圈子的数据加载到内存中才可以了。微信的数据量如此庞大,纯内存能不能支撑呢,会不会造成内存溢出呢?这些都是有可能的。
很显然这种方案,可以但是不那么合适。那么有没有什么其他更好的办法呢?那就得拿出我们的大杀器Redis
,轻松应对。
4、Redis实现海量关系数据实时计算
Set
和ZSet
是Redis
中的无序集合和有序集合都可以实现集合的运算,本章以Set
集合为例。
数据准备
shell
# 添加我的朋友圈
sadd myFriends "张君宝" "杨过" "郭靖" "黄蓉" "赵敏" "周芷若" "李莫愁" "小龙女" "令狐冲"
# 添加公众号的朋友圈
sadd gZHFriends "张君宝" "黄蓉" "赵敏" "周芷若" "令狐冲" "胡斐" "欧阳锋" "李寻欢" "林平之" "岳不群" "任我行"

交集(A∩B)
shell
# 共同的朋友
sinter myFriends gZHFriends

因为Redis客户端的问题,展示成16进制的内容了,翻译之后和集合运算的结果相同。
并集(A∪B)
shell
# 所有的朋友
sunion myFriends gZHFriends

这里就不做字符的翻译了,合计15个朋友,同集合运算的结果一致。
差集(A-B)
shell
# A中不包含B中的所有朋友
sdiff myFriends gZHFriends

同样获取的结果和集合运算一致。
Zset
同样可以实现,并且能够保持有序。
5、小结
看似小小的功能,里面其实蕴藏着许多设计思想。看似每一种方式都可以实现,但总有场景不适合的,这也是技术选型的难点。
Redis
有其得天独厚的优势,不仅因为它是基于内存计算的,更重要的是它有持久化的功能,RBD和AOF为其保驾护航。速度又快,更不拍数据的丢失。
这就是一个程序员的日常和思考。
关注我的公众号:【编程朝花夕拾】,可获取首发内容。