Clojure持久化數據結構的底層實現

文章目錄

  • 一、Clojure的持久化數據結構
  • 二、向量(Vector)/Map的底層結構?
    • 1. HAMT 哈希數組映射字典樹
      • (1)簡介
      • (2)HAMT 的核心思想
      • (3)HAMT 的結構?
        • a. 基本組成?
        • b. 樹的分支因子?
      • (4)HAMT 的操作?
        • a. 查詢(Get)??
        • b. 插入/更新(Assoc)??
        • c. 刪除(Dissoc)??
      • (5)HAMT 的優化技術?
        • a.位圖(Bitmap)優化?
        • b. 路徑壓縮?
        • c.惰性哈希計算?
      • (6)HAMT 在 Clojure 中的應用?
        • a.不可變哈希表(PersistentHashMap)??
        • b.不可變向量(PersistentVector)??
      • (7)總結
    • 2. HAMT 的 32 路分支
      • (1)HAMT 的 32 路分支(32-way branching)簡介
      • (2)示例 [1 2 3 4 5] 的樹形結構?
        • a. 可能的實際存儲形式?
        • b. 插入更多元素后的變化?
        • c. 為什么示例中節點元素未達到閾值?
      • (3)動態調整規則?
      • (4)為什么選擇 32?
      • (5)32路分支總結
    • 2. 修改向量
    • 3. assoc 函數詳解
      • (1)基本語法?
      • (2)對不同數據結構的操作
        • a. 操作 Map(哈希表)
        • b. 操作 Vector(向量)??
        • c. 操作 Record?
      • (3)不可變性保證?
      • (4)底層實現原理?
        • a. 對 Map 的實現?
        • ??b. 對 Vector 的實現?
      • (5) 總結?
    • 4. (assoc v1 2 99) 的具體過程
      • (1)定位修改路徑?
      • ?(2)路徑復制?
      • (3)結構共享?

一、Clojure的持久化數據結構

Clojure 的持久化數據結構(Persistent Data Structures)在"修改"時會保留原版本的完整性,同時高效生成新版本。其核心是通過結構共享(Structural Sharing)和路徑復制(Path Copying)實現,而非完整拷貝數據。

二、向量(Vector)/Map的底層結構?

1. HAMT 哈希數組映射字典樹

(1)簡介

HAMT(Hash Array Mapped Trie)是一種高效的持久化數據結構,廣泛應用于函數式編程語言(如 Clojure、Scala)中,用于實現高性能的不可變哈希表(Hash Map)和向量(Vector)。它通過哈希值的位分割路徑壓縮技術,在保證不可變性的同時,實現了接近 O(1) 時間復雜度的查詢、插入和刪除操作。

(2)HAMT 的核心思想

HAMT 的核心是通過 ?哈希值的位分段? 和 ?樹形結構? 來組織數據,結合 ?位圖(Bitmap)?? 和 ?路徑壓縮? 優化存儲效率。其核心特性包括:

  • ?不可變性?:所有操作生成新版本,原數據不變。 ?
  • 高效查詢?:平均時間復雜度為 O(log??n),實際接近 O(1)。
  • 結構共享?:新舊版本共享未修改的部分,減少內存開銷。
  • ?動態擴展?:根據數據量自動調整樹深度。

(3)HAMT 的結構?

a. 基本組成?

HAMT 的每個節點分為兩種:

  • 內部節點(Branch Node)??
    • 存儲 ?位圖(Bitmap)?? 和 ?子節點數組
    • 位圖標記哪些子節點存在(32 位,對應 32 個子節點)。
    • 子節點可以是內部節點或葉子節點。
    • 子節點數組存儲的是對子節點的引用,類似于指針或引用(具體實現可能是內存地址、對象引用等)。
    • 子節點數組是 ?動態分配? 的,僅存儲實際存在的子節點。子節點數組的長度 = 位圖中 1 的個數(即實際存在的子節點數),通過位圖的 popcount(統計 1 的數量)快速定位子節點。
  • ?葉子節點(Leaf Node)??
    • 存儲實際的鍵值對(Key-Value)。
    • 如果哈希沖突(多個鍵哈希到同一位置),使用鏈表或進一步擴展。
b. 樹的分支因子?

