lua數組中使用nil值,可能會導致意想不到的結果,淺析其中原因及解決辦法。
先看以下lua代碼用例
local t = {1, 2, 3, 4, 5, 6}
print("Test1 " .. #t)
t[6] = nil
print("Test2 " .. #t)
t[4] = nil
print("Test3 " .. #t)
t[2] = nil
print("Test4 " .. #t)
我們使用LuaJIT 2.1執行這個用例,結果如下
# luajit test.lua
Test1 6
Test2 5
Test3 3
Test4 1
使用Lua 5.1.4執行這個用例,結果如下
# lua test.lua
Test1 6
Test2 5
Test3 3
Test4 3
可以發現,我們本意是想刪除數組中的元素,每次調用完數組長度應該是減1,但測試結果卻令人匪夷所思。這是為什么呢。
查看官方手冊,這里直接引用 Lua 5.1 manual 上的原話:
The length of a table t is defined to be any integer index n such that t[n] is not nil and t[n+1] is nil; moreover, if t[1] is nil, n can be zero. For a regular array, with non-nil values from 1 to a given n, its length is exactly that n, the index of its last value. If the array has "holes" (that is, nil values between other non-nil values), then #t can be any of the indices that directly precedes a nil value (that is, it may consider any such nil value as the end of the array).
總結就是,對于常規數組,里面從1到n放著一些非空的值的時候,它的長度就精確的為 n。但如果數組中間被掏空,即 nil 值被夾在非空值之間,那么任意 nil 值前面的索引都有可能是 # 操作符返回的值,所以 #t 的結果才這么奇怪。
除了 # 操作符結果異常外,所有受帶nil數組的長度影響的操作都有可能出問題,比如說unpack 、table.getn、ipairs 。
-
table.getn:同 # 操作符 -
ipairs:遍歷的時候遇到任意 nil 可能導致后面的元素沒遍歷到 -
unpack:遇到 nil 返回值會缺失
所以在lua代碼中盡量不要在數組中使用nil,值得一提的是,這些無法預估的現象在測試中是相對難發現的,這些代碼跑在生產環境中是個不小的隱患。
解決方法有以下:
-
使用table.remove來刪除數組元素,取代賦值nil。注意的是,remove之后數組長度發生變化,同時改變的還有數組元素的索引,比如刪掉第二個元素后,使用t[2]才能索引到原來的t[3]。有這種指定索引值的場景需要注意一下。
-
對于空數據,不適用nil,使用別的約定的值去替代。ngx_lua里有兩個比較適合的候選:false和ngx.null。這種方式實際上就是用了個約定的“占位符”來替代nil,因此數組長度是不變的,索引也不會發生變化。
-
false:在lua里false也是個假值,所以涉及到
if condition或res or default_value相關代碼也不需要調整。 -
ngx.null:如果false本身可能是個返回值,就考慮用
ngx.null。但注意ngx.null是個真值,相關代碼可能需要調整,需要自己在代碼中判斷if res ~= ngx.null,來判斷是否是空數據
-