java实现中文模糊查询的示例代码

 更新时间:2025年06月10日 09:32:54   作者:Katie。  
Java作为后端主流语言,承担着绝大多数企业级应用的检索功能,如何在Java中实现中文模糊查询,兼顾准确率和性能,是企业和开发者面对的共同挑战,下面我们就来看看具体实现方法吧

1. 项目背景详细介绍

1.1 检索体验现状

随着互联网和移动应用的普及,用户越来越习惯于“所见即所得”的搜索体验。传统的精确匹配(Exact Match)已经无法满足用户在海量中文数据中进行快速定位的需求——拼写错误、输入法候选词偏差、用户记忆模糊等都会导致精确匹配失败。

1.2 模糊查询的重要性

模糊查询(Fuzzy Search)通过对关键词进行相似度或近似度计算,能够容忍用户输入的错别字、音近字、简繁体差异等。它在电商商品搜索、企业通讯录检索、日志分析、智能客服、医疗诊断辅助等场景中发挥着至关重要的作用。

1.3 Java 平台的应用场景

Java 作为后端主流语言,承担着绝大多数企业级应用的检索功能。如何在 Java 中高效、可扩展地实现中文模糊查询,兼顾准确率和性能,是企业和开发者面对的共同挑战。

2. 项目需求详细介绍

2.1 功能性需求

  • 支持拼写纠错:对用户输入的错别字进行纠正,如“北京”可匹配“北京”;
  • 支持拼音首字母和全拼匹配:如“bj”或“beijing”均可匹配“北京”;
  • 支持简繁体互转:输入“國家”也可匹配“国家”;
  • 支持编辑距离匹配:允许1–2个字符的插入、删除、替换;
  • 基于数据库与内存双模式:既可对 MySQL/Oracle 等数据库的指定字段进行 LIKE+补偿算法查询,也可对内存中 Java 对象列表进行快速检索;
  • 提供分页排序:允许按照匹配度或相关度排序,并支持分页加载;
  • 简单易用 API:封装成 Java 类库,支持 Maven/Gradle 一键引入;

2.2 非功能性需求

高性能:100 万级记录内存检索毫秒级返回;数据库检索在索引列上 100ms 内响应;

易扩展:可插拔分词器(IKAnalyzer、HanLP 等)、可替换相似度算法(Jaro-Winkler、Cosine、TF-IDF+BM25);

可维护性:模块化设计、单元测试覆盖率≥90%,可生成 JavaDoc 文档;

兼容性:Java 8+;数据库可兼容主流 RDBMS;

3. 相关技术详细介绍

3.1 中文分词与拼音处理

IKAnalyzer:基于 Lucene 的轻量级中文分词器,效率高、精度好;

HanLP:功能完备,支持命名实体识别等高级 NLP 功能;

pinyin4j:用于中文转拼音、获取声母、韵母;

3.2 相似度与编辑距离算法

Levenshtein 编辑距离:衡量两个字符串之间的最小编辑操作数;

Damerau–Levenshtein:在编辑距离基础上加入相邻字符交换;

Jaro–Winkler:对短字符串(人名、地名)效果更好;

3.3 数据库 LIKE 优化

前缀匹配索引:WHERE col LIKE '北京%' 可走索引;

倒排索引模拟:将字段拆分为 n-gram 存储,并对 n-gram 建索引;

全文索引:MySQL InnoDB 支持全文检索,但对中文支持有限;

4. 实现思路详细介绍

4.1 系统架构

core 模块:提供 FuzzySearchService 接口及默认实现

pinyin 模块:封装 PinyinConverter,提供全拼、首字母转换等

distance 模块:封装多种相似度计算器,如 LevenshteinDistance、JaroWinklerDistance

db 模块:DatabaseSearchService,对接 JDBC,实现基于 LIKE+补偿算法的模糊查询

memory 模块:InMemorySearchService,对 Java 对象列表进行索引与检索

4.2 数据处理流程

标准化:输入关键词去除空白、统一简繁体、转为小写;

拼音扩展:生成全拼、首字母两个维度的候选关键词;

