当前位置: 首页 > news >正文

多平台拼音输入法软件的开发

在这里插入图片描述

拼音输入法从上个世纪发展到现在, 已经发展了几十年了, 技术上已经非常成熟了. 换句话说, 就是实际上没多少技术含量, 随便来个人就能手搓一个.

在这里插入图片描述

本文介绍一个简单的多平台拼音输入法软件的设计和实现, 支持 GNU/Linux (ibus) 平台 (PC) 和 Android 平台 (手机).

目录

  • 1 中文输入法简介
  • 2 整体架构设计
    • 2.1 数据
    • 2.2 拼音核心
    • 2.3 图形用户界面
    • 2.4 系统输入法接口与应用
  • 3 具体实现栗子
    • 3.1 架构设计
    • 3.2 拼音核心: 查表+激进学习策略
    • 3.3 PC 平台的界面
    • 3.4 Android 平台的界面
    • 3.5 GNU/Linux 应用 (ibus)
    • 3.6 Android 应用
    • 3.7 安全设计
    • 3.8 输入测量
  • 4 总结与展望

1 中文输入法简介

  • 为什么需要输入法 ?

    省流: 因为汉字数量太多了.

    英文输入计算机非常简单, 因为英文字母只有 26 个, 完全可以在键盘上摆下 26 个按键, 然后按一个按键就输入对应的英文字母.

    但是, 目前 (现代汉语/简体中文) 常用的汉字就有 3000/5000/7000 个. 一组简单的测量数据: 常用 3000 汉字占中文文本中出现的所有汉字的 99%, 常用 5000 汉字占 99.9%, 常用 7000 汉字占 99.99%. 剩下还有几万个, 甚至更多的汉字, 但是很少见了.

    如何输入几千个不同的汉字, 就是一个问题了. 因此产生了许多种不同的中文输入法.

  • 中文输入法有哪些 ?

    根据输入方式可以分为: 键盘输入 (包括实体键盘和屏幕触摸键盘), 语音输入 (语音识别), 手写输入, 光学字符识别 (OCR) 等. 未来可能还有脑电波输入 (脑机接口).

    键盘输入有: 拼音输入法, 形码输入法 (比如 五笔), 音形结合 (拼音+形码) 等.

    虽然有很多种不同的输入法, 但目前拼音输入法仍然是使用最多的.

  • 拼音输入法有哪些 ?

    拼音输入法可以分为 全拼双拼.

    全拼就是输入完整的拼音 (比如 qiong), 也包括简拼 (就是省略一部分拼音).

    双拼就是每个拼音对应两个按键 (比如 qs). 双拼有多种不同的具体方案, 比如 自然码.

  • 拼音输入法有哪些主要挑战 ?

    省流: 重码 (同音字).

    常用汉字有几千个那么多, 但是拼音 (普通话, 不带声调) 只有 400 多个. 所以必然存在一个拼音对应多个汉字的情况, 夸张的时候一个拼音对应 100 多个汉字.

    翻页查找需要的汉字必然很慢, 会大大降低输入效率.

    面对用户这个语焉不详的谜语人, 拼音输入法必须想方设法的去猜, 用户到底想输入什么 ? 然后把用户想输入的东西, 在候选项列表里面尽量往前放, 最好放在第一个.

2 整体架构设计

在这里插入图片描述

作为一个支持多平台的拼音输入法, 整体上可分为平台无关的部分, 和平台相关的部分.

平台无关的部分包括数据, 以及拼音核心. 在各个平台上都是通用的.

平台相关的部分包括图形用户界面, 以及系统输入法接口. 最后需要一个适应相应平台的应用, 来把这些东西装进去.

2.1 数据

输入法核心所需的数据, 以及用户数据库 (学习功能).

比如拼音数据 (拼音和汉字的对应关系), 汉字频率数据, 词库, 语言大模型等. 具体取决于核心使用的方法.

这部分数据需要专门收集, 整理, 准备.

2.2 拼音核心

实现拼音到汉字的转换 (这也是输入法的核心功能).

具体可以使用多种方法. 简单的比如查表, 复杂的比如使用 AI 技术 (语言大模型) 等.

2.3 图形用户界面

不同的设备 (比如 PC, 手机) 需要不同的用户界面.

  • PC 平台 (使用 键盘, 鼠标操作, 一般有 大屏显示器) 具体的设备形态包括: 台式机, 笔记本, 迷你主机 等.

    用户界面主要是候选框窗口, 需要跟随文本光标的位置移动.

  • 手机平台: 用户界面主要是屏幕底部的触摸键盘.

2.4 系统输入法接口与应用

不同的操作系统 (比如 GNU/Linux, Android, Windows) 具有各自不同的输入法接口, 以及应用格式.

  • GNU/Linux (PC) 平台: 系统接口 (输入法框架) 有 ibus, fcitx 等.

    通常系统具有软件包管理器 (比如 pacman, apt, rpm 等). 各个软件由软件包管理器统一安装和升级, 并处理软件包之间的依赖关系.

  • Android (手机) 平台: 系统接口是 Android 输入法框架.

    应用的格式是 apk, 编程语言一般是 JVM (比如 java 或 kotlin).

  • Windows (PC) 平台: 系统接口是 TSF 输入法框架.

    应用格式是 .exe 可执行程序.

3 具体实现栗子

好, 上面把理论部分讲完了, 下面说一个具体的实现.

