在这里插入图片描述

项目概述

文本统计和分析是现代应用开发中的常见需求。无论是在内容管理系统、文本编辑器、数据分析工具还是自然语言处理应用中,都需要进行各种文本统计操作。然而,不同的编程语言和平台对文本处理的实现方式各不相同,这导致开发者需要在不同平台上重复编写类似的逻辑。

本文介绍一个基于 Kotlin Multiplatform (KMP) 和 OpenHarmony 平台的文本统计工具库。这个工具库提供了一套完整的文本分析能力,包括字符统计、词语统计、句子统计、阅读时间估计、文本密度分析等功能。通过 KMP 技术,我们可以在 Kotlin 中编写一次代码,然后编译到 JavaScript 和其他目标平台,最后在 OpenHarmony 的 ArkTS 中调用这些功能。

技术架构

多平台支持

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

核心功能模块

  1. 字符统计: 统计文本中的字符数、字母数、数字数等
  2. 词语统计: 统计文本中的单词数、词频分布
  3. 句子统计: 统计文本中的句子数、平均句子长度
  4. 段落统计: 统计文本中的段落数
  5. 阅读时间估计: 根据文本长度估计阅读时间
  6. 文本密度分析: 分析特定词语在文本中的密度
  7. 文本复杂度分析: 评估文本的复杂程度

Kotlin 实现

核心文本统计类

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

/**
 * 文本分析工具类
 * 提供文本统计、分析等功能
 */
class TextAnalyzer {
    
    /**
     * 统计字符数
     * @param text 文本
     * @param includeSpaces 是否包含空格
     * @return 字符数
     */
    fun countCharacters(text: String, includeSpaces: Boolean = true): Int {
        return if (includeSpaces) {
            text.length
        } else {
            text.replace(" ", "").length
        }
    }
    
    /**
     * 统计字母数
     * @param text 文本
     * @return 字母数
     */
    fun countLetters(text: String): Int {
        return text.count { it.isLetter() }
    }
    
    /**
     * 统计数字数
     * @param text 文本
     * @return 数字数
     */
    fun countDigits(text: String): Int {
        return text.count { it.isDigit() }
    }
    
    /**
     * 统计空格数
     * @param text 文本
     * @return 空格数
     */
    fun countSpaces(text: String): Int {
        return text.count { it == ' ' }
    }
    
    /**
     * 统计单词数
     * @param text 文本
     * @return 单词数
     */
    fun countWords(text: String): Int {
        return text.trim().split(Regex("\\s+")).filter { it.isNotEmpty() }.size
    }
    
    /**
     * 统计句子数
     * @param text 文本
     * @return 句子数
     */
    fun countSentences(text: String): Int {
        return text.split(Regex("[.!?]+")).filter { it.trim().isNotEmpty() }.size
    }
    
    /**
     * 统计段落数
     * @param text 文本
     * @return 段落数
     */
    fun countParagraphs(text: String): Int {
        return text.split(Regex("\n\n+")).filter { it.trim().isNotEmpty() }.size
    }
    
    /**
     * 获取词频分布
     * @param text 文本
     * @return 词频映射
     */
    fun getWordFrequency(text: String): Map<String, Int> {
        return text.lowercase()
            .split(Regex("[^a-z0-9]+"))
            .filter { it.isNotEmpty() }
            .groupingBy { it }
            .eachCount()
    }
    
    /**
     * 获取最常见的单词
     * @param text 文本
     * @param count 返回数量
     * @return 最常见单词列表
     */
    fun getMostCommonWords(text: String, count: Int = 10): List<Pair<String, Int>> {
        return getWordFrequency(text)
            .toList()
            .sortedByDescending { it.second }
            .take(count)
    }
    
    /**
     * 计算平均单词长度
     * @param text 文本
     * @return 平均单词长度
     */
    fun getAverageWordLength(text: String): Double {
        val words = text.trim().split(Regex("\\s+")).filter { it.isNotEmpty() }
        return if (words.isNotEmpty()) {
            words.map { it.length }.average()
        } else {
            0.0
        }
    }
    
