多平臺拼音輸入法軟件的開發

在這里插入圖片描述

拼音輸入法從上個世紀發展到現在, 已經發展了幾十年了, 技術上已經非常成熟了. 換句話說, 就是實際上沒多少技術含量, 隨便來個人就能手搓一個.

在這里插入圖片描述

本文介紹一個簡單的多平臺拼音輸入法軟件的設計和實現, 支持 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 許可發布.

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/719540.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/719540.shtml
英文地址,請注明出處:http://en.pswp.cn/news/719540.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

E: 無法修正錯誤,因為您要求某些軟件包保持現狀,就是它們破壞了軟件包間的依賴關系。

比如&#xff0c;安裝ros的時候&#xff0c;用 執行&#xff1a; sudo apt install ros-melodic-desktop-full 出現如下問題&#xff1a; 如果你根據提示&#xff0c;安裝ros-melodic-desktop&#xff0c;他有會說類似“E: 無法修正錯誤&#xff0c;因為您要求某些軟件包保持…

Vue.js入門指南:簡介、環境配置與Yarn創建項目

一、Vue.js簡介 Vue.js&#xff0c;一個流行的JavaScript框架&#xff0c;以其直觀、靈活和高效的特點&#xff0c;在前端開發者中贏得了廣泛的贊譽。Vue.js的核心庫專注于視圖層&#xff0c;使得開發者能夠構建出響應式的數據綁定和組合的視圖組件。Vue.js的目標是通過盡可能簡…

BUUCTF---[極客大挑戰 2019]Http1

1.題目描述&#xff0c;在地址框輸入下面的網址 2.來到頁面&#xff0c;ctrlu查看源碼&#xff0c;仔細觀察會看到一個.php的跳轉頁面 3.點進去頁面提示It doesnt come from https://Sycsecret.buuoj.cn 4.頁面提示它不是來源于這個網址&#xff0c;我們需要用bp抓包對數據進行…

多波束水深數據粗差剔除方法總結(不斷更新)

目錄 一、粗差產生原因 二、粗差剔除方法 三、自動濾波方法分類 3.1 趨勢面濾波 3.1.1 現有方法 3.1.2 缺點

Web開發介紹,制作小網站流程和需要的技術【詳解】

1.什么是web開發 Web&#xff1a;全球廣域網&#xff0c;也稱為萬維網(www World Wide Web)&#xff0c;能夠通過瀏覽器訪問的網站。 所以Web開發說白了&#xff0c;就是開發網站的&#xff0c;例如網站&#xff1a;淘寶&#xff0c;京東等等 2. 網站的工作流程 1.首先我們需…

sparse transformer 常見稀疏注意力

參考&#xff1a; https://zhuanlan.zhihu.com/p/259591644 主要就是降低transformer自注意力模塊的復雜度 復雜度主要就是 Q K^T影響的&#xff0c;稀疏注意力就是在Q點乘K的轉置這模塊做文章 下列式一些sparse transformer稀疏注意力方法 a、transformer原始的 &#xff0…

b站小土堆pytorch學習記錄—— P17 土堆說卷積操作

文章目錄 一、前置知識什么是卷積操作 二、代碼 一、前置知識 什么是卷積操作 推薦幾個高贊博客&#xff1a; 卷積最容易理解的解釋 卷積神經網絡&#xff08;CNN&#xff09;詳細介紹及其原理詳解 還有pytorch官網的動態圖&#xff1a; pytorch卷積 二、代碼 import t…

MyBatis源碼分析之基礎支持層反射

(/≧▽≦)/~┴┴ 嗨~我叫小奧 ??? &#x1f440;&#x1f440;&#x1f440; 個人博客&#xff1a;小奧的博客 &#x1f44d;&#x1f44d;&#x1f44d;&#xff1a;個人CSDN ??????&#xff1a;傳送門 &#x1f379; 本人24應屆生一枚&#xff0c;技術和水平有限&am…

PowerShell禁止運行腳本解決方案

錯誤代碼 CategoryInfo : SecurityError: (:) []&#xff0c;ParentContainsErrorRecordException FullyQualifiedErrorId : UnauthorizedAccess在計算機上啟動 Windows PowerShell 時&#xff0c;執行策略很可能是 Restricted&#xff08;默認設置&#xff09;。 Restricted …

圖像分類應用

先留一段圖像分類代碼&#xff0c;空閑時間再做分析&#xff1a; 創建神經網絡&#xff1a; import torch from torch import nn import torch.nn.functional as F class MyAlexNet(nn.Module):def __init__(self):super(MyAlexNet, self).__init__()self.c1nn.Conv2d(in_cha…

