拼音輸入法從上個世紀發展到現在, 已經發展了幾十年了, 技術上已經非常成熟了. 換句話說, 就是實際上沒多少技術含量, 隨便來個人就能手搓一個.
本文介紹一個簡單的多平臺拼音輸入法軟件的設計和實現, 支持 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 3月 3日 20:29 /home/s2/.config/pmim/pmim_user.db
這位于用戶的主目錄中, 而用戶主目錄默認的權限是
700
:> ls -ld ~ drwx------ 1 s2 s2 1136 3月 4日 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 3月 3日 22:27 /run/user/1000/pmim/server_token
而
XDG_RUNTIME_DIR
的默認權限是700
:> ls -ld $XDG_RUNTIME_DIR drwx------ 19 s2 s2 660 3月 4日 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 3月 3日 22: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-24 | 119 | 1284 | 22.7 | 38.1 | 0 |
2024-02-25 | 29 | 305 | 21.4 | 38.7 | 0.414 |
2024-02-26 | 46 | 448 | 20.7 | 33.8 | 0.121 |
2024-02-27 | 112 | 1798 | 20.1 | 33.6 | 0.264 |
2024-02-28 | 236 | 1887 | 19.5 | 26.8 | 0.112 |
2024-02-29 | 228 | 2647 | 20.1 | 24.6 | 0.075 |
2024-03-01 | 30 | 162 | 20.5 | 33.1 | 0.014 |
2024-03-02 | 25 | 273 | 18.3 | 24.5 | 0.084 |
2024-03-03 | 144 | 2498 | 19.0 | 26.5 | 0.181 |
2024-03-04* | 132 | 2337 | 17.4 | 25.0 | 0.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 許可發布.