?32 路分支?(32-way branching):每個內部節點最多 32 個子節點。

  • 原因:32 = 2?,哈希值按 5 位分段決定路徑。
  • 例如,哈希值 0x1A3B 的分段:
    第 1 層:取 0x1A3B & 0x1F(低 5 位)→ 分支索引。
    第 2 層:取 (0x1A3B >> 5) & 0x1F → 下一層分支索引。

(4)HAMT 的操作?

a. 查詢(Get)??

第一步:計算鍵的哈希值(如 hash(“key”))。
第二步:使用 ?哈希值的分段(5 位一組)?? 逐層向下查找,每一層對應哈希值的一部分:

  • 第 1 層:hash & 0x1F(取最低 5 位)→ 索引 i?。
  • 第 2 層:(hash >> 5) & 0x1F → 索引 i?。
  • 第 3 層?:(hash >> 10) & 0x1F → 索引 i?。
  • … 依此類推,直到找到目標葉子節點或返回 nil。

?時間復雜度?:O(log??n) ≈ O(1)(通常 2-4 層即可定位)。

為什么查詢時間復雜度是O(log??n) ?
因為HAMT的結構類似于 ?32 叉樹,層數與 n 的對數相關。
實際應用中,由于層數極少(2-4 層),可以近似看作 O(1)?,比傳統哈希表更穩定(無沖突退化問題)。

b. 插入/更新(Assoc)??

第一步:計算哈希值,按路徑查找目標位置。
第二步:

  • 如果目標位置為空:
    • 創建新葉子節點,更新位圖和子節點數組。
  • 如果目標位置已有數據:
    • 哈希沖突?:擴展為新的內部節點,重新分布數據。

第三步:?路徑復制?:復制受影響路徑上的節點,未修改部分共享。

示例:

(def m {:a 1})  ; 創建映射 m = {:a 1}
(def m2 (assoc m :b 2))  ; 創建新映射 m2 = {:a 1, :b 2},m 保持不變
c. 刪除(Dissoc)??

第一步:查找目標鍵的位置。
第二步:移除對應葉子節點,更新位圖。
第三步:如果子節點數組為空,向上收縮樹結構。

(5)HAMT 的優化技術?

a.位圖(Bitmap)優化?

傳統 Trie 需要預分配 32 個子節點指針(內存浪費)。
HAMT 使用 ?位圖標記有效子節點,只存儲存在的分支。例如,位圖 0b101 表示只有第 0 和第 2 個子節點存在。

b. 路徑壓縮?

如果某條路徑上只有一個子節點,可以壓縮存儲(跳過中間節點)。
減少樹深度,提高查詢速度。

c.惰性哈希計算?

哈希值按需計算(避免預計算所有鍵的哈希)。
在沖突時再計算更深的哈希位。

(6)HAMT 在 Clojure 中的應用?

a.不可變哈希表(PersistentHashMap)??

Clojure 的 {} 和 hash-map 基于 HAMT 實現。
支持高效的 assoc、dissoc 和 get。

示例:

(def m {:a 1, :b 2})
(get m :a)     ; => 1
(assoc m :c 3) ; => {:a 1, :b 2, :c 3}
b.不可變向量(PersistentVector)??

Clojure 的 [] 和 vector 也使用類似結構(但按索引而非哈希分布)。
支持高效的 nth 和 assoc。

(def v [1 2 3])
(nth v 1)      ; => 2
(assoc v 1 99) ; => [1 99 3]

(7)總結

  • ?HAMT 是什么?:基于哈希的不可變樹形結構,支持高效查詢和持久化修改。 ?
  • 核心優化?:位圖壓縮、路徑復制、惰性哈希。 ?
  • 在Clojure 中的應用?:PersistentHashMap 和 PersistentVector 的底層實現。
  • 適用場景?:需要高性能、線程安全的不可變數據結構。

HAMT 是函數式編程中 ??“通過不可變性實現并發安全”?? 的經典實踐,也是 Clojure 高效數據結構的基石

2. HAMT 的 32 路分支

Clojure 的向量是基于 HAMT實現的,這是一種高度優化的樹形結構:

  • ?每個節點包含 32 個子節點(32-way branching)
    • ?HAMT 的 ?32 個子節點是每個內部節點的最大容量,但實際存儲時會根據數據分布動態調整。
  • 葉子節點存儲實際數據 ?
  • 內部節點存儲子節點引用

