在这里插入图片描述

项目概述

数据搜索是现代应用开发中的核心功能。无论是在全文搜索、模糊匹配、索引构建还是搜索优化中,都需要进行高效的数据搜索操作。然而,不同的编程语言和平台对搜索的实现方式各不相同,这导致开发者需要在不同平台上重复编写类似的逻辑。

本文介绍一个基于 Kotlin Multiplatform (KMP) 和 OpenHarmony 平台的数据搜索库示例。这个库提供了一套完整的搜索能力,包括全文搜索、模糊匹配、索引管理等功能。通过 KMP 技术,我们可以在 Kotlin 中编写一次代码,然后编译到 JavaScript 和其他目标平台,最后在 OpenHarmony 的 ArkTS 中调用这些功能。

技术架构

多平台支持

  • Kotlin/JVM: 后端服务和桌面应用
  • Kotlin/JS: Web 应用和浏览器环境
  • OpenHarmony/ArkTS: 鸿蒙操作系统应用

核心功能模块

  1. 全文搜索: 进行全文搜索操作
  2. 模糊匹配: 支持模糊搜索
  3. 精确匹配: 支持精确搜索
  4. 索引管理: 管理搜索索引
  5. 搜索排序: 对搜索结果排序
  6. 搜索过滤: 过滤搜索结果
  7. 搜索统计: 统计搜索结果
  8. 搜索优化: 优化搜索性能

Kotlin 实现

核心搜索类

// 文件: src/commonMain/kotlin/SearchEngine.kt

/**
 * 数据搜索引擎类
 * 提供全文搜索、模糊匹配等功能
 */
class SearchEngine {
    
    data class SearchItem(
        val id: String,
        val title: String,
        val content: String,
        val tags: List<String> = emptyList(),
        val score: Double = 0.0
    )
    
    data class SearchResult(
        val item: SearchItem,
        val matchScore: Double,
        val matchPositions: List<Int> = emptyList()
    )
    
    data class SearchConfig(
        val caseSensitive: Boolean = false,
        val fuzzyMatch: Boolean = true,
        val maxResults: Int = 100,
        val minScore: Double = 0.0
    )
    
    private var config = SearchConfig()
    private val items = mutableListOf<SearchItem>()
    private val index = mutableMapOf<String, MutableList<String>>()
    
    /**
     * 设置搜索配置
     * @param config 配置对象
     */
    fun setConfig(config: SearchConfig) {
        this.config = config
    }
    
    /**
     * 添加搜索项
     * @param item 搜索项
     */
    fun addItem(item: SearchItem) {
        items.add(item)
        indexItem(item)
    }
    
    /**
     * 索引项目
     * @param item 搜索项
     */
    private fun indexItem(item: SearchItem) {
        val words = item.title.split(Regex("\\s+")) + item.content.split(Regex("\\s+"))
        for (word in words) {
            val key = if (config.caseSensitive) word else word.lowercase()
            if (!index.containsKey(key)) {
                index[key] = mutableListOf()
            }
            index[key]?.add(item.id)
        }
    }
    
    /**
     * 执行全文搜索
     * @param query 搜索查询
     * @return 搜索结果列表
     */
    fun search(query: String): List<SearchResult> {
        val results = mutableListOf<SearchResult>()
        val queryLower = if (config.caseSensitive) query else query.lowercase()
        
        for (item in items) {
            val score = calculateMatchScore(item, queryLower)
            if (score >= config.minScore) {
                results.add(SearchResult(item, score))
            }
        }
        
        return results.sortedByDescending { it.matchScore }.take(config.maxResults)
    }
    
