如何在Elasticsearch中停止令牌分析及解决文件名字段分词问题
首先来说说你遇到的问题:你的filename字段是text类型,默认会被标准分词器处理——标准分词器会把.当作分隔符,所以像bob4322_35_223.jpg这样的文件名会被拆分成["bob4322_35_223", "jpg"]。你用addDocValueField("filename")获取的是分词后的词项集合,这就是为什么返回的是数组。
你提到的循环重组方式确实存在隐患:如果文件名包含多个.(比如report.v2.2024.jpg),分词后会得到["report","v2","2024","jpg"],直接拼接会变成reportv22024jpg,完全丢失了原始的分隔符,这显然不是你想要的结果。
接下来给你几个可行的解决方案,以及如何停止令牌分析:
方案1:添加keyword子字段(推荐)
最稳妥的方式是给filename字段新增一个keyword类型的子字段,keyword类型不会对文本进行分词,会完整存储原始文件名。修改你的字段映射为:
"filename": { "type": "text", "fielddata": true, "fields": { "keyword": { "type": "keyword", "ignore_above": 256 // 根据你的文件名最大长度调整,超过这个长度的会被截断 } } }
修改映射后需要重新索引已有数据。之后在Java API中,你只需要获取这个子字段的doc value即可:
client.prepareSearch(IMYINDEX) .setQuery(query) .addDocValueField("filename.keyword") // 获取未分词的原始文件名 .get();
这样返回的filename.keyword就是完整的bob4322_35_223.jpg,无需拼接。这种方式的好处是:text字段依然支持分词查询(比如搜索包含bob的文件名),keyword子字段可以获取原始值,兼顾两种需求。
方案2:直接获取_source中的原始值
如果你不想修改映射,可以直接从返回结果的_source中提取原始的filename值——_source存储的是你索引时的原始数据,不会被分词处理。Java代码示例:
SearchResponse response = client.prepareSearch(IMYINDEX) .setQuery(query) .setFetchSource(true) // 确保开启获取_source(默认是开启的,显式指定更稳妥) .get(); for (SearchHit hit : response.getHits().getHits()) { // 从_source中获取原始filename String originalFilename = (String) hit.getSourceAsMap().get("filename"); // 处理文件名 }
注意:如果你的查询设置了setFetchSource(false),需要改成true才能获取到_source。
如何停止令牌分析?
要让Elasticsearch不对filename进行分词,有两种方式:
- 使用keyword类型作为主字段:如果不需要对文件名进行分词查询,直接把
filename的类型设为keyword:
"filename": { "type": "keyword" }
这样存储和查询时都是完整的字符串,不会被拆分。
- 给text字段指定keyword分词器:如果需要保留text类型的某些特性,但又不想分词,可以指定
analyzer: "keyword":
"filename": { "type": "text", "fielddata": true, "analyzer": "keyword" // 使用keyword分词器,不会拆分文本 }
这种方式下,text字段存储的是完整的文件名,doc value返回的也是完整字符串,但查询时只能精确匹配整个文件名,无法进行分词后的模糊查询。
总结一下,最推荐的是方案1(text+keyword子字段),既能支持分词查询,又能方便获取原始文件名,避免拼接带来的错误。
内容的提问来源于stack exchange,提问作者Stellar Sword