示例 :

向量 [1 2 3 4 5] 的簡化樹形表示:Root/   |   \[1,2] [3,4] [5]

(1)HAMT 的 32 路分支(32-way branching)簡介

  • 理論設計?:
    • 每個內部節點最多有 ?32 個子節點?(對應哈希值的 5 位片段,因為 2的5次方=32)。
    • 哈希值被分割為多個 5 位片段,每段決定一層樹的選擇路徑。
  • 內存優化?:
    • 實際實現中,節點會壓縮存儲非空分支(避免分配 32 個指針的固定數組)。
    • 通過位圖(bitmap)?標記存在的子節點,按需分配內存。

(2)示例 [1 2 3 4 5] 的樹形結構?

a. 可能的實際存儲形式?
       Root (bitmap: 0b111)  ; 標記前3個分支非空/   |   \[1,2] [3,4] [5]             ; 葉子節點合并存儲(優化小數據)

?為什么只有 3 個子節點???

  • 數據局部性優化?:當元素較少時,Clojure 會合并相鄰葉子節點以減少樹深度。
  • 惰性拆分?:只有插入新元素導致節點溢出時(如超過 32 個元素),才會分裂為完整 32 路分支。
b. 插入更多元素后的變化?

若繼續添加元素直到節點超過合并閾值:

;; 假設閾值為 5(實際由 Clojure 內部決定)
(def v [1 2 3 4 5 6 7 8 9 10]);; 樹可能變為:Root (bitmap: 0b11111111)/ | ... | \[1,2] [3,4] ... [9,10]  ; 更多葉子節點
c. 為什么示例中節點元素未達到閾值?

正如上面的例子中,閾值為5,為什么每個節點有兩個元素,共5個葉子節點。而不是每個節點有5個元素,一共兩個葉子節點呢?

澄清概念:閾值的作用?
?閾值(Threshold)?? 是觸發節點分裂的上限,但實際合并的葉子節點大小取決于數據插入順序哈希分布
?關鍵規則?:

  • ?插入時檢查?:當向葉子節點插入元素后,若其大小超過閾值,則分裂為 32 個子節點(最多)。 ?
  • 初始構建?:向量在構造時可能直接生成優化后的結構(不一定填滿閾值)。

正確的閾值示例?

; 葉子節點合并(≤閾值)??
(def v [1 2 3 4 5])  ; 樹結構:[1,2,3,4,5]       ; 單葉子節點(未分裂); 葉子節點分裂(>閾值)
(def v [1 2 3 4 5 6])  ; 樹結構:Root/   |   \[1,2] [3,4] [5,6]     ; 分裂為3個葉子節點(具體數量依賴哈希分布)

為什么有時分支元素較少???

  • ?哈希分布不均?:某些哈希路徑可能只有少量元素。 ?
  • 惰性分裂?:未達到全局閾值時,局部可能先分裂。 ?
  • 優化策略?:Clojure可能犧牲部分密度換取更平衡的樹。

正如 Clojure 源碼注釋所述:

“The trie dynamically balances node density based on insertion
patterns, not just a fixed threshold.” (字典樹根據插入模式動態平衡節點密度,而非僅依賴固定閾值。)

(3)動態調整規則?

場景節點行為示例
?元素少(≤閾值)合并為單個葉子節點[1,2,3] → 不分裂
?元素多(>閾值)拆分為 32 路分支[1…33] → 分裂為兩層樹
哈希沖突?鏈表或進一步哈希擴展罕見,需處理沖突

(4)為什么選擇 32?

  • CPU 緩存友好?:32 個子節點(每個指針 4-8 字節)可填充常見緩存行(64-256 字節)。
  • 時間效率?:log??n 的深度平衡了查找速度和內存占用。
    對比:32 路 vs 2 路(二叉樹)
    • 32 路:log??1M≈3.3 層
    • 2 路:log?1M≈20 層

(5)32路分支總結

  • ?32 是理論最大值,實際存儲會動態合并葉子節點優化小數據。
  • 設計目標?:在內存效率(小數據)和查詢速度(大數據)間平衡。
  • 用戶無需關心?:Clojure 隱藏了這些優化,保證一致的 O(1) 訪問語義。