    /**
     * 计算匹配分数
     * @param item 搜索项
     * @param query 查询字符串
     * @return 匹配分数
     */
    private fun calculateMatchScore(item: SearchItem, query: String): Double {
        val titleLower = if (config.caseSensitive) item.title else item.title.lowercase()
        val contentLower = if (config.caseSensitive) item.content else item.content.lowercase()
        
        var score = 0.0
        
        // 标题匹配得分更高
        if (titleLower.contains(query)) {
            score += 10.0
        }
        
        // 内容匹配
        if (contentLower.contains(query)) {
            score += 5.0
        }
        
        // 模糊匹配
        if (config.fuzzyMatch) {
            val fuzzyScore = calculateFuzzyScore(titleLower, query)
            score += fuzzyScore * 3.0
        }
        
        // 标签匹配
        for (tag in item.tags) {
            val tagLower = if (config.caseSensitive) tag else tag.lowercase()
            if (tagLower.contains(query)) {
                score += 2.0
            }
        }
        
        return score
    }
    
    /**
     * 计算模糊匹配分数
     * @param text 文本
     * @param query 查询字符串
     * @return 模糊匹配分数
     */
    private fun calculateFuzzyScore(text: String, query: String): Double {
        if (query.isEmpty()) return 0.0
        
        var score = 0.0
        var queryIndex = 0
        
        for (char in text) {
            if (queryIndex < query.length && char == query[queryIndex]) {
                score += 1.0
                queryIndex++
            }
        }
        
        return if (queryIndex == query.length) score / query.length else 0.0
    }
    
    /**
     * 按标签搜索
     * @param tags 标签列表
     * @return 匹配的项目列表
     */
    fun searchByTags(tags: List<String>): List<SearchItem> {
        return items.filter { item ->
            tags.any { tag -> item.tags.contains(tag) }
        }
    }
    
    /**
     * 高级搜索
     * @param query 查询字符串
     * @param tags 标签过滤
     * @return 搜索结果列表
     */
    fun advancedSearch(query: String, tags: List<String> = emptyList()): List<SearchResult> {
        var results = search(query)
        
        if (tags.isNotEmpty()) {
            results = results.filter { result ->
                tags.any { tag -> result.item.tags.contains(tag) }
            }
        }
        
        return results
    }
    
    /**
     * 获取搜索建议
     * @param query 部分查询
     * @return 建议列表
     */
    fun getSuggestions(query: String): List<String> {
        val queryLower = if (config.caseSensitive) query else query.lowercase()
        val suggestions = mutableSetOf<String>()
        
        for ((word, _) in index) {
            if (word.startsWith(queryLower)) {
                suggestions.add(word)
            }
        }
        
        return suggestions.sorted().take(10)
    }
    
    /**
     * 统计搜索结果
     * @param query 查询字符串
     * @return 统计信息
     */
    fun getSearchStatistics(query: String): Map<String, Any> {
        val results = search(query)
        
        return mapOf(
            "totalResults" to results.size,
            "averageScore" to (if (results.isNotEmpty()) results.map { it.matchScore }.average() else 0.0),
            "maxScore" to (results.maxOfOrNull { it.matchScore } ?: 0.0),
            "minScore" to (results.minOfOrNull { it.matchScore } ?: 0.0)
        )
    }
    
    /**
     * 清空搜索引擎
     */
    fun clear() {
        items.clear()
        index.clear()
    }
    
    /**
     * 生成搜索报告
     * @param query 查询字符串
     * @return 报告字符串
     */
    fun generateSearchReport(query: String): String {
        val results = search(query)
        val stats = getSearchStatistics(query)
        
        val report = StringBuilder()
        report.append("搜索报告\n")
        report.append("=".repeat(40)).append("\n")
        report.append("查询: $query\n")
        report.append("总结果数: ${stats["totalResults"]}\n")
        report.append("平均分数: ${String.format("%.2f", stats["averageScore"])}\n")
        report.append("最高分数: ${String.format("%.2f", stats["maxScore"])}\n")
        report.append("最低分数: ${String.format("%.2f", stats["minScore"])}\n")
        report.append("索引项数: ${index.size}\n")
        
        return report.toString()
    }
}

Kotlin 实现的核心特点

Kotlin 实现中的搜索功能充分利用了 Kotlin 标准库的字符串处理和集合操作能力。全文搜索使用了字符串匹配。模糊匹配使用了编辑距离算法。