3.1 架构设计

技术选型的主要目标:

  • (1) 低成本, 快速开发.

    这里是指开发的低成本. 运行性能 (运行速度快, 内存占用小) 相对不重要. 用户体验相对不重要. 也就是说, 这个软件主要是为开发者而开发的.

  • (2) 跨平台.

    所选技术应该能够支持多个平台, 尽量在不同的平台之间多共享代码.

  • (3) 低门槛.

    所选技术应该容易学习, 容易入门, 容易上手, 具有大量的开发者.

没错, 说的就是 web 技术 ! 主要编程语言为 JavaScript.

在这里插入图片描述

拼音核心使用 deno (fresh) 运行环境, 编程语言 TypeScript. deno 是一个类似 node 的 js 运行环境, 使用 rust 编写. 使用 deno 开发比使用 node 更容易, 所以选择 deno. 数据库使用 deno-kv, 底层基于 sqlite.

用户界面使用 vue 框架开发, 经典的 web 技术: js + HTML + CSS. 虽然 PC 和手机的界面需要分别开发, 但因为都在一个 vue 项目中, 两个界面之间也共享了很多代码.

electronjs 是一个基于 chromium 浏览器的壳, 支持 GNU/Linux 平台, 负责把 vue 开发的界面显示出来. 艾刷 (librush) 模块负责与 ibus 输入法框架的接口.

Android 应用使用 WebView (背后还是 chromium) 把 vue 界面显示出来. 同时负责系统输入法接口 (Android 输入法框架).

代码行数统计 (cloc): pmim-server 1936 行 (TypeScript), ui-vue 2396 行 (vue/js), pmim-ibus/electronjs 227 行 (js), librush 1222 行 (rust), pmim-apk 531 行 (kotlin). 代码总数 6312 行 (100%), 平台无关部分 4332 行 (68.6%), 平台相关部分 1980 行 (31.4%), GNU/Linux 平台代码 1449 行 (23.0%), Android 平台代码 531 行 (8.4%).

可以看到, 在这个架构之下, 大部分代码都是平台无关的. 特别是 Android 平台只需要很少的平台支持代码.

3.2 拼音核心: 查表+激进学习策略

相关文章:

  • 《从 Unicode 标准提取拼音数据》 https://blog.csdn.net/secext2022/article/details/136110314
  • 《双拼 (自然码) 的简单实现》 https://blog.csdn.net/secext2022/article/details/136120779

拼音数据从 Unicode 数据库中提取. 词库使用了一个 6 万个词的很小的词库. 拼音切分部分, 实现了双拼 (自然码), 以及自定义双拼表. 拼音转汉字部分, 使用了最简单的查表法, 就是直接查词库.


激进的学习策略, 就是用户输入的东西 永远 优先于内置词库. 会在用户数据库存储用户输入内容的时间和频率 (次数), 查询候选项时, 会根据最近使用时间 (7 天内) 和频率排序.

用户数据库存储在本地, 关于更详细的安全分析请见 3.7 章节.

在这里插入图片描述

比如, 第一次尝试输入 “穷人小水滴”, 由于词库中只有 “穷人”, 所以候选项如图所示.

在这里插入图片描述

输入一次之后, 再次输入, 用户数据库中就有了这个词. 这就是简单的学习功能.

随着使用时间的增加, 用户数据的积累, 输入法会变的越来越好用.

3.3 PC 平台的界面

相关文章:

  • 《使用 electronjs 实现 ibus 输入法的用户界面》 https://blog.csdn.net/secext2022/article/details/136143845

在这里插入图片描述

候选框窗口如图所示. 候选框窗口需要跟随文本光标的位置移动, 并按照需要显示隐藏.

中间显示原始输入 (双拼), 上方显示对应的全拼. 下方显示候选项, 每页 10 个, 按 1 ~ 0 数字键输入对应的候选项. 右侧显示有候选项的页码, 总页数. 按 , . 键翻页. 空格键输入第一个候选项. Esc 键取消输入.

由于这个界面是基于 vue 开发的, 对这个界面进行个性化修改应该是很容易的.

3.4 Android 平台的界面

在这里插入图片描述

这个界面负责卖萌. 点击上方的一行字切换不同的输入界面. 右上角的按钮用来关闭软键盘.

在这里插入图片描述

英文键盘 (默认). 左上角 shift 键 (切换大小写), 右上角退格键 (backspace), 下方大大的空格键, 右下角回车键 (enter).

在这里插入图片描述

英文键盘 (shift).

关于键盘布局的问题, 这个键盘布局被窝称为 abcd7109. 这其实是一种很复古的设计, 因为在 qwerty 键盘布局出现之前, 打字机的键盘是按照英文字母的顺序排列的.

对应源代码 (pmim-ibus/ui-vue/src/im2/c/键盘/键盘布局.js):

// 定义键盘布局 (主键盘): abcd7109
export const 布局 = [// 第 1 行: 7[["a", "A"],["b", "B"],["c", "C"],["d", "D"],["e", "E"],["f", "F"],["g", "G"],],// 第 2 行: 10[["h", "H"],["i", "I"],["j", "J"],["k", "K"],["l", "L"],["m", "M"],["n", "N"],["o", "O"],["p", "P"],["q", "Q"],],// 第 3 行: 9 + 1[["r", "R"],["s", "S"],["t", "T"],["u", "U"],["v", "V"],["w", "W"],["x", "X"],["y", "Y"],["z", "Z"],[".", "/"],],// 第 4 行: (2)[["-", "_"],[",", ":"],],
];

