Android 如何实现中文全拼、简拼搜索

一、需求背景

作为一款IM类的应用通常会存在联系人、群组等会话数据。用户要能够根据关键字快速查找到某个会话就显得比较重要,尤其是当用户业务较繁忙,本地存在大量联系人以及群时。

搜索关键字通常可能是直接中文、英文、中文全拼、中文简拼等。在我们的业务中,搜索业务大致经过四个阶段。

在这里插入图片描述

在本篇文章我们主要介绍如何在客户端构建搜索树,提高中文搜索速度,同时支持全拼、简拼。

二、使用搜索树进行搜索的技术方案

1、整体流程

获取数据源如所有的联系人账号以及名称,创建搜索树对象,循环遍历联系人集合,将参与搜索的联系人名称添加到搜索树中,同时也将关联数据一同传入,例如账号、联系人状态等,搜索到联系人后,这些关联数据用于构建显示的数据。

2、如何构建拼音搜索树

接口设计

搜索树接口

interface ISearchTree<T> {
    /**
     * 添加元素以及配置数据
     */
    fun pushElement(value: String, extraInfo: T)

    /**
     * 全拼搜索
     */
    fun searchQuanPin(keyword: String): List<SearchNode<T>>

    /**
     * 简拼搜索
     */
    fun searchJianPin(keyword: String): List<SearchNode<T>>
}

搜索树主要有三个方法,添加元素、全拼搜索、简拼搜索。

数据结构设计

拼音搜索节点

public class SearchNode<T> implements Comparable<SearchNode<T>> {

    /**
     * key值(连接所有父级节点key 可以得到当前Node的全拼/简拼)
     */
    public char key;

    /**
     * 子节点
     */
    public SortedList<SearchNode<T>> childNodes = new SortedList<>();

    /**
     * 父节点
     */
    public SearchNode<T> parentNode;

    /**
     * 节点携带的额外信息
     */
    public Set<T> valueSet = new HashSet<>();

    @Override
    public int compareTo(SearchNode<T> o) {
        if (isLetter(key) && isLetter(o.key)) {
            return compareChar(key, o.key);
        } else if (isLetter(key) && !isLetter(o.key)) {
            return -1;
        } else if (!isLetter(key) && isLetter(o.key)) {
            return 1;
        } else {
            return compareChar(key, o.key);
        }
    }

    private int compareChar(char char1, char char2) {
        if (char1 > char2) {
            return 1;
        } else if (char1 < char2) {
            return -1;
        } else {
            return 0;
        }
    }

    private boolean isLetter(char value) {
        return (value >= 'a' && value <= 'z') || (value > 'A' && value <= 'Z');
    }
}

可以看到每一个拼音搜索节点,包含对父节点的引用。同时包含子节点的集合。同时valueSet就是当拼音搜索以该节点结尾时,所保存的数据。

在SearchTree 对象中则保存着整个整条链路头部的集合:

public class SearchTree<T> {
    /**
     * 全拼搜索树顶级入口:为以首字符为key的节点集合
     */
    private SortedList<SearchNode<T>> quanpinTopNodeList;

    /**
     * 简拼搜索树顶级入口:为以首字符为key的节点集合
     */
    private SortedList<SearchNode<T>> jianpinTopNodeList;

    ...
}

构建搜索树的过程实际上就是将参与搜索的名称转为拼音(全拼、简拼)后,保存到quanpinTopNodeList、jianpinTopNodeList中的过程。

添加元素流程

在这里插入图片描述

上图简拼与全拼的流程相同。这里只给出全拼流程。

添加元素时,我们先将元素转为全拼拼音,如果元素本身是英文就不需要转了。查找第一个字符时,我们是从quanpinTopNodeList中查找是否存在。如果不存在,则新增一个搜索节点,将该搜索节点添加到quanpinTopNodeList中吗,同时将这个节点作为新的父节点。如果存在,说明在已经保存的顶层节点中,已经有这个节点,不再需要创建,获取该节点作为父节点。