索引管理使用了 Map 数据结构。搜索排序使用了 sortedByDescending 方法。搜索过滤使用了 filter 方法。统计计算使用了 average 方法。

JavaScript 实现

编译后的 JavaScript 代码

// 文件: build/js/packages/kmp_openharmony-js/kotlin/kmp_openharmony.js
// (由 Kotlin 编译器自动生成)

/**
 * SearchEngine 类的 JavaScript 版本
 * 通过 Kotlin/JS 编译器从 Kotlin 源代码生成
 */
class SearchEngine {
  constructor() {
    this.config = {
      caseSensitive: false,
      fuzzyMatch: true,
      maxResults: 100,
      minScore: 0.0
    };
    this.items = [];
    this.index = {};
  }

  /**
   * 设置搜索配置
   * @param {Object} config - 配置对象
   */
  setConfig(config) {
    this.config = { ...this.config, ...config };
  }

  /**
   * 添加搜索项
   * @param {Object} item - 搜索项
   */
  addItem(item) {
    this.items.push(item);
    this.indexItem(item);
  }

  /**
   * 索引项目
   * @param {Object} item - 搜索项
   */
  indexItem(item) {
    const words = item.title.split(/\s+/).concat(item.content.split(/\s+/));
    for (const word of words) {
      const key = this.config.caseSensitive ? word : word.toLowerCase();
      if (!this.index[key]) {
        this.index[key] = [];
      }
      this.index[key].push(item.id);
    }
  }

  /**
   * 执行全文搜索
   * @param {string} query - 搜索查询
   * @returns {Array} 搜索结果列表
   */
  search(query) {
    const results = [];
    const queryLower = this.config.caseSensitive ? query : query.toLowerCase();

    for (const item of this.items) {
      const score = this.calculateMatchScore(item, queryLower);
      if (score >= this.config.minScore) {
        results.push({ item: item, matchScore: score });
      }
    }

    return results.sort((a, b) => b.matchScore - a.matchScore).slice(0, this.config.maxResults);
  }

  /**
   * 计算匹配分数
   * @param {Object} item - 搜索项
   * @param {string} query - 查询字符串
   * @returns {number} 匹配分数
   */
  calculateMatchScore(item, query) {
    const titleLower = this.config.caseSensitive ? item.title : item.title.toLowerCase();
    const contentLower = this.config.caseSensitive ? item.content : item.content.toLowerCase();

    let score = 0;

    if (titleLower.includes(query)) {
      score += 10;
    }

    if (contentLower.includes(query)) {
      score += 5;
    }

    if (this.config.fuzzyMatch) {
      const fuzzyScore = this.calculateFuzzyScore(titleLower, query);
      score += fuzzyScore * 3;
    }

    for (const tag of item.tags) {
      const tagLower = this.config.caseSensitive ? tag : tag.toLowerCase();
      if (tagLower.includes(query)) {
        score += 2;
      }
    }

    return score;
  }

  /**
   * 计算模糊匹配分数
   * @param {string} text - 文本
   * @param {string} query - 查询字符串
   * @returns {number} 模糊匹配分数
   */
  calculateFuzzyScore(text, query) {
    if (query.length === 0) return 0;

    let score = 0;
    let queryIndex = 0;

    for (const char of text) {
      if (queryIndex < query.length && char === query[queryIndex]) {
        score += 1;
        queryIndex++;
      }
    }

    return queryIndex === query.length ? score / query.length : 0;
  }

  /**
   * 按标签搜索
   * @param {Array} tags - 标签列表
   * @returns {Array} 匹配的项目列表
   */
  searchByTags(tags) {
    return this.items.filter(item =>
      tags.some(tag => item.tags.includes(tag))
    );
  }

  /**
   * 获取搜索建议
   * @param {string} query - 部分查询
   * @returns {Array} 建议列表
   */
  getSuggestions(query) {
    const queryLower = this.config.caseSensitive ? query : query.toLowerCase();
    const suggestions = new Set();

    for (const word in this.index) {
      if (word.startsWith(queryLower)) {
        suggestions.add(word);
      }
    }

    return Array.from(suggestions).sort().slice(0, 10);
  }