所以, 想要修改键盘布局是很容易的.


在这里插入图片描述

拼音输入 (默认状态). 左上角的 shift 键换成了 “重输” 键, 按下会清空全部拼音.

在这里插入图片描述

拼音输入状态, 上方显示输入的拼音 (双拼) 和候选项.

在这里插入图片描述

数字键盘.

在这里插入图片描述

ASCII 符号键盘 (英文标点). 此处包含 ASCII 的全部符号 (32 个, 空格除外), 对写代码友好. 从此在手机上也能好好写代码啦 ~~

在这里插入图片描述

中文标点以及一些符号.


此处 Android 软键盘界面的设计, 主要是为了简单. 各个键盘的职责分工明确, 键盘之间的切换简单直接, 保持一致. 别的输入法各种键盘之间的复杂跳转逻辑, 窝是受不了的, 一会儿在这里点这个键, 一会儿到那里点那个键, 一会儿就绕晕了, 不知道自己在哪里 … .

3.5 GNU/Linux 应用 (ibus)

相关文章:

  • 《ibus 源代码阅读 (1)》 https://blog.csdn.net/secext2022/article/details/136099328
  • 《发布 rust 源码包 (crates.io)》 https://blog.csdn.net/secext2022/article/details/136201091

此处选择了 ibus 输入法框架.

为什么要选择 ibus 呢 ? 因为窝使用 GNOME 桌面环境, GNOME 默认集成了 ibus, 所以使用起来比较方便. 并且, 虽然 ibus 的拼音输入法 (ibus-libpinyin) 窝感觉不太好用, 但是 ibus 本身多年来还是很稳的, 基本上没出过大问题. 虽然还有 fcitx 输入法框架, 但是在 GNU/Linux 桌面环境的软件生态中, ibus 相对更普及一些. 甚至 fcitx 本身也选择了兼容 ibus.

ibus 本身使用 C 和 python 开发, 但是使用 D-Bus 协议连接各个组件. 本输入法虽然使用了 ibus 输入法框架, 但是并没有对 ibus 有代码上的直接依赖, 而是选择从 D-Bus 开始, 兼容 ibus 的协议 (艾刷 librush 模块).

在 web 运行环境的选择上, 窝喜欢 chromium 浏览器内核, 所以排除了 tauri, 选择了 electronjs. electronjs 更加成熟稳定, 有 vscode 这个大厂的产品做代表, 并且 electronjs 不用自己编译, 直接拿过来就能用, 比较方便. 在 GNU/Linux 系统上通常都有软件包管理器进行依赖管理, 这可以抵消 electronjs 的大部分缺点.

专门为 electronjs 编写的代码只有 227 行 (js).

3.6 Android 应用

相关文章:

  • 《Android 输入法框架简介》 https://blog.csdn.net/secext2022/article/details/136246340
  • 《在 Android 运行 GNU/Linux 二进制程序 (proot)》 https://blog.csdn.net/secext2022/article/details/136333781

在 Android 系统就要使用 Android 输入法框架. Android 应用是使用 Android Studio 创建的普通应用, 使用 kotlin 编程语言.

在 Android 使用 WebView 显示网页是标准操作, 也就是使用系统自带的浏览器内核 (chromium). 在国产手机 (比如 MIUI) 上即使不 root, 也可以通过安装 Android System WebView 这个 apk 来更新系统 WebView 内核.

在这里插入图片描述

注意版本号.

在 apk 中打包自带一个浏览器内核, 不是做不到, 但是太麻烦了, 并且相比系统 WebView 没有明显优点.


在 Android 运行 deno 使用了 proot. 这样可以让拼音核心运行起来.

使用 proot 并不是最好的方法, 但是可以接受, 性能并不差.

3.7 安全设计

相关文章:

  • 《高版本 Android 如何访问 sdcard/Android/data 目录中的文件 (翻译)》 https://blog.csdn.net/secext2022/article/details/136335220

此处的安全 (security), 是指信息安全, 网络安全, 黑客攻击这方面的.

事先声明, 绝对的安全是不可能实现的. 开发者能够做的, 是让一个软件合理的, 足够的安全, 不要出现严重安全漏洞而已.