分词:对数据库字段或内存对象属性进行分词,生成 n-gram 或词元列表;

匹配:

  • 内存模式:对每个对象属性字符串计算相似度评分,过滤阈值以上结果;
  • 数据库模式:先用 LIKE '%key%' 或 n-gram 索引粗筛,再在 Java 端补偿计算真实相似度;
  • 排序与分页:根据相似度打分降序排序,截取指定页码结果;

5. 完整实现代码

// 文件:core/FuzzySearchService.java
package com.example.fuzzy.core;
import java.util.List;
import java.util.Map;
/**
 * 模糊查询服务接口
 */
public interface FuzzySearchService<T> {
    /**
     * 对内存数据列表进行模糊查询
     * @param dataList 待检索对象列表
     * @param fieldExtractor 字段提取器,返回待匹配字符串
     * @param keyword 用户输入关键词
     * @param topK 返回前 K 名排序结果
     * @return 匹配结果列表
     */
    List<T> searchInMemory(List<T> dataList,
                           FieldExtractor<T> fieldExtractor,
                           String keyword,
                           int topK);
 
    /**
     * 对数据库指定表字段进行模糊查询
     * @param tableName 表名
     * @param columnName 列名
     * @param keyword 用户输入关键词
     * @param params JDBC 参数(如分页)
     * @return 查询结果列表,每条记录为列名→值的 Map
     */
    List<Map<String, Object>> searchInDatabase(String tableName,
                                               String columnName,
                                               String keyword,
                                               Map<String, Object> params);
}
 
// 文件:core/FieldExtractor.java
package com.example.fuzzy.core;
/**
 * 字段提取器,用于从对象中获取待匹配字符串
 */
public interface FieldExtractor<T> {
    String extract(T obj);
}
 
// 文件:pinyin/PinyinConverter.java
package com.example.fuzzy.pinyin;
import net.sourceforge.pinyin4j.PinyinHelper;
import net.sourceforge.pinyin4j.format.*;
/**
 * 拼音转换工具
 */
public class PinyinConverter {
    private static HanyuPinyinOutputFormat fmt = new HanyuPinyinOutputFormat();
    static {
        fmt.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
        fmt.setVCharType(HanyuPinyinVCharType.WITH_V);
    }
    /** 获取字符串全拼,如“北京”→“beijing” */
    public static String toPinyin(String chinese) throws BadHanyuPinyinOutputFormatCombination {
        StringBuilder sb = new StringBuilder();
        for (char c : chinese.toCharArray()) {
            if (Character.toString(c).matches("[\\u4E00-\\u9FA5]+")) {
                String[] arr = PinyinHelper.toHanyuPinyinStringArray(c, fmt);
                sb.append(arr[0]);
            } else {
                sb.append(c);
            }
        }
        return sb.toString();
    }
    /** 获取拼音首字母,如“北京”→“bj” */
    public static String toPinyinInitials(String chinese) throws BadHanyuPinyinOutputFormatCombination {
        StringBuilder sb = new StringBuilder();
        for (char c : chinese.toCharArray()) {
            if (Character.toString(c).matches("[\\u4E00-\\u9FA5]+")) {
                String[] arr = PinyinHelper.toHanyuPinyinStringArray(c, fmt);
                sb.append(arr[0].charAt(0));
            } else {
                sb.append(c);
            }
        }
        return sb.toString();
    }
}
 
// 文件:distance/LevenshteinDistance.java
package com.example.fuzzy.distance;
/**
 * 编辑距离算法实现
 */
public class LevenshteinDistance {
    public static int compute(String s1, String s2) {
        int n = s1.length(), m = s2.length();
        int[][] dp = new int[n+1][m+1];
        for (int i = 0; i <= n; i++) dp[i][0] = i;
        for (int j = 0; j <= m; j++) dp[0][j] = j;
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++) {
                int cost = s1.charAt(i-1) == s2.charAt(j-1) ? 0 : 1;
                dp[i][j] = Math.min(Math.min(dp[i-1][j] + 1, dp[i][j-1] + 1),
                                    dp[i-1][j-1] + cost);
            }
        }
        return dp[n][m];
    }
    /** 归一化相似度 = 1 - distance/maxLen */
    public static double similarity(String s1, String s2) {
        int dist = compute(s1, s2);
        int max = Math.max(s1.length(), s2.length());
        return max == 0 ? 1.0 : 1.0 - (double) dist / max;
    }
}
 
