背景
工作中经常遇到多选值存储问题,例如:发送消息时,用户可选 短信,app,pc系统,微信,邮箱等。
方案
方案1:多字段存储
一个值对应一个字段。0表示未拥有,1表示拥有。
这种方式等缺点:每增加一种认证方式都需要添加一个表字段需要修改数据结构,扩展性差。
方案2:单字段拼接
单字段存储数据,多个值用分隔符区分。比如以如下方式存储:【短信,app】。
这种方式等缺点:不利于查询,需要使用模糊查询,搞不好会影响性能。
方案3:使用附表形式
使用附表存储数据,需要和主表数据关联起来。
这种方式等缺点:表设计复杂,查询需要关联查询数据
方案4:使用int进行位运算
比如 1代表短信,2代表app。则3可以表示短信和app。
这种方式很好的弥补了上诉方式的缺点。缺点是代码相对复杂,不易理解。
方案4案例
该方案需要注意一下几点:
- 枚举值定义时需要相加不重复。采用机器二进制数[1,2,4,8,16,32,64...]就能很好的表达该数据结构。
- 根据数据结构,需提供以下方法:
- 判断某个数据值是否存在于存储值中(having),用于判断数据库是否存储某个值时。
- 将存储值进行数据拆分,拆分成单项(mapping),用于将数据库保存数据进行还原。
- 将多个数据值进行合并相加,整理成存储值(merging),用于进行数据库保存时。
- 返回多个数据值可能存在的存储值组合(being)相当于in查询。
实现细节
having
表示某个数值集合中是否包含某个数值。比如 数字集合3是否包含数字1, 输入(3,1),输出 true,即代表3中包含1。 用于数据查询后进行逻辑判断等操作。
代码示例:
public static boolean having(int number, int memberCode) {
return (number & memberCode) == memberCode;
}
mapping
表示返回当前数据可拆分的数据的集合。 比如:输入 7 ,输出 (1、2、4) 。 多用于查询后返回界面数据展示时。
代码示例:
public static int[] mapping(int number) {
return Arrays.stream(CODE).filter(v -> (number & v) == v).toArray();
}
merging
将散列的值进行合并,这步操作比较简单。 比如输入(1、2、4),输出 7 。 用于将所选的数值进行储存时调用。
代码示例:
public static int merging(int[] memberCode) {
return Arrays.stream(memberCode).sum();
}
being
用于返回某个数值集合存在于某个数值集合的可能性。举个列子:比如你现在想知道 (1、2)这个可能存在哪些合集中就可以调用该函数。 输入(1、2)输出(3、7 )。 这种在数据查询时多用到,比如用户想筛选我包含使用了短信发送信息的数据。 那么就需要调用该函数 being(1)获取所有包含1的可能性数值集合然后再去数据库作in查询。
代码示例:
public static Integer[] being(int... memberCodes) {
if (!inCode(memberCodes)) {
throw new IllegalArgumentException("参数错误");
}
int sum = Arrays.stream(memberCodes).sum();
Integer[] excludedCodes = excluded(memberCodes);
List<List<Integer>> objects = new ArrayList<>();
backtrack(objects, new ArrayList<>(), excludedCodes, 0);
List<Integer> allSum = new ArrayList<>();
allSum.add(sum);
objects.forEach(o -> {
int i = 0;
for (Integer integer : o) {
i += integer;
}
allSum.add(i + sum);
});
return allSum.toArray(new Integer[0]);
}
一起进步
你好,我是啊Q,是一个爱技术爱生活的的程序员。
写程序几年了,现在想记录一下自己的程序员生活和故事,来掘金和大家交朋友。写的不好请多多包涵,支持一下。
我是啊Q,可掘金关注或私信我,我们一起进步。
开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 1 天 ,点击查看活动详情
ps:由于规则限制,想需要详细的代码的小伙伴可私信我。