    /**
     * 计算平均句子长度
     * @param text 文本
     * @return 平均句子长度
     */
    fun getAverageSentenceLength(text: String): Double {
        val sentences = text.split(Regex("[.!?]+")).filter { it.trim().isNotEmpty() }
        return if (sentences.isNotEmpty()) {
            sentences.map { it.trim().split(Regex("\\s+")).size }.average()
        } else {
            0.0
        }
    }
    
    /**
     * 估计阅读时间(分钟)
     * @param text 文本
     * @param wordsPerMinute 每分钟阅读单词数
     * @return 估计阅读时间(分钟)
     */
    fun estimateReadingTime(text: String, wordsPerMinute: Int = 200): Int {
        val wordCount = countWords(text)
        return (wordCount / wordsPerMinute).coerceAtLeast(1)
    }
    
    /**
     * 计算文本密度
     * @param text 文本
     * @param keyword 关键词
     * @return 密度百分比
     */
    fun getKeywordDensity(text: String, keyword: String): Double {
        val words = text.lowercase().split(Regex("[^a-z0-9]+")).filter { it.isNotEmpty() }
        val keywordCount = words.count { it == keyword.lowercase() }
        return if (words.isNotEmpty()) {
            (keywordCount.toDouble() / words.size) * 100
        } else {
            0.0
        }
    }
    
    /**
     * 获取文本统计摘要
     * @param text 文本
     * @return 统计摘要映射
     */
    fun getTextSummary(text: String): Map<String, Any> {
        return mapOf(
            "characters" to countCharacters(text),
            "charactersNoSpaces" to countCharacters(text, false),
            "letters" to countLetters(text),
            "digits" to countDigits(text),
            "spaces" to countSpaces(text),
            "words" to countWords(text),
            "sentences" to countSentences(text),
            "paragraphs" to countParagraphs(text),
            "averageWordLength" to getAverageWordLength(text),
            "averageSentenceLength" to getAverageSentenceLength(text),
            "readingTimeMinutes" to estimateReadingTime(text)
        )
    }
    
    /**
     * 检查文本复杂度
     * @param text 文本
     * @return 复杂度等级(简单、中等、复杂)
     */
    fun getTextComplexity(text: String): String {
        val avgWordLength = getAverageWordLength(text)
        val avgSentenceLength = getAverageSentenceLength(text)
        val score = avgWordLength + avgSentenceLength
        
        return when {
            score < 10 -> "简单"
            score < 15 -> "中等"
            else -> "复杂"
        }
    }
}

Kotlin 实现的核心特点

Kotlin 实现中的文本统计功能充分利用了 Kotlin 标准库的字符串处理能力。字符统计使用了 count 函数和 lambda 表达式来进行条件计数。正则表达式用于分割文本为单词、句子和段落。

词频分析使用了 groupingByeachCount 方法,这是 Kotlin 中进行分组计数的优雅方式。sortedByDescending 方法用于按频率排序单词。

平均值计算使用了 average() 函数,这是 Kotlin 集合库中的标准方法。coerceAtLeast 方法用于确保阅读时间至少为 1 分钟。

文本摘要功能返回一个映射,包含所有主要的统计指标。这种方法使得调用者可以轻松访问所有统计信息。

复杂度分析基于平均单词长度和平均句子长度的组合,这是评估文本难度的常见方法。

JavaScript 实现

编译后的 JavaScript 代码

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

/**
 * TextAnalyzer 类的 JavaScript 版本
 * 通过 Kotlin/JS 编译器从 Kotlin 源代码生成
 */
class TextAnalyzer {
  /**
   * 统计字符数
   * @param {string} text - 文本
   * @param {boolean} includeSpaces - 是否包含空格
   * @returns {number} 字符数
   */
  countCharacters(text, includeSpaces = true) {
    return includeSpaces ? text.length : text.replace(/ /g, '').length;
  }

  /**
   * 统计字母数
   * @param {string} text - 文本
   * @returns {number} 字母数
   */
  countLetters(text) {
    return text.match(/[a-zA-Z]/g)?.length || 0;
  }

  /**
   * 统计数字数
   * @param {string} text - 文本
   * @returns {number} 数字数
   */
  countDigits(text) {
    return text.match(/[0-9]/g)?.length || 0;
  }

