最近接收到了一个需求,一个用户有一组搜索串,比如字符串:abcdefg,如果新增的数据是abdcdfge,则视为是同一条数据,可以理解为即使顺序不同,但是实际上也是唯一串;
为什么是一个数据串我解释一下:大部分的公司es搜索功能(中台),都是会做二次开发的,到实际业务部门,需要根据他们二次开发的规则,提供一个查询数据串
思路
- 对即将生成的唯一串数据,要先处理成有序字段【treeMap】
- 生成的唯一串结果最好是定长(不定长的字符串->定长的字符串:hash算法)
- 运算效率(暂时不考虑,一般都很快)
有点数字签名的意思,只不过这个地方不需要公司秘钥
代码
我选择的是sha-256哈希加密,安全性还可以,生成的效率够用
如果不考虑安全性的话,md5效率要更好一点;
public static String genUniqueKey(String uid, String queryCond) {
String[] paramsArray = queryCond.split("&");
Map<String, String> paramsMap = new TreeMap<>();
for (String param : paramsArray) {
String[] keyValue = param.split("=");
paramsMap.put(keyValue[0], keyValue[1]);
}
// 将参数按照键名排序
StringBuilder sortedParams = new StringBuilder(uid).append("&");
for (Map.Entry<String, String> entry : paramsMap.entrySet()) {
sortedParams.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
}
// 使用SHA-256生成哈希值
try {
java.security.MessageDigest md = java.security.MessageDigest.getInstance("SHA-256");
byte[] hash = md.digest(sortedParams.toString().getBytes("UTF-8"));
StringBuilder hexString = new StringBuilder();
for (byte b : hash) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) hexString.append('0');
hexString.append(hex);
}
return hexString.toString();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
思考
sha-256生成的串太长了,想短点?
这个算法输出的结果是64(32 * 2)个字符,一个字符2个字节(sha-256生成的结果是32位的)
策略1: 截断处理
直接截断或者自定义抽取不同位置的字符,形成新的串,数据量不大的情况下,理论上唯一性也可以保证
策略2:套娃算法
- 考虑过压缩算法(一般在网络传输的时候传输、接收用,不符合场景)
- 使用其他的定长算法二次加密【没找到】
- 自定义算法,一定范围内的定长【转载自:https://developer.aliyun.com/article/816907,见仁见智吧,如果每个字符串都不一样,长度直接翻倍了】
第一种,只统计字符出现次数,比如aabcccccaaa,压缩成a5b1c5
public static String doDepressTwo(String str) {
if ("".equals(str)) return str;
char[] chars = str.toCharArray();
HashMap<Character, Integer> map = new HashMap<>();
for (char c : chars) {
if (map.containsKey(c)) {
//如果存在该键,则只需要value+1
Integer integer = map.get(c);
map.put(c, ++integer);
} else {
//如果不存在该键,就直接放入,value为1
map.put(c, 1);
}
}
StringBuilder sb = new StringBuilder();
map.entrySet().forEach(entry -> {
//将key和value拼接到sb
sb.append(entry.getKey()).append(entry.getValue());
});
return sb.toString();
}
第二种,统计相邻字符串出现次数,比如aabcccccaaa,压缩成a2b1c5a3
思路:需要维护一个当前对比字符,一个字符出现次数,用第n个字符去和第n+1个字符对比,相等则出现次数+1,不相等则拼接在字符串后面
public static String doDepress(String str) {
//字符串为空直接返回
if ("".equals(str)) return str;
StringBuilder buffer = new StringBuilder();
//用第n个字符去和第n+1个字符对比
char c = str.charAt(0);
//记录字符出现次数,默认为1
int repeat = 1;
for (int i = 1; i < str.length(); i++) {
if (c == str.charAt(i)) {
//如果第n个字符和第n+1个字符相等,则出现次数+1
repeat++;
} else {
//如果第n个字符和第n+1个字符不相等,则拼接在字符串后面
buffer.append(c).append(repeat);
//拼接完后重置当前字符串
c = str.charAt(i);
//重置字符出现次数
repeat = 1;
}
}
//最后一组在for循环里是没有拼接的
return buffer.append(c).append(repeat).toString();
}