正如 Clojure 的持久化數據結構論文所述:

“The trie adapts its branching factor based on data density, ensuring
compactness without sacrificing speed.”
(字典樹根據數據密度自適應分支因子,在保證速度的同時實現緊湊存儲。)

2. 修改向量

?修改向量時?:只復制受影響的部分節點,未變部分共享引用。
?示例?:

(def v1 [1 2 3 4 5])
(def v2 (assoc v1 2 99))  ; 僅復制索引 2 的路徑

v1 和 v2 共享未被修改的部分(如索引 0,1,3,4)。

3. assoc 函數詳解

assoc 是 Clojure 中用于關聯性數據結構?(如 Map、Vector、Record)的核心函數,其作用是根據指定的鍵(或索引)?添加、替換或更新值,并返回一個新的不可變數據結構,而原數據保持不變。

(1)基本語法?

(assoc 數據結構 鍵/索引 新值)
(assoc 數據結構 鍵1 值1 鍵2 值2 ...)  ; 多鍵值對操作

(2)對不同數據結構的操作

a. 操作 Map(哈希表)
; ?添加或替換鍵值對?:
(def m {:a 1, :b 2})
(assoc m :c 3)   ; => {:a 1, :b 2, :c 3}  (添加)
(assoc m :b 99)   ; => {:a 1, :b 99}       (替換); 多鍵值操作?:
(assoc m :x 10 :y 20)  ; => {:a 1, :b 2, :x 10, :y 20}
b. 操作 Vector(向量)??
; 按索引替換值?(索引必須存在):
(def v [1 2 3])
(assoc v 1 "two")  ; => [1 "two" 3]; ?越界會報錯?:
(assoc v 5 :x)  ; => IndexOutOfBoundsException
c. 操作 Record?
; 類似 Map,但字段需預定義:
(defrecord Person [name age])
(def p (->Person "Alice" 30)) ; 創建Person類型示例p
(assoc p :age 31)  ; => #user.Person{:name "Alice", :age 31}

以上的assoc操作結果沒有綁定到變量,所以下次訪問需要重新調用assoc生成。

  • Clojure 中數據是值?(values),而非可變對象。如果不對 (assoc p :age 31)的結果綁定變量或傳遞到其他函數,該結果會被垃圾回收,且無法再次訪問。
  • 如果需要多次使用修改后的數據,必須顯式保存它(通過變量綁定、函數參數傳遞等)。

?assoc 主要支持?:Map、Vector、Record。
?不支持?:List、Set、基礎類型、Java 集合(需轉換)。
?設計原則?:通過限制類型保證操作的可預測性和性能。

(3)不可變性保證?

原數據始終不變,生成新版本:

(def original {:a 1})
(def modified (assoc original :b 2))
original   ; => {:a 1}    (未被修改)
modified   ; => {:a 1, :b 2}

(4)底層實現原理?

a. 對 Map 的實現?

基于 ?HAMT(Hash Array Mapped Trie)??:

  • 計算鍵的哈希值,定位到樹中對應路徑。
  • 復制路徑上的節點并更新值,未修改的分支共享。
  • 時間復雜度:平均 ?O(log?? n)?,近似O(1)。
??b. 對 Vector 的實現?

基于 ?持久化向量樹?:

  • 類似 HAMT,但使用索引而非哈希值定位路徑。
  • 復制修改路徑的節點,共享未修改部分。
  • 時間復雜度:?O(log?? n)?。

(5) 總結?

  • ?assoc? 是 Clojure 不可變數據操作的核心工具,適用于 Map/Vector/Record。
  • 特點?:無副作用、結構共享、高效生成新版本。
  • ?哲學?:通過值語義(Value Semantics)簡化并發和狀態管理。
    正如 RichHickey 所說:

“Assoc is the gateway to functional updates.” (assoc 是通向函數式更新的門戶。)

4. (assoc v1 2 99) 的具體過程

當執行 (assoc [1 2 3 4 5] 2 99) 時:

(1)定位修改路徑?

計算索引 2 的位置(第 3 個元素)
從根節點向下找到包含該元素的葉子節點

?(2)路徑復制?