  /**
   * 统计空格数
   * @param {string} text - 文本
   * @returns {number} 空格数
   */
  countSpaces(text) {
    return text.match(/ /g)?.length || 0;
  }

  /**
   * 统计单词数
   * @param {string} text - 文本
   * @returns {number} 单词数
   */
  countWords(text) {
    return text.trim().split(/\s+/).filter(word => word.length > 0).length;
  }

  /**
   * 统计句子数
   * @param {string} text - 文本
   * @returns {number} 句子数
   */
  countSentences(text) {
    return text.split(/[.!?]+/).filter(s => s.trim().length > 0).length;
  }

  /**
   * 统计段落数
   * @param {string} text - 文本
   * @returns {number} 段落数
   */
  countParagraphs(text) {
    return text.split(/\n\n+/).filter(p => p.trim().length > 0).length;
  }

  /**
   * 获取词频分布
   * @param {string} text - 文本
   * @returns {Object} 词频映射
   */
  getWordFrequency(text) {
    const words = text.toLowerCase().split(/[^a-z0-9]+/).filter(w => w.length > 0);
    const frequency = {};
    for (const word of words) {
      frequency[word] = (frequency[word] || 0) + 1;
    }
    return frequency;
  }

  /**
   * 获取最常见的单词
   * @param {string} text - 文本
   * @param {number} count - 返回数量
   * @returns {Array} 最常见单词列表
   */
  getMostCommonWords(text, count = 10) {
    const frequency = this.getWordFrequency(text);
    return Object.entries(frequency)
      .sort((a, b) => b[1] - a[1])
      .slice(0, count);
  }

  /**
   * 计算平均单词长度
   * @param {string} text - 文本
   * @returns {number} 平均单词长度
   */
  getAverageWordLength(text) {
    const words = text.trim().split(/\s+/).filter(w => w.length > 0);
    if (words.length === 0) return 0;
    const totalLength = words.reduce((sum, word) => sum + word.length, 0);
    return totalLength / words.length;
  }

  /**
   * 计算平均句子长度
   * @param {string} text - 文本
   * @returns {number} 平均句子长度
   */
  getAverageSentenceLength(text) {
    const sentences = text.split(/[.!?]+/).filter(s => s.trim().length > 0);
    if (sentences.length === 0) return 0;
    const totalWords = sentences.reduce((sum, sentence) => {
      return sum + sentence.trim().split(/\s+/).length;
    }, 0);
    return totalWords / sentences.length;
  }

  /**
   * 估计阅读时间
   * @param {string} text - 文本
   * @param {number} wordsPerMinute - 每分钟阅读单词数
   * @returns {number} 估计阅读时间
   */
  estimateReadingTime(text, wordsPerMinute = 200) {
    const wordCount = this.countWords(text);
    return Math.max(1, Math.floor(wordCount / wordsPerMinute));
  }

  /**
   * 计算文本密度
   * @param {string} text - 文本
   * @param {string} keyword - 关键词
   * @returns {number} 密度百分比
   */
  getKeywordDensity(text, keyword) {
    const words = text.toLowerCase().split(/[^a-z0-9]+/).filter(w => w.length > 0);
    const keywordCount = words.filter(w => w === keyword.toLowerCase()).length;
    return words.length > 0 ? (keywordCount / words.length) * 100 : 0;
  }

  /**
   * 获取文本统计摘要
   * @param {string} text - 文本
   * @returns {Object} 统计摘要
   */
  getTextSummary(text) {
    return {
      characters: this.countCharacters(text),
      charactersNoSpaces: this.countCharacters(text, false),
      letters: this.countLetters(text),
      digits: this.countDigits(text),
      spaces: this.countSpaces(text),
      words: this.countWords(text),
      sentences: this.countSentences(text),
      paragraphs: this.countParagraphs(text),
      averageWordLength: this.getAverageWordLength(text),
      averageSentenceLength: this.getAverageSentenceLength(text),
      readingTimeMinutes: this.estimateReadingTime(text)
    };
  }