// 文件:core/impl/InMemorySearchServiceImpl.java
package com.example.fuzzy.core.impl;
import com.example.fuzzy.core.*;
import com.example.fuzzy.distance.LevenshteinDistance;
import com.example.fuzzy.pinyin.PinyinConverter;
import java.util.*;
/**
 * 内存模糊查询实现
 */
public class InMemorySearchServiceImpl<T> implements FuzzySearchService<T> {
    @Override
    public List<T> searchInMemory(List<T> dataList,
                                  FieldExtractor<T> fieldExtractor,
                                  String keyword,
                                  int topK) {
        List<Result<T>> results = new ArrayList<>();
        // 预处理关键词
        String kw = preprocess(keyword);
        String kwPinyin = toPinyinSafe(kw);
        String kwInitials = toInitialsSafe(kw);
        for (T item : dataList) {
            String text = fieldExtractor.extract(item);
            String txt = preprocess(text);
            // 原文相似度
            double simText = LevenshteinDistance.similarity(txt, kw);
            // 拼音相似度
            double simPin = LevenshteinDistance.similarity(toPinyinSafe(txt), kwPinyin);
            // 首字母相似度
            double simInit = LevenshteinDistance.similarity(toInitialsSafe(txt), kwInitials);
            double score = Math.max(Math.max(simText, simPin), simInit);
            if (score > 0.5) {
                results.add(new Result<>(item, score));
            }
        }
        // 排序并截取 topK
        results.sort((a, b) -> Double.compare(b.score, a.score));
        List<T> top = new ArrayList<>();
        for (int i = 0; i < Math.min(topK, results.size()); i++) {
            top.add(results.get(i).data);
        }
        return top;
    }
    private String preprocess(String s) {
        return s == null ? "" : s.trim().toLowerCase();
    }
    private String toPinyinSafe(String s) {
        try { return PinyinConverter.toPinyin(s); }
        catch (Exception e) { return s; }
    }
    private String toInitialsSafe(String s) {
        try { return PinyinConverter.toPinyinInitials(s); }
        catch (Exception e) { return s; }
    }
 
    @Override
    public List<Map<String, Object>> searchInDatabase(String tableName, String columnName, String keyword, Map<String, Object> params) {
        // 简化示例:只演示 SQL 构造
        String sql = "SELECT * FROM " + tableName +
                     " WHERE " + columnName + " LIKE ? " +
                     "ORDER BY LENGTH(" + columnName + ") ASC " +
                     "LIMIT ?, ?";
        // 参数:%keyword%, offset, pageSize
        // JDBC 执行略
        return Collections.emptyList();
    }
}
 
// 辅助类
class Result<T> {
    T data;
    double score;
    Result(T data, double score) { this.data = data; this.score = score; }
}

6. 代码详细解读

FuzzySearchService 接口:定义内存和数据库两种模糊查询方法,统一调用入口;

FieldExtractor 接口:用于提取对象中待匹配的文本字段,实现与业务对象解耦;

PinyinConverter:基于 pinyin4j 将中文转换为全拼和首字母,辅助拼音匹配;

LevenshteinDistance:经典编辑距离算法及归一化相似度计算,用于度量字符串相似度;

InMemorySearchServiceImpl:

  • 预处理:去空格、转换小写、简繁体可扩展;
  • 多维度匹配:原文、全拼、首字母三种相似度计算,取最大值作为最终得分;
  • 阈值过滤:只保留相似度 >0.5 的候选结果;
  • 排序与分页:按得分降序并截取前 K;

Database 模式(示例):

  • 构造基于 LIKE '%keyword%' 的 SQL 粗筛;
  • 可结合 n-gram 索引与 Java 端补偿算法提升准确度;