復制從根節點到目標葉子節點的整條路徑?
新路徑指向新值(99),其他分支保持原引用

修改后的樹結構:Root'                  (新根節點)/   |   \[1,2] [99,4] [5]            (僅修改了第二個分支)↑      ↑     ↑共享   新節點  共享

(3)結構共享?

未被修改的分支(如 [1,2] 和 [5])?仍被新舊版本共享?
只有被修改的路徑(圖中 [3,4] → [99,4])需要創建新節點

這種設計使得"修改"操作在邏輯上生成新數據,在物理上卻智能共享不變部分,完美平衡了函數式編程的純粹性與實際性能需求。正如 Rich Hickey 所說:

“Persistent data structures are the bridge between functional purity
and practical efficiency.” (持久化數據結構是函數式純粹性與實際效率之間的橋梁。)

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

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

相關文章

面試150 二叉樹展開為鏈表

思路 思路:使用列表存儲先序遍歷的相關節點。然后遍歷列表,分別獲取前驅節點和當前節點,將前驅節點的左指針指向空,前驅節點的右指針指向當前節點。 # Definition for a binary tree node. # class TreeNode: # def __init__(self, val0, leftNone, …

代碼隨想錄算法訓練營第十七天

目錄 LeetCode.654 最大二叉樹 題目鏈接 最大二叉樹 題解 解題思路 LeetCode.617 合并二叉樹 題目鏈接 合并二叉樹 題解 解題思路 LeetCode.700 二叉搜索樹中的搜索 題目鏈接 二叉搜索樹中的搜索 題解 解題思路 解題思路 LeetCode.98 驗證二叉搜索樹 題目鏈接 驗…

pycharm無法識別pip安裝的包

在使用conda創建一個新的環境后,有些包通過pip的方式安裝更方便有效,若在pip安裝后,遇到該環境沒有此包,或pycharm監測不到此包,通常是pip的環境指向有問題。 解決措施: # 首先檢查當前pip的指向 which pip…

Elasticsearch 的 `modules` 目錄