再次遍历下一个字符时,就获取父节点中的子节点集合,判断字符是否在子节点集合中。如果存在说明子节点再次重复存在,依然不需要创建。如果不存在则说明又是一条新的链路,继续创建新的节点。然后继续更新父节点。

这样一次反复我们就可以创建了一条链路。

假设某个用户有以下几个联系人

  • 王明(wangming、wm)
  • 王名(wangming、wm)
  • 王明阳(wangmingyang、wmy)

那么搜索树构建完成之后,整体的结构应该是这样的:

在这里插入图片描述

  • 每个节点都是一个集合。在本案例中,quanpinTopNodeList集合中只存在一个字符就是"w"。其子节点集合仅有一个字符"a"。
  • 在王明、王名由于全拼的拼音相同,则在“wangming”中g结束之后保存了[王明、王名]数据。当用户输入的关键字为“wangming、王明、网名、亡命、王铭”等最终匹配到[王明、王名]。
  • 由于源数据中还存在[王明阳],其拼音为"wangmingyang",所以在构建搜索树时,前面的节点都不需要另外创建。用户在输入关键字“wangming、王明、网名、亡命、王铭”,最终也可以匹配到[王明阳]

假设用户还有一个联系人

  • 王一(wangyi、wy)

将[王一]保存到搜索树中,最终数据将是:

在字母g的子节点集合中,将存在两个节点,一个是原节点m,还有一个就是新的节点y。

这样当用户输入关键字“王”,可以匹配到[王一、王名、王明、王明阳]。

3、搜索

查找算法

当用户输入“一”字时,我们怎么去上面的链路中查找对应的节点呢?

SearchTree中除了两个SortedList,还有另外Map集合,用于存储a-z对应存在的节点

/**
 * 记录key为该字母的节点集合
 */
private ConcurrentHashMap<Character, Set<SearchNode<T>>> quanpinSubSet = new ConcurrentHashMap<>();

/**
 * 记录key为该字母的节点集合
 */
private ConcurrentHashMap<Character, Set<SearchNode<T>>> jianpinSubSet = new ConcurrentHashMap<>();

这里的Map key为字母a-z,value为节点的集合。

在这里插入图片描述

在上述创建新的节点时,我们会将节点也同步保存到Map。当用户输入关键字后,将关键字转为拼音后,先获取第一个字符,通过Map,获取到这个字符对应的所有的节点。以这些节点作为头部节点一次向下查找。

例如用户输入“一”,转为拼音后,全拼为"yi",我们通过“y”从Map中查找所有对应的节点,按照上面我们构建的搜索树,我们可以拿到两个节点。

两个y节点的父节点都是"g",一个节点的子节点集合中含有“a”,一个节点的子节点集合中含有"i"。通过遍历我们可以最终找到[王一]。

有序集合

SeachNode节点中,子节点的集合我们设计成了一个有序的集合。即节点会从a-z这样来排列。之所以这样设计,是为了方便查找子节点时,可以使用二分法提高速度。一个集合查找节省的时间比较少,如果用户输入的关键字比较长,例如"王明阳"转为拼音"wangmingyang",需要查找11次集合,当用户内容较多,搜索树比较复杂时,节省的时间就比较客观了。

三、总结

至此整个流程就介绍完了,构建搜索树目的主要满足了以下几点诉求:

  • 支持用户输入拼音搜索、简拼搜索。
  • 支持基础的模糊搜索(同音字、中段匹配)。
内存影响

由于创建了大量的节点对象,所以构建搜索树时需要考虑对内存的影响。在实际测试过程中,我们测试10000条联系人数据对内存的影响比较小。

搜索耗时

10000条联系人数据,通过搜索树搜索耗时基本是毫秒级别。

怎么支持英文

构建搜索树时,如果是中文就转拼音存储。如果是英文那么就直接存储。

搜索时如果关键字包含中文,那么就转拼音进行搜索。如果都是英文,那么就直接搜索。

如果你看到了这里,觉得文章写得不错就给个赞呗?
更多Android进阶指南 可以扫码 解锁更多Android进阶资料

在这里插入图片描述
敲代码不易,关注一下吧。ღ( ´・ᴗ・` )

  • 11
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值