循環引用
使用示例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對兒分別是什麼數據類型,而後分別處理 函數
local keystr = nil if type(k) == "string" then keystr = string.format("[\"%s\"]", k) elseif type(k) == "number" then keystr = string.format("[%d]", k) end
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
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
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