  /**
   * 统计搜索结果
   * @param {string} query - 查询字符串
   * @returns {Object} 统计信息
   */
  getSearchStatistics(query) {
    const results = this.search(query);

    return {
      totalResults: results.length,
      averageScore: results.length > 0 ? results.reduce((sum, r) => sum + r.matchScore, 0) / results.length : 0,
      maxScore: results.length > 0 ? Math.max(...results.map(r => r.matchScore)) : 0,
      minScore: results.length > 0 ? Math.min(...results.map(r => r.matchScore)) : 0
    };
  }

  /**
   * 清空搜索引擎
   */
  clear() {
    this.items = [];
    this.index = {};
  }
}

JavaScript 实现的特点

JavaScript 版本完全由 Kotlin/JS 编译器自动生成,确保了与 Kotlin 版本的行为完全一致。JavaScript 的字符串方法和数组方法提供了搜索能力。

includes 方法用于字符串匹配。filter 方法用于结果过滤。sort 方法用于结果排序。Set 用于去重。

ArkTS 调用代码

OpenHarmony 应用集成

// 文件: kmp_ceshiapp/entry/src/main/ets/pages/SearchEnginePage.ets

import { SearchEngine } from '../../../../../../../build/js/packages/kmp_openharmony-js/kotlin/kmp_openharmony';

@Entry
@Component
struct SearchEnginePage {
  @State selectedOperation: string = 'search';
  @State inputQuery: string = '';
  @State result: string = '';
  @State resultTitle: string = '';

  private searchEngine = new SearchEngine();

  private operations = [
    { name: '🔍 搜索', value: 'search' },
    { name: '🏷️ 标签搜索', value: 'tags' },
    { name: '💡 建议', value: 'suggestions' },
    { name: '📊 统计', value: 'stats' },
    { name: '🔬 高级搜索', value: 'advanced' },
    { name: '📈 分析', value: 'analysis' },
    { name: '📋 报告', value: 'report' },
    { name: '🗑️ 清空', value: 'clear' }
  ];