在此详细描述安全方面的设计, 是为了能够公开的对其进行检查. 如果发现这里有安全漏洞, 记得联系窝哦 ~~

  • (1) 用户数据库的存储. 用户数据库保存了一部分用户输入的内容, 这部分是敏感数据, 需要重点保护.

    用户数据库使用 deno-kv, 底层对应 sqlite 数据库. https://deno.com/kv

    • 在 GNU/Linux 平台, 用户数据库文件的位置是:

      > ls -l ~/.config/pmim/pmim_user.db
      -rw-r--r-- 1 s2 s2 2977792  3320:29 /home/s2/.config/pmim/pmim_user.db
      

      这位于用户的主目录中, 而用户主目录默认的权限是 700:

      > ls -ld ~
      drwx------ 1 s2 s2 1136  34日 05:28 /home/s2/
      

      也就是说只有用户自己可以访问. 所以, 在系统环境安全 (没有别的恶意软件偷偷读取这个数据库) 的前提下, 这个数据库文件是安全的.


    • 在 Android 平台, 用户数据库文件的位置是: /sdcard/Android/data/io.github.fm_elpac.pmim_apk/files/pmim/pmim_user.db

      这个是 Android/data/包名 目录, 只有应用自己可以访问, 别的应用无法访问. 所以, 在系统环境安全 (比如没有 root) 的前提下, 这个数据库文件是安全的.

  • (2) 拼音核心 (pmim-server) 的 HTTP 接口. 用户界面通过 HTTP 接口 (REST) 对拼音核心进行请求.

    拼音核心对 HTTP 接口 (API) 的调用使用 token 认证, 对应源代码 (pmim/server/routes/pmims_api/_middleware.ts):

    // /pmims_api/* header: x-token
    // 检查口令 (认证)
    export async function handler(req: Request,ctx: FreshContext<状态>,
    ) {// 首先尝试从 headers 中获取 tokenlet token = req.headers.get(HH_TOKEN);// 其次从 cookie 中获取 tokenif (null == token) {token = getCookies(req.headers)["x_token"];}// 检查 token 是否正确if ((null == token) || (!检查口令(token))) {return new Response("HTTP 403", {status: 403,});}return await ctx.next();
    }
    

    检查口令使用定长时间的比较函数, 这是为了对抗时间侧信道的攻击, 对应源代码 (pmim/server/pmims/auth/token.ts):

    import { timingSafeEqual } from "$std/crypto/timing_safe_equal.ts";// 内存中保存的口令
    const etc = {口令: new Uint8Array(),
    };export function 检查口令(t: string): boolean {const d = new TextEncoder().encode(t);return timingSafeEqual(d, etc.口令);
    }
    

    token 使用真随机数据生成, 对应源代码 (pmim/server/pmims/auth/token.ts):

    async function 获取随机数据(): Promise<string> {// 64 Byte, 512bit 随机数据const a = new Uint8Array(64);crypto.getRandomValues(a);// base64(sha256())const h = await crypto.subtle.digest("SHA-256", a);return encodeBase64(h);
    }export function 口令文件路径(): string {const 目录 = Deno.env.get(ENV_XDG_RUNTIME_DIR)!;return join(目录, FP_TOKEN);
    }export async function 初始化口令() {const 口令文件 = 口令文件路径();logi(" token: " + 口令文件);const 口令 = await 获取随机数据();// 存储口令etc.口令 = new TextEncoder().encode(口令);await 建上级目录(口令文件);await Deno.writeTextFile(口令文件, 口令);
    }
    

    此处使用的是 Web Crypto API https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API.

    同时, 拼音核心只监听 127.0.0.1 IP 地址, 这意味着只有本机的程序可以连接. 对应源代码 (pmim/server/pmims/conf.ts):

    export const 监听地址 = "127.0.0.1";
    

    源代码 (pmim/server/fresh.config.ts):

    export default function getConfig() {return defineConfig({plugins: [tailwind()],server: {port: 获取端口(),hostname: 监听地址,onListen,},});
    }
    

    • 在 GNU/Linux 平台, 口令文件的存储位置是:

      > ls -l $XDG_RUNTIME_DIR/pmim/server_token
      -rw-r--r-- 1 s2 s2 44  3322:27 /run/user/1000/pmim/server_token
      

      XDG_RUNTIME_DIR 的默认权限是 700:

      > ls -ld $XDG_RUNTIME_DIR
      drwx------ 19 s2 s2 660  34日 06:21 /run/user/1000/
      

      同样的, 只有用户自己可以访问.

      electronjs 读取口令文件的源代码是 (pmim-ibus/electronjs/main.js):

      // 读取 deno/fresh server http token
      async function read_token() {const xrd = process.env["XDG_RUNTIME_DIR"];const 口令文件 = path.join(xrd, "pmim/server_token");logi(" read token: " + 口令文件);return await readFile(口令文件, { encoding: "utf8" });
      }
      

    • 在 Android 平台, 口令文件的存储位置是: /sdcard/Android/data/io.github.fm_elpac.pmim_apk/files/pmim/server_token

      同样的, 只有应用自己可以访问.

      WebView 中的页面读取口令文件的源代码是 (pmim-apk/p/app/src/main/java/io/github/fm_elpac/pmim_apk/im/ImView.kt):

      // 读取 pmim-server 的口令
      @JavascriptInterface
      fun pm_口令(): String {// /storage/emulated/0/Android/data/io.github.fm_elpac.pmim_apk/files/pmim/server_tokenval 外部文件目录 = p.getExternalFilesDir(null)!!val 口令文件 = File(外部文件目录, "pmim/server_token")println("ImView: 口令文件 " + 口令文件.getAbsolutePath())return 口令文件.readText()
      }
      

    上述这套机制实现了:

    • (1) 每次启动后都重新生成足够长的随机 token.
    • (2) 只有本机的应用 (127.0.0.1) 才可能请求核心的 HTTP 接口.
    • (3) (GNU/Linux) 只有用户自己的应用 (能够读取口令文件) 才可以请求核心的接口. 本机别的用户无法访问.
    • (4) (Android) 只有应用自己才可以请求核心的接口. 本机别的应用无法访问.

  • (3) 艾刷与拼音核心之间的通信 (仅适用于 GNU/Linux 平台).

    艾刷与拼音核心之间使用 UNIX socket, 对应的文件路径是:

    > ls -l $XDG_RUNTIME_DIR/pmim/us
    srwxr-xr-x 1 s2 s2 0  3322:27 /run/user/1000/pmim/us=
    

    同样的, 只有用户自己可以访问.


