背景
最近想到搜索引擎是如何快速的匹配字符串的,假如数据库录入几十万甚至几千万的数据...那计算量该有多大?
数据来源, 本次测试数据量是12W+条。
暴力破解
const data = [...] // 假设10W+数组字符
/**
* indexOf查找方式
* @param {*} str
*/
function matchIndexOf(str) {
console.log('输入', str);
console.time()
const match = data.filter((e) => ~e.indexOf(str) === 0)
console.timeEnd()
}
matchIndexOf('Zach') // 匹配Zach开头
试一下这个性能怎样
测试方式 | 搜索内容 | 耗时 |
---|---|---|
暴力破解 | Zach | 8.997ms |
暴力破解 | J | 5.991ms |
暴力破解 | Juabc | 7.615ms |
这个对于小数据还好,但是对于大数据的时候就显得有点鸡肋,那大数据量有没有更好的办法?答案是有,那就是提前对数据做索引,通过key: string[]
的形式匹配。下面来讲详细过程:
索引方式搜索
索引方式搜索分几个步骤:
- 打好索引。
- 用打好索引的数据进行搜索。
先说说要达到的目的结果是什么样子
// 原始数据
const oldData = [
"Z",
"Zac",
"Zs",
"Zccddasda",
"Zaxzx",
"Za",
"Zacg",
"Zac",
"Zacgsdsa",
...
]
// 搜索内容
var search = 'Z'
// 已经打好索引的数据
var matchIndexData = {
'Z': ['Z', 'Zac', 'Zs', 'Z2s', 'Zccddasda'], // 以 Z 开头数据
'Za': ['Zaxzx', 'Za', 'Zacg', 'Zac', 'Zacgsdsa'], // 以 Za 开头的数据
'Zac': [...], // 以 Zac 开头的数据
'Zach': [...], // 以 Zach 开头的数据
...
}
// 模拟搜索
console.log(matchIndexData[search])
[
'Z',
'Zac',
'Zs',
'Z2s',
'Zccddasda'
]
输入Z
进行搜索,得出以Z
为key的数组数据
实现方式
思路大概是整个数组循环一次,然后再对各个字符串进行循环,其中累计加当前下标的字符当成key
,并把当前字符串设置为value
/**
* 创建索引补丁
* @param {Array} data
* @returns
*/
function createPatch(data) {
const obj = {}
const adds = (item) => {
let key = ''
for (let i = 0; i < item.length; i++) {
const e = item[i];
key += e
if (obj[key]) {
obj[key].push(item)
} else {
obj[key] = [item]
}
}
}
data.forEach(e => adds(e))
return obj
}
// 创建索引补丁
var matchIndexData = createPatch(data)
var search = 'Zach'
// 模拟搜索
console.time()
const match = matchIndexData[search]
console.timeEnd()
性能对比
测试方式 | 搜索内容 | 耗时 |
---|---|---|
索引 | Zach | 0.113ms |
索引 | J | 0.102ms |
索引 | Juabc | 0.099ms |
总体保持在0.1毫秒左右。
索引方式搜索-Map版本
/**
* 创建索引补丁
* @param {Array} data
* @returns
*/
function createPatch(data) {
const map = new Map()
const adds = (item) => {
let key = ''
for (let i = 0; i < item.length; i++) {
const e = item[i];
key += e
if (map.has(key)) {
map.get(key).push(item)
} else {
map.set(key, [item])
}
}
}
data.forEach(e => adds(e))
return map
}
var map = createPatch(data)
console.time()
map.get('M')
console.timeEnd()
性能对比
测试方式 | 搜索内容 | 耗时 |
---|---|---|
索引Map版本 | Zach | 0.076ms |
索引Map版本 | J | 0.078ms |
索引Map版本 | Juabc | 0.076ms |
总结
测试方式 | 搜索内容 | 耗时 |
---|---|---|
暴力破解 | Zach | 8.997ms |
暴力破解 | J | 5.991ms |
暴力破解 | Juabc | 7.615ms |
索引 | Zach | 0.113ms |
索引 | J | 0.102ms |
索引 | Juabc | 0.099ms |
索引Map版本 | Zach | 0.076ms |
索引Map版本 | J | 0.078ms |
索引Map版本 | Juabc | 0.076ms |
通过key: value
形式搜索字符串的方式比起暴力循环破解会快狠多,本次测试数据量是12W+条,两种查找方式性能差距7倍。
其次,es6
中的Map
数据结构和对象性能对比,Map
的查找方式快一点,数据量越大越明显,当然也有兼容性要求,如果对兼容性不要求的情况下,尽量使用Map
。(Ps:最近面试问了好多位中高级前端,大多数对Map
只存在于认识,不懂什么场景下使用)。
通过本文,你能学习到大量字符串搜索和Map
数据结构。
本文源码。