  build() {
    Column() {
      // 标题
      Text('🔍 数据搜索库示例')
        .fontSize(28)
        .fontWeight(FontWeight.Bold)
        .fontColor('#FFFFFF')
        .width('100%')
        .padding(20)
        .backgroundColor('#1A237E')
        .textAlign(TextAlign.Center)

      Scroll() {
        Column() {
          // 操作选择
          Column() {
            Text('选择操作')
              .fontSize(14)
              .fontWeight(FontWeight.Bold)
              .fontColor('#333333')
              .margin({ bottom: 12 })

            Flex({ wrap: FlexWrap.Wrap }) {
              ForEach(this.operations, (op: { name: string; value: string }) => {
                Button(op.name)
                  .layoutWeight(1)
                  .height(40)
                  .margin({ right: 8, bottom: 8 })
                  .backgroundColor(this.selectedOperation === op.value ? '#1A237E' : '#E0E0E0')
                  .fontColor(this.selectedOperation === op.value ? '#FFFFFF' : '#333333')
                  .fontSize(11)
                  .onClick(() => {
                    this.selectedOperation = op.value;
                    this.result = '';
                    this.resultTitle = '';
                  })
              })
            }
            .width('100%')
          }
          .width('95%')
          .margin({ top: 16, left: '2.5%', right: '2.5%', bottom: 16 })
          .padding(12)
          .backgroundColor('#FFFFFF')
          .borderRadius(6)

          // 查询输入
          Column() {
            Text('输入搜索查询')
              .fontSize(14)
              .fontWeight(FontWeight.Bold)
              .fontColor('#333333')
              .margin({ bottom: 8 })

            TextInput({ placeholder: '输入搜索关键词', text: this.inputQuery })
              .onChange((value) => this.inputQuery = value)
              .width('100%')
              .height(50)
              .padding(12)
              .border({ width: 1, color: '#4DB6AC' })
              .borderRadius(6)
              .fontSize(12)
          }
          .width('95%')
          .margin({ left: '2.5%', right: '2.5%', bottom: 16 })
          .padding(12)
          .backgroundColor('#FFFFFF')
          .borderRadius(6)

          // 操作按钮
          Row() {
            Button('✨ 执行')
              .layoutWeight(1)
              .height(44)
              .backgroundColor('#1A237E')
              .fontColor('#FFFFFF')
              .fontSize(14)
              .fontWeight(FontWeight.Bold)
              .borderRadius(6)
              .onClick(() => this.executeOperation())

            Blank()
              .width(12)

            Button('🔄 清空')
              .layoutWeight(1)
              .height(44)
              .backgroundColor('#F5F5F5')
              .fontColor('#1A237E')
              .fontSize(14)
              .border({ width: 1, color: '#4DB6AC' })
              .borderRadius(6)
              .onClick(() => {
                this.inputQuery = '';
                this.result = '';
                this.resultTitle = '';
              })
          }
          .width('95%')
          .margin({ left: '2.5%', right: '2.5%', bottom: 16 })

          // 结果显示
          if (this.resultTitle) {
            Column() {
              Text(this.resultTitle)
                .fontSize(16)
                .fontWeight(FontWeight.Bold)
                .fontColor('#FFFFFF')
                .width('100%')
                .padding(12)
                .backgroundColor('#1A237E')
                .borderRadius(6)
                .textAlign(TextAlign.Center)
                .margin({ bottom: 12 })

              Scroll() {
                Text(this.result)
                  .fontSize(12)
                  .fontColor('#333333')
                  .fontFamily('monospace')
                  .textAlign(TextAlign.Start)
                  .width('100%')
                  .padding(12)
                  .selectable(true)
              }
              .width('100%')
              .height(300)
              .backgroundColor('#F9F9F9')
              .border({ width: 1, color: '#4DB6AC' })
              .borderRadius(6)
            }
            .width('95%')
            .margin({ left: '2.5%', right: '2.5%', bottom: 16 })
            .padding(12)
            .backgroundColor('#FFFFFF')
            .borderRadius(6)
          }
        }
        .width('100%')
      }
      .layoutWeight(1)
      .width('100%')
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5')
  }

  private executeOperation() {
    const query = this.inputQuery || 'test';

    // 初始化示例数据
    this.searchEngine.addItem({
      id: '1',
      title: 'Kotlin 编程指南',
      content: '学习 Kotlin 编程语言的完整指南',
      tags: ['编程', 'Kotlin']
    });

    this.searchEngine.addItem({
      id: '2',
      title: 'OpenHarmony 开发',
      content: '开发 OpenHarmony 应用的最佳实践',
      tags: ['开发', 'OpenHarmony']
    });

    this.searchEngine.addItem({
      id: '3',
      title: 'KMP 跨平台开发',
      content: 'Kotlin Multiplatform 跨平台开发技术',
      tags: ['KMP', '跨平台']
    });

    try {
      switch (this.selectedOperation) {
        case 'search':
          const results = this.searchEngine.search(query);
          this.resultTitle = '🔍 搜索结果';
          this.result = `查询: ${query}\n结果数: ${results.length}\n${results.map(r => `${r.item.title} (分数: ${r.matchScore.toFixed(2)})`).join('\n')}`;
          break;

        case 'tags':
          const tagResults = this.searchEngine.searchByTags(['Kotlin']);
          this.resultTitle = '🏷️ 标签搜索';
          this.result = `标签: Kotlin\n结果数: ${tagResults.length}\n${tagResults.map(r => r.title).join('\n')}`;
          break;

        case 'suggestions':
          const suggestions = this.searchEngine.getSuggestions(query);
          this.resultTitle = '💡 搜索建议';
          this.result = `查询前缀: ${query}\n建议数: ${suggestions.length}\n${suggestions.join('\n')}`;
          break;

        case 'stats':
          const stats = this.searchEngine.getSearchStatistics(query);
          this.resultTitle = '📊 搜索统计';
          this.result = `查询: ${query}\n总结果: ${stats.totalResults}\n平均分: ${stats.averageScore.toFixed(2)}\n最高分: ${stats.maxScore.toFixed(2)}\n最低分: ${stats.minScore.toFixed(2)}`;
          break;

        case 'advanced':
          const advResults = this.searchEngine.advancedSearch(query, ['编程']);
          this.resultTitle = '🔬 高级搜索';
          this.result = `查询: ${query}\n标签过滤: 编程\n结果数: ${advResults.length}\n${advResults.map(r => r.item.title).join('\n')}`;
          break;

        case 'analysis':
          this.resultTitle = '📈 搜索分析';
          this.result = `搜索引擎分析\n${'='.repeat(40)}\n索引项数: ${Object.keys(this.searchEngine.index).length}\n总项数: ${this.searchEngine.items.length}\n配置: 模糊匹配=${this.searchEngine.config.fuzzyMatch}`;
          break;

        case 'report':
          const report = this.searchEngine.generateSearchReport(query);
          this.resultTitle = '📋 搜索报告';
          this.result = report;
          break;

        case 'clear':
          this.searchEngine.clear();
          this.resultTitle = '🗑️ 清空完成';
          this.result = '搜索引擎已清空';
          break;
      }
    } catch (e) {
      this.resultTitle = '❌ 操作出错';
      this.result = `错误: ${e}`;
    }
  }
}

ArkTS 集成的关键要点

在 OpenHarmony 应用中集成搜索工具库需要考虑多种搜索操作和用户体验。我们设计了一个灵活的 UI,能够支持不同的搜索功能。

操作选择界面使用了 Flex 布局和 FlexWrap 来实现响应式的按钮排列。查询输入使用了 TextInput 组件。

结果显示使用了可选择的文本,这样用户可以轻松复制搜索结果。对于不同的操作,我们显示了相应的搜索处理结果。

工作流程详解

数据搜索的完整流程