二刷代碼隨想錄算法訓練營第十天 | 232.用棧實現隊列、 225. 用隊列實現棧

目錄 一、232. 用棧實現隊列 二、225. 用隊列實現棧 一、232. 用棧實現隊列 題目鏈接&#xff1a;力扣 文章講解&#xff1a;代碼隨想錄 視頻講解&#xff1a; 棧的基本操作&#xff01; | LeetCode&#xff1a;232.用棧實現隊列 題目&#xff1a; 請你僅使用兩個棧實現先…

Vision Pro開發者學習路線

官方給到的Vision Pro開發者學習路線&#xff1a; 1. 學習基礎知識&#xff1a; - 學習 Xcode、Swift 和 SwiftUI 的基礎知識&#xff0c;包括語法、UI 設計等。 - 掌握 ARKit 和 SwiftUI 的使用&#xff0c;了解如何創建沉浸式增強現實體驗。 2. 學習 3D 建模&#xf…

『Linux從入門到精通』第 ? 期 - System V 共享內存

文章目錄 &#x1f490;專欄導讀&#x1f490;文章導讀&#x1f427;共享內存原理&#x1f427;共享內存相關函數&#x1f426;key 與 shmid 區別 &#x1f427;代碼實例 &#x1f490;專欄導讀 &#x1f338;作者簡介&#xff1a;花想云 &#xff0c;在讀本科生一枚&#xff0…

CentOS7安裝DockerCompose和Docker鏡像倉庫的配置

CentOS7安裝DockerCompose 1.下載 Linux下需要通過命令下載&#xff1a; # 安裝 curl -L https://github.com/docker/compose/releases/download/1.23.1/docker-compose-uname -s-uname -m > /usr/local/bin/docker-compose2.修改文件權限 修改文件權限&#xff1a; # …

YOLOv9獨家原創改進|加入幽靈卷積Ghost Convolution模塊,輕量化!

專欄介紹&#xff1a;YOLOv9改進系列 | 包含深度學習最新創新&#xff0c;主力高效漲點&#xff01;&#xff01;&#xff01; 一、論文摘要 由于內存和計算資源有限&#xff0c;在嵌入式設備上部署卷積神經網絡是困難的。特征圖中的冗余是那些成功的細胞神經網絡的一個重要特征…

【網站項目】158企業人事管理系統

&#x1f64a;作者簡介&#xff1a;擁有多年開發工作經驗&#xff0c;分享技術代碼幫助學生學習&#xff0c;獨立完成自己的項目或者畢業設計。 代碼可以私聊博主獲取。&#x1f339;贈送計算機畢業設計600個選題excel文件&#xff0c;幫助大學選題。贈送開題報告模板&#xff…

突破編程_C++_字符串算法(判斷字符串是否包含)

1 算法題 &#xff1a;判斷一個字符串是否包含另一個字符串的所有字符&#xff08;不一定連續&#xff09; 1.1 題目含義 判斷一個字符串&#xff08;稱為“主字符串”或“大字符串”&#xff09;是否包含另一個字符串&#xff08;稱為“子字符串”或“小字符串”&#xff09…

代碼隨想錄算法訓練營第31天—貪心算法05 | ● 435. 無重疊區間 ● *763.劃分字母區間 ● *56. 合并區間

435. 無重疊區間 https://programmercarl.com/0435.%E6%97%A0%E9%87%8D%E5%8F%A0%E5%8C%BA%E9%97%B4.html 考點 貪心算法重疊區間 我的思路 先按照區間左坐標進行排序&#xff0c;方便后續處理進行for循環&#xff0c;循環范圍是0到倒數第二個元素如果當前區間和下一區間重疊…

在Linux以命令行方式(靜默方式/非圖形化方式)安裝MATLAB(正版)

1.根據教程&#xff0c;下載windows版本matlab&#xff0c;打開圖形化界面&#xff0c;選擇linux版本的只下載不安裝 2.獲取安裝文件夾 3.獲取許可證 4.安裝 &#xff08;1&#xff09;跳過引用文章的2.2章節 &#xff08;2&#xff09;本文的安裝文件夾代替引用文章的解壓IS…

Java進階(鎖)——鎖的升級,synchronized與lock鎖區別

目錄 引出Java中鎖升級synchronized與lock鎖區別 緩存三兄弟&#xff1a;緩存擊穿、穿透、雪崩緩存擊穿緩存穿透緩存雪崩 總結 引出 Java進階&#xff08;鎖&#xff09;——鎖的升級&#xff0c;synchronized與lock鎖區別 Java中鎖升級 看一段代碼&#xff1a; public class…