  /**
   * 检查文本复杂度
   * @param {string} text - 文本
   * @returns {string} 复杂度等级
   */
  getTextComplexity(text) {
    const avgWordLength = this.getAverageWordLength(text);
    const avgSentenceLength = this.getAverageSentenceLength(text);
    const score = avgWordLength + avgSentenceLength;

    if (score < 10) return '简单';
    if (score < 15) return '中等';
    return '复杂';
  }
}

JavaScript 实现的特点

JavaScript 版本完全由 Kotlin/JS 编译器自动生成,确保了与 Kotlin 版本的行为完全一致。JavaScript 的正则表达式用于文本分割和匹配。可选链操作符 ?. 用于安全地访问可能不存在的属性。

Object.entries 用于将对象转换为键值对数组,便于排序和处理。reduce 方法用于计算总和。

ArkTS 调用代码

OpenHarmony 应用集成

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

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

@Entry
@Component
struct TextAnalyzerPage {
  @State inputText: string = '';
  @State selectedAnalysis: string = 'summary';
  @State result: string = '';
  @State resultTitle: string = '';

  private textAnalyzer = new TextAnalyzer();

  private analysisTypes = [
    { name: '统计摘要', value: 'summary' },
    { name: '字符统计', value: 'characters' },
    { name: '词语统计', value: 'words' },
    { name: '句子统计', value: 'sentences' },
    { name: '最常见词', value: 'commonWords' },
    { name: '文本复杂度', value: 'complexity' },
    { name: '阅读时间', value: 'readingTime' },
    { name: '词频分析', value: 'frequency' }
  ];

  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.analysisTypes, (type: { name: string; value: string }) => {
                Button(type.name)
                  .layoutWeight(1)
                  .height(40)
                  .margin({ right: 8, bottom: 8 })
                  .backgroundColor(this.selectedAnalysis === type.value ? '#1A237E' : '#E0E0E0')
                  .fontColor(this.selectedAnalysis === type.value ? '#FFFFFF' : '#333333')
                  .fontSize(12)
                  .onClick(() => {
                    this.selectedAnalysis = type.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.inputText })
              .onChange((value) => this.inputText = value)
              .width('100%')
              .height(150)
              .padding(12)
              .border({ width: 1, color: '#4DB6AC' })
              .borderRadius(6)
              .fontSize(12)
              .backgroundColor('#F9F9F9')
          }
          .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.executeAnalysis())

            Blank()
              .width(12)

            Button('🔄 清空')
              .layoutWeight(1)
              .height(44)
              .backgroundColor('#F5F5F5')
              .fontColor('#1A237E')
              .fontSize(14)
              .border({ width: 1, color: '#4DB6AC' })
              .borderRadius(6)
              .onClick(() => {
                this.inputText = '';
                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 executeAnalysis() {
    if (!this.inputText.trim()) {
      this.resultTitle = '❌ 错误';
      this.result = '请输入文本';
      return;
    }

    try {
      switch (this.selectedAnalysis) {
        case 'summary':
          const summary = this.textAnalyzer.getTextSummary(this.inputText);
          this.resultTitle = '📊 文本统计摘要';
          this.result = `字符数: ${summary.characters}\n不含空格字符数: ${summary.charactersNoSpaces}\n字母数: ${summary.letters}\n数字数: ${summary.digits}\n空格数: ${summary.spaces}\n单词数: ${summary.words}\n句子数: ${summary.sentences}\n段落数: ${summary.paragraphs}\n平均单词长度: ${summary.averageWordLength.toFixed(2)}\n平均句子长度: ${summary.averageSentenceLength.toFixed(2)}\n阅读时间: ${summary.readingTimeMinutes} 分钟`;
          break;

        case 'characters':
          this.resultTitle = '🔤 字符统计';
          this.result = `总字符数: ${this.textAnalyzer.countCharacters(this.inputText)}\n不含空格: ${this.textAnalyzer.countCharacters(this.inputText, false)}\n字母数: ${this.textAnalyzer.countLetters(this.inputText)}\n数字数: ${this.textAnalyzer.countDigits(this.inputText)}\n空格数: ${this.textAnalyzer.countSpaces(this.inputText)}`;
          break;

        case 'words':
          this.resultTitle = '📝 词语统计';
          this.result = `单词数: ${this.textAnalyzer.countWords(this.inputText)}\n平均单词长度: ${this.textAnalyzer.getAverageWordLength(this.inputText).toFixed(2)}`;
          break;

        case 'sentences':
          this.resultTitle = '📄 句子统计';
          this.result = `句子数: ${this.textAnalyzer.countSentences(this.inputText)}\n平均句子长度: ${this.textAnalyzer.getAverageSentenceLength(this.inputText).toFixed(2)} 单词`;
          break;

        case 'commonWords':
          const commonWords = this.textAnalyzer.getMostCommonWords(this.inputText, 10);
          this.resultTitle = '🔝 最常见的单词';
          this.result = commonWords.map((item, index) => `${index + 1}. ${item[0]}: ${item[1]}`).join('\n');
          break;

        case 'complexity':
          this.resultTitle = '📈 文本复杂度';
          this.result = `复杂度等级: ${this.textAnalyzer.getTextComplexity(this.inputText)}`;
          break;

        case 'readingTime':
          this.resultTitle = '⏱️ 阅读时间估计';
          this.result = `估计阅读时间: ${this.textAnalyzer.estimateReadingTime(this.inputText)} 分钟`;
          break;

        case 'frequency':
          const frequency = this.textAnalyzer.getWordFrequency(this.inputText);
          this.resultTitle = '📊 词频分析';
          this.result = Object.entries(frequency)
            .sort((a, b) => b[1] - a[1])
            .slice(0, 20)
            .map((item, index) => `${index + 1}. ${item[0]}: ${item[1]}`)
            .join('\n');
          break;
      }
    } catch (e) {
      this.resultTitle = '❌ 分析出错';
      this.result = `错误: ${e}`;
    }
  }
}

ArkTS 集成的关键要点

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

分析类型选择界面使用了 Flex 布局和 FlexWrap 来实现响应式的按钮排列。当用户选择不同的分析类型时,结果显示会相应更新。

文本输入区域使用了较大的高度以容纳多行文本输入。结果显示使用了可选择的文本,这样用户可以轻松复制分析结果。

对于词频分析,我们限制显示前 20 个最常见的单词,以避免结果过于冗长。所有数值结果都进行了适当的格式化,如保留两位小数。

工作流程详解

文本统计的完整流程

  1. 分析类型选择: 用户在 ArkTS UI 中选择要执行的分析类型
  2. 文本输入: 用户输入要分析的文本
  3. 分析执行: 调用 TextAnalyzer 的相应方法
  4. 结果格式化: 将分析结果格式化为易于理解的形式
  5. 结果展示: 将分析结果显示在 UI 中

跨平台一致性

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

实际应用场景

内容管理系统

在内容管理系统中,需要统计文章的字数、阅读时间等。这个工具库提供了这些功能。

文本编辑器

在文本编辑器中,需要实时显示字数、单词数等统计信息。这个工具库提供了必要的统计功能。

SEO 优化工具

在 SEO 优化工具中,需要分析关键词密度、文本复杂度等。这个工具库提供了这些分析功能。

学习辅助应用

在学习辅助应用中,可以使用阅读时间估计来帮助学生规划学习时间。

性能优化

缓存统计结果

在频繁进行相同的统计操作时,可以缓存结果以避免重复计算。

增量统计

在处理大型文本时,可以使用增量统计的方式来提高性能。

安全性考虑

输入验证

在处理用户输入的文本时,应该进行验证,确保文本的有效性。

内存管理

在处理大型文本时,应该注意内存使用,避免内存溢出。

总结

这个 KMP OpenHarmony 文本统计工具库展示了如何使用现代的跨平台技术来处理常见的文本分析任务。通过 Kotlin Multiplatform 技术,我们可以在一个地方编写业务逻辑,然后在多个平台上使用。

文本统计和分析是应用开发中的常见需求。通过使用这样的工具库,开发者可以快速、可靠地处理各种文本分析操作,从而提高开发效率和代码质量。

在实际应用中,建议根据具体的需求进行定制和扩展,例如添加更复杂的自然语言处理功能、支持多语言分析等高级特性。同时,定期进行性能测试和优化,确保应用在处理大型文本时仍然保持良好的性能。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