7. 项目详细总结

本项目以纯 Java 实现了对中文数据的模糊查询,支持编辑距离、拼音全拼与首字母匹配,既可对内存列表进行高效检索,也可与关系型数据库结合使用。模块化设计易于扩展新分词器、相似度算法和繁体简体转换策略。

8. 项目常见问题及解答

Q1:为什么要同时使用原文、拼音和首字母匹配?

A1:中文用户输入习惯多样,有时输入汉字、有时输入拼音,或只输入首字母拼写缩写,多维度匹配可覆盖更多场景。

Q2:编辑距离算法性能如何优化?

A2:可采用 Ukkonen 提前剪枝、基于 Trie 的多模式匹配,或将热点查询转为规则正则,加速过滤。

Q3:数据库 LIKE 查询为何无法完全满足需求?

A3:LIKE 无法处理错别字与拼音匹配;同时大数据量时 %keyword% 会导致全表扫描。

9. 扩展方向与性能优化

分布式检索:使用 Elasticsearch/Solr 等引擎替代关系型数据库,利用倒排索引与分词插件;

多线程并行:内存模式下对大规模列表采用 Fork/Join 或并行流;

专用字典:集成行业领域同义词、专有名词词典,提升匹配准确率;

动态阈值:结合机器学习模型,根据用户行为动态调整相似度阈值和排序权重;

缓存与预热:对热点关键词结果做缓存,降低重复计算开销。

以上就是java实现中文模糊查询的示例代码的详细内容,更多关于java模糊查询的资料请关注脚本之家其它相关文章!

相关文章

  • 如何在java中使用SFTP协议安全的传输文件

    如何在java中使用SFTP协议安全的传输文件

    这篇文章主要介绍了如何在java中使用SFTP协议安全的传输文件,帮助大家更好的理解和使用JSch,感兴趣的朋友可以了解下
    2020-10-10
  • 亲手带你解决Debug Fastjson的安全漏洞

    亲手带你解决Debug Fastjson的安全漏洞

    这篇文章主要介绍了亲手带你解决Debug Fastjson的安全漏洞,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-06-06
  • 详解Android开发中Fragment的使用

    详解Android开发中Fragment的使用

    这篇文章主要介绍了详解Android开发中Fragment的使用,包括Java代码中调用Fragment的方法,需要的朋友可以参考下
    2015-07-07
  • MyBatisX逆向工程的实现示例

    MyBatisX逆向工程的实现示例

    本文主要介绍了MyBatisX逆向工程的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2025-05-05
  • Java实现字符串反转的常用方法小结

    Java实现字符串反转的常用方法小结

    在Java中,你可以使用多种方法来反转字符串,这篇文章主要为大家整理了几种常见的反转字符串的方法,感兴趣的小伙伴可以跟随小编一起学习一下
    2024-03-03
  • 一文带你真正理解Java中的内部类

    一文带你真正理解Java中的内部类

    不知道大家在平时的开发过程中或者源码里是否留意过内部类,那有思考过为什么要有内部类,内部类都有哪几种形式,本篇文章主要带领大家理解下这块内容
    2022-08-08
  • springboot在idea下debug调试热部署问题

    springboot在idea下debug调试热部署问题

    这篇文章主要介绍了springboot在idea下debug调试热部署问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-02-02
  • Spring RestTemplate远程调用过程

    Spring RestTemplate远程调用过程

    这篇文章主要介绍了Spring RestTemplate远程调用过程,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-11-11
  • Java获取当前时间方法总结

    Java获取当前时间方法总结

    本篇文章给大家整理了关于Java获取当前时间方法,以及相关代码分享,有需要的朋友测试参考下吧。
    2018-02-02
  • Spring Validation参数效验的各种使用姿势总结

    Spring Validation参数效验的各种使用姿势总结

    在实际项目中经常需要对前段传来的数据进行校验,下面这篇文章主要给大家介绍了关于Spring Validation参数效验的各种使用姿势,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2022-04-04

最新评论