Kotlin基于contains()实现搜索:字符串与列表的行为差异及替代joinToString的方案
Kotlin 搜索功能实现:是否可以不使用
joinToString 达成一致匹配效果 基础数据结构定义
业务中维护的可搜索对象结构如下:
data class Sample( val name: String, val keywords: List<String>, // 其他业务属性 )
原有可正常运行的搜索实现
最初的实现通过拼接字符串完成关键词的模糊匹配,可满足预期效果:
private lateinit var samples: List<Sample> fun search(keyword: String) { val samplesSearch: MutableList<Sample> = mutableListOf() samples.forEach { sample -> // 名称转小写后做子串匹配 val sampleName = sample.name.toLowerCase(Locale.ROOT) // 关键词列表转小写后,拼接为逗号分隔的长字符串 val sampleKeywords = sample.keywords.joinToString( separator = ",", transform = { it.toLowerCase(Locale.ROOT) } ) // 命中规则:名称包含搜索词 或 拼接后的关键词字符串包含搜索词 if (sampleName.contains(keyword) || sampleKeywords.contains(keyword)) { samplesSearch.add(sample) } } }
常见实现误区:直接对关键词列表调用 contains
不少人尝试跳过joinToString的拼接环节,直接操作关键词列表,写出如下实现:
fun search(keyword: String) { val samplesSearch: MutableList<Sample> = mutableListOf() samples.forEach { sample -> val sampleName = sample.name.toLowerCase(Locale.ROOT) // 尝试对列表元素做小写转换 val sampleKeywords = sample.keywords.onEach { it.toLowerCase(Locale.ROOT) } if (sampleName.contains(keyword) || sampleKeywords.contains(keyword)) { samplesSearch.add(sample) } } }
这种实现完全达不到预期效果,核心原因有两个:
- 字符串和列表的
contains方法行为存在本质差异- 字符串的
contains:只要匹配到任意连续字符序列即可命中,不要求整串完全相等 - 列表的
contains:要求传入的搜索值必须和列表中某一个完整元素完全相等才能命中,不支持子串级别的模糊匹配
- 字符串的
- 存在隐蔽的大小写bug:Kotlin的String是不可变类型,
onEach { it.toLowerCase() }生成的小写结果没有被重新赋值回列表元素,实际参与匹配的还是原大小写的字符串。
无拼接的正确实现方案
跳过字符串拼接环节,直接对列表的每个元素单独做子串匹配,即可替代原有joinToString实现:
fun search(keyword: String) { val samplesSearch: MutableList<Sample> = mutableListOf() samples.forEach { sample -> // 直接使用String自带的忽略大小写参数,无需手动转小写 if (sample.name.contains(keyword, ignoreCase = true) || // 对每个关键词元素单独做子串匹配,任意一个关键词命中即算匹配成功 sample.keywords.any { it.contains(keyword, ignoreCase = true) } ) { samplesSearch.add(sample) } } }
边缘场景说明:两种实现存在极个别场景的匹配差异——如果搜索词本身包含逗号(比如搜索内容为
"苹果,香蕉"),原拼接实现会匹配跨两个关键词的组合,但逐元素匹配不会命中这类跨词组合。如果业务上不存在这类跨词搜索需求,逐元素实现完全可以替代原拼接方案,同时省去字符串拼接带来的额外内存开销。
内容的提问来源于stack exchange,提问作者user11255734
相关产品推荐
相关产品推荐

