lua序列化(支持循環引用)

lua序列化

  • 支持key類型爲string, number
  • 支持value類型爲string, number, table, boolean
  • 支持循環引用
  • 支持加密序列化
  • 支持loadstring反序列化

使用示例git

local t = { a = 1, b = 2}
local g = { c = 3, d = 4,  t}
t.rt = g
local ser_str = ser(g)
local unser_table = loadstring(ser_str)()

 

原理詳解

採用遞歸序列化表的方式實現,而且支持循環引用。循環引用支持實現思路參考了雲風的序列化.
先說不考慮支持循環引用的簡單狀況,採用遞歸方式實現序列化很簡單github

直接使用type函數分別判斷表中key-value對兒分別是什麼數據類型,而後分別處理 函數

序列化key

local keystr = nil
if type(k) == "string" then 
    keystr = string.format("[\"%s\"]", k)
elseif type(k) == "number" then 
    keystr = string.format("[%d]", k)
end

 

序列化value

 

local valuestr = nil
if type(v) == "string" then 
    valuestr = string.format("\"%s\"", tostring(v))
elseif type(v) == "number" or type(v) == "boolean" then  
    valuestr = tostring(v)
elseif type(v) == "table" then
    valuestr = table_ser(v)
end

分別處理完key和value直接插入一個表容器中就能夠,最後在使用table.concat鏈接字符串就能夠,這裏要說一下lua中拼接字符串是個比較低效的行爲,這跟lua字符串實現有關,每拼接都會從新生成一個新串,因此字符串越長拼接會越慢
這裏使用table.concat,不但減小了拼接次數,並且這樣拼接效率比較高,由於是調用C拼接,而非Lua字符串的行爲加密

在大字符串鏈接中,咱們應避免..。應用table來模擬buffer,而後concat獲得最終字符串lua

return string.format("{%s}", table.concat(container, ","))

 

精簡後的代碼

 

local function table_ser(tablevalue)
    -- 記錄表中各項
    local container = {}
    for k, v in pairs(tablevalue) do
        -- 序列化key
        local keystr = nil
        if type(k) == "string" then 
            keystr = string.format("[\"%s\"]", k)
        elseif type(k) == "number" then 
            keystr = string.format("[%d]", k)
        end

        -- 序列化value
        local valuestr = nil
        if type(v) == "string" then 
            valuestr = string.format("\"%s\"", tostring(v))
        elseif type(v) == "number" or type(v) == "boolean" then 
            valuestr = tostring(v)
        elseif type(v) == "table" then
            valuestr = table_ser(v)
        end

        table.insert(container, string.format("%s=%s", keystr, valuestr))
    end
    return string.format("{%s}", table.concat(container, ","))
end

支持循環引用

其實普通序列化沒什麼好說的,重點在於對循環引用的支持思路參考了雲風的實現spa

使用一個表mark記錄全部序列化過的表,並記錄其全key(從根表到當前表的全路徑key)每次新序列化一個表時,首先查看是否已經序列化過,若沒有序列化則直接序列化, 若已經序列化過則處理以下:
在一個表assgin中記錄全部循環引用狀況,並給出正確賦值(由於循環引用不能直接序列化,
因此間接的在表構造以後賦值),最後能夠一塊兒拼接到一塊兒。code

table序列化實現以下:

 

local function table_ser(tablevalue, tablekey, mark, assign)
    -- 標記當前table, 並記錄其key名
    mark[tablevalue] = tablekey
    -- 記錄表中各項
    local container = {}
    for k, v in pairs(tablevalue) do
        -- 序列化key
        local keystr = nil
        if type(k) == "string" then 
            keystr = string.format("[\"%s\"]", k)
        elseif type(k) == "number" then 
            keystr = string.format("[%d]", k)
        end

        -- 序列化value
        local valuestr = nil
        if type(v) == "string" then 
            valuestr = string.format("\"%s\"", tostring(v))
        elseif type(v) == "number" or type(v) == "boolean" then 
            valuestr = tostring(v)
        elseif type(v) == "table" then
            -- 得到從根表到當前表項的完整key, tablekey(表明tablevalue的key), mark[v]表明table v的key
            local fullkey = string.format("%s%s", tablekey, keystr)
            if mark[v] then table.insert(assign, string.format("%s=%s", fullkey, mark[v]))
            else valuestr = table_ser(v, fullkey, mark, assign)
            end
        end

        if keystr and valuestr then
            local keyvaluestr = string.format("%s=%s", keystr, valuestr)
            table.insert(container, keyvaluestr)
        end
    end
    return string.format("{%s}", table.concat(container, ","))
end

調用table的序列化

local function ser(var, enc)
    assert(type(var)=="table")
    -- 標記全部出現的table, 並記錄其key, 用於處理循環引用
    local mark = {}
    -- 用於記錄循環引用的賦值語句
    local assign = {}
    -- 序列化表, ret字符串必須與後面的loca ret=%s中的ret相同,由於這個ret可能也會組織到結果字符串中。
    local ret = table_ser(var, "ret", mark, assign)
    local ret = string.format("local ret=%s %s return ret", ret, table.concat(assign, ";"))
    return (enc==nil or enc==true) and string.dump(loadstring(ret)) or ret
end

 

mark:處理循環引用最重要的就是mark表,它記錄了已經序列化的表和其完整key路徑
assgin: 記錄循環引用的後期賦值語句,將這些語句拼接到表構造以外orm

序列化後就是一個lua的table建立並賦值的代碼字符串,因此可使用loadstring直接加載,加載後是一個chunk,能夠看成函數運行就返回結果 blog

序列化加密

string.dump(loadstring(ret))

這就是加密的代碼,由於string.dump參數必須是function, 因此使用loadstring將字符串加載成chunk,而後在由string.dump導成字節碼
其實就是使用了string.dump函數,它能夠把function導成二進制字節碼,使用它處理一下就能夠把明文字符串轉成字節碼了遞歸

完整代碼見我github中的luaser

相關文章
相關標籤/搜索