Elasticsearch 的 modules 目錄是存放**核心功能模塊**的目錄,這些模塊是 Elasticsearch 運行所必需的基礎組件,**隨官方發行版一起提供**,但設計上允許通過移除或替換模塊來**定制化部署**(比如構建一個最小化的 Elasticsearch 實…

https——TCP+TLS

https——TCPTLS主題:基于mbedtls-2.16.0,驗證TLS會話復用功能驗證環境:1.TLS服務端2.TLS客戶端2.1 基于Sesssion ID2.1.1mbedtls-2.16.0庫的宏配置2.1.2 初始化配置2.1.3 TCP連接2.1.4 首次TLS連接2.1.4.1 發送加密算法列表2.1.4.2 選擇加密…

uni-app uni-push 2.0推送圖標不展示問題

問題現象:我在uni-app的配置文件,配置了推送的大圖標小圖標發現在真機測試無法展示配置的推送圖標問題 官網文檔:開通 | uni-app官網 解決方法: 在uni-app官網中說的并不是很清楚只給了一個簡單的示例,配置并沒有告訴我…

scp:上傳大型數據集到實驗室服務器

我通過百度網盤下載了大概200GB的LUNA-2016的肺結節CT數據。實驗是在實驗室服務器上進行的,我現在需要將本地的數據集傳輸到實驗室的服務器上。我已經通過remote-ssh連接上了實驗室的服務器,但是如果通過這個插件上傳數據的話,一方面不支持上…

量子計算突破:8比特擴散模型實現指數級加速

目錄 一、量子擴散模型(Quantum Diffusion) 二、DNA存儲生成(Biological-GAN) 三、光子計算加速 四、神經形態生成 五、引力場渲染 六、分子級生成 七、星際生成網絡 八、元生成系統 極限挑戰方向 一、量子擴散模型&…

Flask3.1打造極簡CMS系統

基于Flask 3.1和Python 3.13的簡易CMS以下是一個基于Flask 3.1和Python 3.13的簡易CMS管理系統實現方案,包含核心功能和可運行代碼示例。環境準備安裝Flask和其他依賴庫:pip install flask3.1.0 flask-sqlalchemy flask-login配置數據庫在config.py中設置…

用 Node.js 構建模塊化的 CLI 腳手架工具,從 GitHub 下載遠程模板

本文將手把手帶你構建一個支持遠程模板下載、自定義項目名稱,并完成模塊化拆分的 CLI 腳手架工具,適用于初創項目、團隊內部工具或者開源項目快速初始化。🧩 為什么要自己造一個 CLI 腳手架? 在日常開發中,我們常用腳手…

08.如何正確關閉文件

如何正確關閉文件(File Handling Best Practices) 文件操作是日常開發中非常常見的任務,正確關閉文件對于避免資源泄漏尤為關鍵。錯誤的文件關閉方式可能導致文件未保存、鎖定或其他異常。 1. 常見的錯誤方式:手動 close() 許多初學者會手動調用 close() 關閉文件,這在異…

算法入門--動態規劃(C++)

深入淺出掌握動態規劃核心思想,圖文并茂實戰代碼 什么是動態規劃? 動態規劃(Dynamic Programming, DP) 是一種高效解決多階段決策問題的方法。它通過將復雜問題分解為重疊子問題,并存儲子問題的解(避免重…

[2025CVPR]GNN-ViTCap:用于病理圖像分類與描述模型

論文結構解析? 本文采用經典學術論文結構: ?引言?:闡述病理圖像分析的挑戰與現有方法局限性?相關工作?:系統梳理MIL、視覺語言預訓練和生物醫學語言模型三大領域?方法?:詳細闡述GNN-ViTCap四階段架構?實驗?:在BreakHis和PatchGastric數據集驗證性能?討論?:通…

Java SE--圖書管理系統模擬實現

一.設計思路首先這個系統可以由倆種用戶使用,分別為管理者用戶和普通者用戶,根據不同的用戶有不同的界面,每個界面有不同的功能。二.代碼實現創建三個包和一個類book包:包括Book類和Booklist類Book類:package book; pu…

[RPA] 批量數據抓取指定商品名稱信息

影刀RPA案例:批量數據抓取指定商品名稱信息流程圖:操作步驟:涉及的影刀RPA大致指令: 1. 打開影刀商城頁面 2. 使用【填寫輸入框(web)】指令輸入用戶名和密碼,并點擊"登錄"按鈕 3. 切換到"訂單管理"…

我的世界Java版1.21.4的Fabric模組開發教程(十四)方塊實體

這是適用于Minecraft Java版1.21.4的Fabric模組開發系列教程專欄第十四章——方塊實體。想要閱讀其他內容,請查看或訂閱上面的專欄。 方塊實體(Block Entity) 指的是一種用于存儲方塊額外數據的方法。但這種數據和為了控制方塊狀態而在自定義方塊類中創建的屬性不太…

【UE教程/進階】UE中的指針與引用

目錄直接屬性引用共享指針 TSharedPtr實現原理共享引用 TSharedRef弱引用指針 TWeakPtrObject弱指針 FWeakPtr實現原理Object軟指針 FSoftObjectPtr原理直接屬性引用 在c通過UPROPERTY()宏將屬性公開,藍圖中屬性類型中的Object Reference。 對一個類型及其子類型的…

早期 CNN 的經典模型—卷積神經網絡(LeNet)

目錄 LeNet 的設計背景與目標 LeNet 的網絡結構(經典 LeNet-5) 局部感受野詳解 一、局部感受野和全連接網絡的區別 1. 傳統全連接網絡的問題 2. 局部感受野的解決方案 二、局部感受野的優勢 1. 參數大幅減少 2. 提取局部特征 3. 平移不變性 參數…

RabbitMQ 高級特性之延遲隊列

1. 簡介 在某些場景下,當生產者發送消息后,可能不需要讓消費者立即接收到,而是讓消息延遲一段時間后再發送給消費者。 2. 實現方式 2.1 TTL 死信隊列 給消息設置過期時間后,若消息在這段時間內沒有被消費,就會將消…

uniapp app安卓下載文件 圖片 doc xls 數據流文件 app安卓本地路徑下載保存

//下載圖片 downloadToLocal() {plus.android.requestPermissions([android.permission.WRITE_EXTERNAL_STORAGE],(success) > {uni.saveImageToPhotosAlbum({filePath: /static/x.png,//本地地址success: () > {this.$refs.uToast.show({message: "模版下載成功&am…