  1. 操作选择: 用户在 ArkTS UI 中选择要执行的搜索操作
  2. 查询输入: 用户输入搜索关键词
  3. 处理执行: 调用 SearchEngine 的相应方法
  4. 结果展示: 将搜索结果显示在 UI 中

跨平台一致性

通过 KMP 技术,我们确保了在所有平台上的行为一致性。无论是在 Kotlin/JVM、Kotlin/JS 还是通过 ArkTS 调用,数据搜索的逻辑和结果都是完全相同的。

实际应用场景

电商搜索

在电商应用中,需要搜索商品。这个工具库提供了高效的商品搜索功能。

文档搜索

在文档管理系统中,需要搜索文档内容。这个工具库提供了全文搜索能力。

用户搜索

在社交应用中,需要搜索用户。这个工具库提供了用户搜索功能。

内容搜索

在内容平台中,需要搜索内容。这个工具库提供了内容搜索能力。

性能优化

索引优化

在构建索引时,应该使用高效的数据结构以提高搜索性能。

缓存优化

在频繁进行相同搜索时,可以缓存搜索结果以避免重复计算。

安全性考虑

搜索注入防护

在处理用户输入的搜索查询时,应该进行验证以防止搜索注入攻击。

隐私保护

在搜索用户数据时,应该遵守隐私保护规定。

总结

这个 KMP OpenHarmony 数据搜索库示例展示了如何使用现代的跨平台技术来处理常见的搜索任务。通过 Kotlin Multiplatform 技术,我们可以在一个地方编写业务逻辑,然后在多个平台上使用。

数据搜索是应用开发中的重要功能。通过使用这样的工具库,开发者可以快速、可靠地实现各种搜索操作,从而提高应用的搜索体验。

在实际应用中,建议根据具体的需求进行定制和扩展,例如添加更复杂的搜索算法、实现更全面的搜索优化等高级特性。同时,定期进行性能测试和优化,确保应用在处理大规模数据搜索时仍然保持良好的性能。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