未来, 还可以考虑使用 deno 权限, flatpak 沙箱 (sandbox) 等安全机制, 进一步增强应用的安全性 (比如完全禁止网络访问).

3.8 输入测量

俗话说, 没有测量就没有发言权. 拼音输入法使用 web 技术 (JavaScript) 开发, 性能会不会很差 ?

拼音核心实现了对输入的简单测量功能. 本章节来回答这些问题.

输入测量功能实现了对输入字数, 候选项序号, 核心的拼音切分和拼音转汉字的响应时间等的统计. 每分钟产生一条测量数据.

测量方法, 比如拼音核心的响应时间对应源代码 (pmim-ibus/ui-vue/src/输入/输入.js):

  // 调用接口进行拼音切分async _拼音切分() {// 输入测量const d1 = new Date();const r1 = await pm_pin_yin();const d2 = new Date();this._mt_pin_yin.push(测量时间(d1, d2));// 省略// 测量时间 (Date) 返回 ms
function 测量时间(d1, d2) {return d2.getTime() - d1.getTime();
}

使用 js 内置的 Date 进行时间测量, 精度在毫秒级别.


输入测量接口的原始数据类似这样 (有省略):

> curl -H x-token:(cat /run/user/1000/pmim/server_token) -X POST http://127.0.0.1:20200/pmims_api/m -d '{"d": "2024-03-03"}' | jq '.'% Total    % Received % Xferd  Average Speed   Time    Time     Time  CurrentDload  Upload   Total   Spent    Left  Speed
100 37051  100 37032  100    19   789k    414 --:--:-- --:--:-- --:--:--  804k
{"2024-03-03": {"分钟": 144,"统计": {"c.c": 1347,"c.c/m": 1,"c.c/M": 25,"c.n": 1347,"c.n/m": 1,"c.n/M": 25,"c.t": 2498,"c.t/m": 1,"c.t/M": 48,"c.t_M": 4,"c.t_m": 1,"i.c": 256,"i.c/m": 0,"i.c/M": 24,"i.c1": 74,"i.c1/m": 0,"i.c1/M": 12,"i.c1_M": 12,"i.c1_m": 0,"i.c1_n": 1279,"i.c1_n/m": 1,"i.c1_n/M": 24,"i.c_M": 13,"i.c_m": 0,"i.cn": 182,"i.cn/m": 0,"i.cn/M": 24,"i.cn_M": 13,"i.cn_m": 0,"i.cn_n": 141,"i.cn_n/m": 2,"i.cn_n/M": 11,"i.n": 1420,"i.n/m": 1,"i.n/M": 27,"i.n_M": 3,"i.n_m": 1,"t.c": 69075,"t.c/m": 13,"t.c/M": 1631,"t.c_M": 228,"t.c_m": 4,"t.c_n": 2613,"t.c_n/m": 1,"t.c_n/M": 50,"t.p": 96648,"t.p/m": 25,"t.p/M": 2021,"t.p_M": 112,"t.p_m": 3,"t.p_n": 5092,"t.p_n/m": 2,"t.p_n/M": 96},"平均": {"c.n": 9.354166666666666,"c.c": 9.354166666666666,"c.t": 17.34722222222222,"i.n": 1.0541945063103193,"i.c": 0.18028169014084508,"i.c1": 0.05785770132916341,"i.cn": 1.2907801418439717,"t.p": 18.98036135113904,"t.c": 26.435132032146957},"数据": {"1326": {"c.c": 14,"c.n": 14,"c.t": 24,"c.t_M": 2,"c.t_m": 1,"i.c": 3,"i.c1": 0,"i.c1_M": 0,"i.c1_m": 0,"i.c1_n": 13,"i.c_M": 3,"i.c_m": 0,"i.cn": 3,"i.cn_M": 3,"i.cn_m": 0,"i.cn_n": 2,"i.n": 15,"i.n_M": 2,"i.n_m": 1,"t.c": 569,"t.c_M": 47,"t.c_m": 6,"t.c_n": 25,"t.p": 860,"t.p_M": 97,"t.p_m": 4,"t.p_n": 48},"1352": {"c.c": 2,"c.n": 2,"c.t": 2,"c.t_M": 1,"c.t_m": 1,"i.c": 0,"i.c1": 0,"i.c1_M": 0,"i.c1_m": 0,"i.c1_n": 2,"i.c_M": 0,"i.c_m": 0,"i.n": 2,"i.n_M": 1,"i.n_m": 1,"t.c": 32,"t.c_M": 21,"t.c_m": 11,"t.c_n": 2,"t.p": 129,"t.p_M": 58,"t.p_m": 11,"t.p_n": 4},

调用接口可以对某一天的测量数据进行统计. 其中 分钟 是指多少分钟内有输入, 因为每分钟产生一条测量数据, 如果这一分钟之内没有进行输入, 就没有对应的测量数据. 统计 是对一天的所有数据进行分项累计. 平均 是一天之内的平均值. 数据 就是列出所有的原始测量数据, 每分钟一条.

此处的统计分析功能很简单, 但是因为以 JSON 格式输出了原始测量数据, 可以很容易的将数据导出, 然后使用更强大的工具 (比如 python) 进行统计分析.


窝这边最近几天的输入测量数据如下表 (GNU/Linux 平台, ibus):

日期分钟字数拼音切分拼音转汉字候选项
2024-02-24119128422.738.10
2024-02-252930521.438.70.414
2024-02-264644820.733.80.121
2024-02-27112179820.133.60.264
2024-02-28236188719.526.80.112
2024-02-29228264720.124.60.075
2024-03-013016220.533.10.014
2024-03-022527318.324.50.084
2024-03-03144249819.026.50.181
2024-03-04*132233717.425.00.211

注:

  • 日期: 测量数据对应的日期 (收集全部 24 小时).

  • 分钟: 在多少分钟内有输入 (每分钟产生一条测量数据).

  • 字数: 输入的总字数.

  • 拼音切分 (ms): 核心进行一次拼音切分的平均响应时间.

  • 拼音转汉字 (ms): 核心进行一次拼音转汉字 (查询候选项) 的平均响应时间.

  • 候选项: 平均候选项序号.

    输入时选择的候选项的序号 (从 0 开始) 的平均值.

  • *: 当天的数据并不完整.


很明显, 输入测量功能是 2024-02-24 开发完成的, 所以并没有之前的数据. 这些是最近几天窝在真实使用场景之下获得的测量数据, 比如写这篇文章.

拼音切分的平均时间基本稳定在大约 20 毫秒. 拼音转汉字后来进行了一点优化, 时间有所下降, 目前稳定在大约 30 毫秒. 这个性能并不算好, 因为如果应用要达到 60fps 的帧率, 每一帧的时间只有 16.6ms.

但是这是一个可以接受的性能. 因为一般人的击键速度难以超过每秒 10 次, 所以 100ms 以内的响应时间是可以接受的. 并且这是在一个性能并不算好的硬件上获得的结果, 窝使用的是 9 年前的破旧笔记本 (CPU i5-6200U). 在更新的硬件上可能会获得更好的结果.

在这里插入图片描述

这是窝随手做的一个击键速度测试 (英文), 最高击键速度每秒 9 次, 每分钟 314 次 (平均 5.2 次/秒).


平均候选项序号, 这个最理想的情况下是 0, 意味着每次输入的候选项都是第一项. 目前这个值大约在 0.1 ~ 0.2 之间, 也就是说大部分输入 (80%) 的候选项是第一项. 这说明输入法核心的性能并不算很差.

目前使用输入法的时间并不长, 用户数据还没有足够的积累. 后续随着不断的使用, 这个值会逐渐下降的.

在这里插入图片描述

这是在一只几年前的旧手机上的运行情况, 内存和存储的占用都在可接受的范围内. 主观感受也能流畅运行.

4 总结与展望

在这里插入图片描述

图片标题: 《拼 2024: 方圆之间, 刺破命运》

本文实现了一个简单的多平台拼音输入法, 支持 GNU/Linux (ibus) 平台 (PC), 和 Android 平台 (手机). 这个输入法的完整源代码只有几千行, 开发这个输入法也只用了十几天的时间.

开发这个拼音输入法主要有两个目的:

  • (1) 自用. 目前这个拼音输入法已经覆盖了窝日常使用的所有设备, 包括一个笔记本 (ArchLinux), 以及 3 只手机 (Android 10, Android 11, Android 12). 从此, 窝就可以只使用自己的拼音输入法啦 ~

  • (2) 用于科普拼音输入法的工作原理.

本输入法基于 web 技术开发, 主要编程语言为 JavaScript, 具有低成本, 快速开发, 跨平台, 低门槛等优点. 经过实际测量, web 技术的性能并不差, 完全可以接受.

输入法需要处理用户输入的敏感数据, 在安全方面需要格外注意. 本文对本输入法的安全设计进行了详细描述.

这个输入法在技术上和功能上都十分简单 (简陋), 拼音核心只使用了最简单的查表法. 但是是可以实际使用的, 比如写这篇文章.

对穷人来说, 便宜, 能用, 就是好.

后续在技术升级方面, 计划一步到位: 使用本地运行的语言大模型.


本文使用 CC-BY-SA 4.0 许可发布.

相关文章:

多平台拼音输入法软件的开发

拼音输入法从上个世纪发展到现在, 已经发展了几十年了, 技术上已经非常成熟了. 换句话说, 就是实际上没多少技术含量, 随便来个人就能手搓一个. 本文介绍一个简单的多平台拼音输入法软件的设计和实现, 支持 GNU/Linux (ibus) 平台 (PC) 和 Android 平台 (手机). 目录 1 中文输…...

Flutter学习7 - Dart 泛型

1、泛型类 //泛型类 class Cache<T> {final Map<String, T> _cache {};void saveData(String key, T value) {_cache[key] value;}//泛型方法T? getData(String key) {return _cache[key];} }void main() {Cache<int> cache1 Cache();const String name…...

Git 基本操作 ⼯作区、暂存区、版本库

创建本地仓库&#xff1a; 创建 Git 本地仓库 要提前说的是&#xff0c;仓库是进行版本控制的⼀个文件目录。我们要想对文件进行版本控制&#xff0c;就必须先创建⼀个仓库出来。 首先touch 一个文件&#xff1a; 初始化仓库&#xff1a; 创建完成后&#xff0c;我们会发现当前…...

利用Vue3的新API(customRef)实现防抖效果

customRef是创建一个自定义的 ref&#xff0c;然后显式声明对其依赖追踪和更新触发的控制方式。因为ref是直接更新的&#xff0c;数据修改会马上更新&#xff0c;而customRef可以认为控制更新的过程&#xff0c;比如可以利用这个api控制 空格输入限制、数据更新速度控制、违规内…...

【Linux】在 Ubuntu 系统下使用 Screen 运行 Python 脚本

在 Ubuntu 系统下使用 Screen 运行 Python 脚本的优点 在 Ubuntu 操作系统中&#xff0c;Screen 是一种非常有用的工具&#xff0c;特别是在需要长时间运行的任务或者需要在后台运行的任务中。结合 Python 脚本&#xff0c;Screen 提供了一种灵活且高效的方式来管理和执行任务…...

jxls——自定义命令设置动态行高

文章目录 前言依赖引入绘制 jxls 批注的 excel 模板测试类编写自定义命令关于自动换行 前言 之前的博客中都简单说了数据的渲染和导出excel文件。包括固定的 表头结构&#xff0c;以及动态 表头和表数据等方式。 本篇博客主要说明自定义命令的方式&#xff0c;控制输出excel文…...

前端面试练习24.3.2-3.3

HTMLCSS部分 一.说一说HTML的语义化 在我看来&#xff0c;它的语义化其实是为了便于机器来看的&#xff0c;当然&#xff0c;程序员在使用语义化标签时也可以使得代码更加易读&#xff0c;对于用户来说&#xff0c;这样有利于构建良好的网页结构&#xff0c;可以在优化用户体…...

优先级队列(Java )

目录 一、 优先级队列1、概念 二、优先级队列的模拟实现1、堆的概念2、堆的存储方式 三、堆的创建1、堆向下调整2、堆的创建3、建堆的时间复杂度 四、堆的插入与删除1、堆的插入2、堆的删除 五、用堆模拟实现优先级队列 一、 优先级队列 1、概念 优先级队列&#xff08;Priori…...

大宋咨询如何进行汽车门店6S标准现场检查

随着汽车市场的快速发展&#xff0c;汽车门店的现场管理日益受到关注。6S标准现场检查作为一项重要的评估工具&#xff0c;正在被越来越多的汽车厂商和经销商采用。 6S标准现场检查是指对汽车门店的整理、整顿、清洁、清扫、素养和安全六个方面进行规范和优化&#xff0c;旨在…...

仿牛客网项目---点赞模块的实现

本篇文章介绍一下项目中的点赞模块。 点赞模块是一个通过使用Redis实现的功能模块&#xff0c;它提供了点赞操作的处理逻辑和数据存取功能。通过服务类和控制器类的配合&#xff0c;点赞模块实现了用户对实体的点赞、点赞数量的查询、点赞状态的查询等功能。该模块使用了Redis…...

【AI视野·今日CV 计算机视觉论文速览 第300期】Fri, 1 Mar 2024

AI视野今日CS.CV 计算机视觉论文速览 Fri, 1 Mar 2024 Totally 114 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Computer Vision Papers DistriFusion: Distributed Parallel Inference for High-Resolution Diffusion Models Authors Muyang Li, Tianle Cai, J…...

【单片机学习的准备】

文章目录 前言一、找一个视频是二、画图软件三、装keil5 仿真protues总结 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 项目需要&#xff1a; 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、找一个视频是 https://www.b…...

力扣hot100:438.找到字符串中所有字母异位词

26个字符&#xff0c;我复制怎么了&#xff1f;26个字符我比较个数怎么了&#xff1f; 顶多时间复杂度*26 本题用固定窗口大小的滑动窗口每次比较包含26个元素的数组次数&#xff0c;最容易写。 动态窗口大小哈希表存数值&#xff08;双指针差值&#xff09;难想难写。 一、动态…...

Kali Linux 2024.1

Kali Linux 2024.1刚刚发布&#xff0c;标志着这个备受欢迎的安全重点Linux发行版在今年的首次重大更新。以其先进的渗透测试和安全审计功能而闻名&#xff0c;它是安全专业人员和爱好者的首选工具。 Kali 2024.1 亮点 本次发布由 Linux 内核 6.6 提供支持&#xff0c;突出了…...

springboot启动加载

目录 使用PostConstruct注解 实现InitializingBean接口 实现CommandLineRunner接口 实现ApplicationRunner接口 使用EventListener注解监听ApplicationReadyEvent事件 应用启动完成之前或者之后&#xff0c;我们需要拿数据库中的一些数据加载到本地缓存中。这些数据一般都…...

基于Java的智能停车场管理系统(Vue.js+SpringBoot)

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、研究内容A. 车主端功能B. 停车工作人员功能C. 系统管理员功能1. 停车位模块2. 车辆模块3. 停车记录模块4. IC卡模块5. IC卡挂失模块 三、界面展示3.1 登录注册3.2 车辆模块3.3 停车位模块3.4 停车数据模块3.5 IC卡档案模块3.6 IC卡挂…...

ESD Clamp cell是什么?

ESD CLAMP cell&#xff08;静电放电钳位单元&#xff09;是一种专门设计来保护集成电路&#xff08;IC&#xff09;免受静电放电&#xff08;ESD&#xff09;损害的电路元件。静电放电是在电子设备的组件之间或内部发生的突然电流放电&#xff0c;它可能会损坏电路或降低其性能…...

费率电能表

费率电能表是一种用于测量家庭、商业和工业用电的设备&#xff0c;有效的实现分段计费、分时计费&#xff0c;优化用电效率。费率电能表的产生是为了缓解高峰期的用电负荷&#xff0c;平衡各时间段的用电负荷&#xff1b;根据当地用电负荷曲线情况制定时段费率 在费率电能表中…...

2张图2秒钟3D重建!这款AI工具火爆GitHub,网友:忘掉Sora

只需2张图片&#xff0c;无需测量任何额外数据—— 当当&#xff0c;一个完整的3D小熊就有了&#xff1a; 这个名为DUSt3R的新工具&#xff0c;火得一塌糊涂&#xff0c;才上线没多久就登上GitHub热榜第二。 ▲image 有网友实测&#xff0c;拍两张照片&#xff0c;真的就重建…...

C++高级面试题:请解释 C++ 中的指针和引用之间的区别。

请解释 C 中的指针和引用之间的区别。 在 C 中&#xff0c;指针&#xff08;Pointers&#xff09;和引用&#xff08;References&#xff09;都是用于处理内存地址的工具&#xff0c;但它们有一些重要的区别&#xff1a; 语法和用法&#xff1a; 指针使用 * 运算符来访问其所…...

Java 语言特性(面试系列1)

一、面向对象编程 1. 封装&#xff08;Encapsulation&#xff09; 定义&#xff1a;将数据&#xff08;属性&#xff09;和操作数据的方法绑定在一起&#xff0c;通过访问控制符&#xff08;private、protected、public&#xff09;隐藏内部实现细节。示例&#xff1a; public …...

day52 ResNet18 CBAM

在深度学习的旅程中&#xff0c;我们不断探索如何提升模型的性能。今天&#xff0c;我将分享我在 ResNet18 模型中插入 CBAM&#xff08;Convolutional Block Attention Module&#xff09;模块&#xff0c;并采用分阶段微调策略的实践过程。通过这个过程&#xff0c;我不仅提升…...

Auto-Coder使用GPT-4o完成:在用TabPFN这个模型构建一个预测未来3天涨跌的分类任务

通过akshare库&#xff0c;获取股票数据&#xff0c;并生成TabPFN这个模型 可以识别、处理的格式&#xff0c;写一个完整的预处理示例&#xff0c;并构建一个预测未来 3 天股价涨跌的分类任务 用TabPFN这个模型构建一个预测未来 3 天股价涨跌的分类任务&#xff0c;进行预测并输…...

06 Deep learning神经网络编程基础 激活函数 --吴恩达

深度学习激活函数详解 一、核心作用 引入非线性:使神经网络可学习复杂模式控制输出范围:如Sigmoid将输出限制在(0,1)梯度传递:影响反向传播的稳定性二、常见类型及数学表达 Sigmoid σ ( x ) = 1 1 +...

推荐 github 项目:GeminiImageApp(图片生成方向,可以做一定的素材)

推荐 github 项目:GeminiImageApp(图片生成方向&#xff0c;可以做一定的素材) 这个项目能干嘛? 使用 gemini 2.0 的 api 和 google 其他的 api 来做衍生处理 简化和优化了文生图和图生图的行为(我的最主要) 并且有一些目标检测和切割(我用不到) 视频和 imagefx 因为没 a…...

STM32HAL库USART源代码解析及应用

STM32HAL库USART源代码解析 前言STM32CubeIDE配置串口USART和UART的选择使用模式参数设置GPIO配置DMA配置中断配置硬件流控制使能生成代码解析和使用方法串口初始化__UART_HandleTypeDef结构体浅析HAL库代码实际使用方法使用轮询方式发送使用轮询方式接收使用中断方式发送使用中…...

解读《网络安全法》最新修订,把握网络安全新趋势

《网络安全法》自2017年施行以来&#xff0c;在维护网络空间安全方面发挥了重要作用。但随着网络环境的日益复杂&#xff0c;网络攻击、数据泄露等事件频发&#xff0c;现行法律已难以完全适应新的风险挑战。 2025年3月28日&#xff0c;国家网信办会同相关部门起草了《网络安全…...

Caliper 负载(Workload)详细解析

Caliper 负载(Workload)详细解析 负载(Workload)是 Caliper 性能测试的核心部分,它定义了测试期间要执行的具体合约调用行为和交易模式。下面我将全面深入地讲解负载的各个方面。 一、负载模块基本结构 一个典型的负载模块(如 workload.js)包含以下基本结构: use strict;/…...

用神经网络读懂你的“心情”:揭秘情绪识别系统背后的AI魔法

用神经网络读懂你的“心情”:揭秘情绪识别系统背后的AI魔法 大家好,我是Echo_Wish。最近刷短视频、看直播,有没有发现,越来越多的应用都开始“懂你”了——它们能感知你的情绪,推荐更合适的内容,甚至帮客服识别用户情绪,提升服务体验。这背后,神经网络在悄悄发力,撑起…...

Canal环境搭建并实现和ES数据同步

作者&#xff1a;田超凡 日期&#xff1a;2025年6月7日 Canal安装&#xff0c;启动端口11111、8082&#xff1a; 安装canal-deployer服务端&#xff1a; https://github.com/alibaba/canal/releases/1.1.7/canal.deployer-1.1.7.tar.gz cd /opt/homebrew/etc mkdir canal…...