OpenResty最佳实践笔记(3)

转自OpenResty最佳实践

Lua 基本概述

Lua 是一个小巧的脚本语言

巴西里约热内卢天主教大学(Pontifical Catholic University of Rio de Janeiro)里的一个研究小组,由 Roberto Ierusalimschy、Waldemar Celes 和 Luiz Henrique de Figueiredo 所组成并于 1993 年开发。

设计目的 是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能

Lua 不适合作为开发独立应用程序的语言

Lua 脚本可以很容易的被 C/C++ 代码调用,也可以反过来调用 C/C++ 的函数,这使得 Lua 在应用程序中可以被广泛应用。 不仅仅作为扩展脚本,也可以作为普通的配置文件,代替 XML、ini 等文件格式,并且更容易理解和维护。

标准 Lua 5.1 解释器由标准 C 编写而成,代码简洁优美,几乎在所有操作系统和平台上都可以编译和运行;

一个完整的标准 `Lua 5.1` 解释器不足 `200KB`

推荐使用的 LuaJIT 2 的代码大小也只有不足 500KB,同时也支持大部分常见的体系结构。 在目前所有脚本语言引擎中,LuaJIT 2 实现的速度应该算是最快的之一。 这一切都决定了 Lua 是作为嵌入式脚本的最佳选择。

Lua 语言的各个版本是不相兼容的。

Lua 环境搭建

  • windows

OpenResty1.9.3.2开始对外同时公布维护了 Windows 版本, 其中直接包含了编译好的最新版本 LuaJIT

  • linux
# wget http://luajit.org/download/LuaJIT-2.1.0-beta1.tar.gz
# tar -xvf LuaJIT-2.1.0-beta1.tar.gz
# cd LuaJIT-2.1.0-beta1
# make
# sudo make install


# 由于LuaJIT 2.1 目前还是beta版本,所以在make install后,并没有进行luajit的符号连接,
# 可以执行下面的指令将luajit-2.1.0-beta1和luajit进行软连接,从而可以直接使用luajit命令
ln -sf luajit-2.1.0-beta1 /usr/local/bin/luajit

# 验证 LuaJIT 是否安装成功
# luajit -v
LuaJIT 2.1.0-beta1 -- Copyright (C) 2005-2015 Mike Pall.
http://luajit.org/

编辑器 JetBrains

  • 安装 emmylua 插件

使用JetBrains的编辑器, File->Settings->Plugins安装 emmylua 插件

pycharm phpstorm都可以

Lua语法

数据类型


函数 type 能够返回一个值或一个变量所属的类型。

print(type("hello world")) -->output:string
print(type(print))         -->output:function
print(type(true))          -->output:boolean
print(type(360.0))         -->output:number
print(type(nil))           -->output:nil

  • nil(空)

nil 是一种类型,Lua 将 nil 用于表示“无效值”。 一个变量在第一次赋值前的默认值是 nil

将 nil 赋予给一个全局变量就等同于删除它

local num
print(num)        -->output:nil

num = 100
print(num)        -->output:100

OpenResty 的 Lua 接口还提供了一种特殊的空值,即 ngx.null, 用来表示不同于 nil 的“空值”

  • boolean(布尔)

布尔类型,可选值 true/false; Lua 中 nil 和 false 为“假”, 其它所有值均为“真”。

比如 0 和空字符串就是“真”

C 或者 Perl 程序员或许会对此感到惊讶。
local a = true
local b = 0
local c = nil
if a then
    print("a")        -->output:a
else
    print("not a")    --这个没有执行
end

if b then
    print("b")        -->output:b
else
    print("not b")    --这个没有执行
end

if c then
    print("c")        --这个没有执行
else
    print("not c")    -->output:not c
end

  • number(数字)

Number 类型用于表示实数,和 C/C++ 里面的 double 类型很类似。 可以使用数学函数 math.floor(向下取整)math.ceil(向上取整)进行取整操作。

local order = 3.99
local score = 98.01
print(math.floor(order))   -->output:3
print(math.ceil(score))    -->output:99

一般地,Luanumber 类型就是用双精度浮点数来实现的。

LuaJIT 支持所谓的“dual-number”(双数)模式, 即 LuaJIT 会根据上下文用整型来存储整数,而用双精度浮点数来存放浮点数。

print(9223372036854775807LL - 1) -- 9223372036854775806

  • string 字符串

Lua 的字符串是不可改变的值

Lua 中有三种方式表示字符串:

1 使用一对匹配的单引号。例:'hello'。

2 使用一对匹配的双引号。例:"abclua"。

3 字符串还可以用一种长括号(即[[ ]])括起来的方式定义

    把两个正的方括号(即[[)间插入 n 个等号定义为第 n 级正长括号。
    就是说,0 级正的长括号写作 [[ ,一级正的长括号写作 [=[,如此等等
local str1 = 'hello world'
local str2 = "hello lua"
local str3 = [["add\name",'hello']]
local str4 = [=[string have a [[]].]=]

print(str1)    -->output:hello world
print(str2)    -->output:hello lua
print(str3)    -->output:"add\name",'hello'
print(str4)    -->output:string have a [[]].

Lua 实现中,Lua 字符串一般都会经历一个“内化”(intern)的过程, 即两个完全一样的 Lua 字符串Lua 虚拟机中只会存储一份。

每一个 Lua 字符串在创建时都会插入到 Lua 虚拟机内部的一个全局的哈希表

- 创建相同的 Lua 字符串并不会引入新的动态内存分配操作,所以相对便宜(但仍有全局哈希表查询的开销),

- 内容相同的 Lua 字符串不会占用多份存储空间,

- 已经创建好的 Lua 字符串之间进行相等性比较时是 O(1) 时间度的开销,而不是通常见到的 O(n).

  • table 表 (关联数组)

在 Lua 中,数组下标从 1 开始计数。

官方解释:Lua lists have a base index of 1 because it was thought to be most friendly for non-programmers, as it makes indices correspond to ordinal element positions.
local corp = {
    web = "www.google.com",   --索引为字符串,key = "web",
                              --              value = "www.google.com"
    telephone = "12345678",   --索引为字符串
    staff = {"Jack", "Scott", "Gary"}, --索引为字符串,值也是一个表
    100876,              --相当于 [1] = 100876,此时索引为数字
                         --      key = 1, value = 100876
    100191,              --相当于 [2] = 100191,此时索引为数字
    [10] = 360,          --直接把数字索引给出
    ["city"] = "Beijing" --索引为字符串
}

print(corp.web)               -->output:www.google.com
print(corp["telephone"])      -->output:12345678
print(corp[2])                -->output:100191
print(corp["city"])           -->output:"Beijing"
print(corp.staff[1])          -->output:Jack
print(corp["staff"][1])       -->output:Jack
print(corp[10])               -->output:360

  • function 函数
local function foo()
    print("in the function")
    --dosomething()
    local x = 10
    local y = 20
    return x + y
end

local a = foo    --把函数赋给变量

print(a())

--output:
--in the function
--30


---1 全局

function foo()
end
--- 等价于

foo = function ()
end


-- 2 局部

local function foo()
end
---等价于

local foo = function ()
end

表达式

  • 算术运算符
算术运算符 说明
+ 加法
- 减法
* 乘法
/ 除法
^ 指数
% 取模
print(1 + 2)       -->打印 3
print(5 / 10)      -->打印 0.5。 这是Lua不同于c语言的
print(5.0 / 10)    -->打印 0.5。 浮点数相除的结果是浮点数
-- print(10 / 0)   -->注意除数不能为0,计算的结果会出错
print(2 ^ 10)      -->打印 1024。 求2的10次方

local num = 1357
print(num % 2)       -->打印 1
print((num % 2) == 1) -->打印 true。 判断num是否为奇数
print((num % 5) == 0)  -->打印 false。判断num是否能被5整数
  • 关系运算符

Lua 语言中“不等于”运算符的写法为:~=

关系运算符 说明
< 小于
> 大于
<= 小于等于
>= 大于等于
== 等于
~= 不等于
print(1 < 2)    -->打印 true
print(1 == 2)   -->打印 false
print(1 ~= 2)   -->打印 true
local a, b = true, false
print(a == b)  -->打印 false

--- table 
local a = { x = 1, y = 0}
local b = { x = 1, y = 0}
if a == b then
  print("a==b")
else
  print("a~=b")
end

---output:
a~=b

由于 Lua 字符串总是会被“内化”

即相同内容的字符串只会被保存一份

因此 Lua 字符串之间的相等性比较可以简化为其内部存储地址的比较。

这意味着 Lua 字符串的相等性比较总是为 O(1). 而在其他编程语言中,字符串的相等性比较则通常为 O(n),即需要逐个字节(或按若干个连续字节)进行比较

  • 逻辑运算符
逻辑运算符 说明
and 逻辑与
or 逻辑或
not 逻辑非
local c = nil
local d = 0
local e = 100
print(c and d)  -->打印 nil
print(c and e)  -->打印 nil
print(d and e)  -->打印 100
print(c or d)   -->打印 0
print(c or e)   -->打印 100
print(not c)    -->打印 true
print(not d)    -->打印 false

所有逻辑操作符将 false 和 nil 视作其他任何值视作

对于 and 和 or“短路求值” 对于 not,永远只返回 true 或者 false

  • 字符串连接

1) 连接两个字符串,可以使用操作符“..”(两个点)

如果其任意一个操作数是数字的话,Lua 会将这个数字转换成字符串
print("Hello " .. "World")    -->打印 Hello World
print(0 .. 1)                 -->打印 01

2) string 库函数 string.format 连接字符串

str1 = string.format("%s-%s","hello","world")
print(str1)              -->打印 hello-world

str2 = string.format("%d-%s-%.2f",123,"world",1.21)
print(str2)              -->打印 123-world-1.21

3) 推荐使用 tabletable.concat() 来进行很多字符串的拼接

由于 Lua 字符串本质上是只读的,因此字符串连接运算符几乎总会创建一个 新的(更大的)字符串。

这意味着如果有很多这样的连接操作(比如在循环中使用 .. 来拼接最终结果),则性能损耗会非常大。
local pieces = {}
for i, ele in ipairs(my_list) do 
    pieces[i] = my_list(ele)
end 

local res = table.concat(pieces)
  • 优先级
优先级
^
not   # -
*   /   %
+   -
..
< > <=  >=  ==  ~=
and
or
local a, b = 1, 2
local x, y = 3, 4
local i = 10
local res = 0
res = a + i < b/2 + 1  -->等价于res =  (a + i) < ((b/2) + 1)
res = 5 + x^2*8        -->等价于res =  5 + ((x^2) * 8)
res = a < y and y <=x  -->等价于res =  (a < y) and (y <= x)

流程控制

  • if-else
score = 90
if score == 100 then
    print("Very good!Your score is 100")
elseif score >= 60 then
    print("Congratulations, you have passed it,your score greater or equal to 60")
--此处可以添加多个elseif
else
    print("Sorry, you do not pass the exam! ")
end
  • while
x = 1
sum = 0

while x <= 5 do
    sum = sum + x
    x = x + 1
end
print(sum)  -->output 15
  • repeat

执行 repeat 循环体后,直到 until 的条件为真时结束

x = 10

repeat
    x = x - 1
    print(x)
until x <= 0
  • for

for 语句有两种形式:数字 for(numeric for)范型 for(generic for)

1) 数字 for(numeric for)


for var = begin, finish, step do
    --body
end


for i = 1, 10, 2 do
  print(i)
end

-- output:
1
3
5
7
9

--- 不想给循环设置上限的话,可以使用常量 math.huge:

for i = 1, math.huge do
    if (0.3*i^3 - 20*i^2 - 500 >=0) then
      print(i)
      break
    end
end

2) for 泛型 通过一个迭代器(ipairs)

local days = {
   "Monday", "Tuesday", "Wednesday", "Thursday",
   "Friday", "Saturday","Sunday"
}

local revDays = {}
for k, v in pairs(days) do
  revDays[v] = k
end

-- print value
for k,v in pairs(revDays) do
  print("k:", k, " v:", v)
end

-- output:
k:  Tuesday   v: 2
k:  Monday    v: 1
k:  Sunday    v: 7
k:  Thursday  v: 4
k:  Friday    v: 5
k:  Wednesday v: 3
k:  Saturd
  • break, return, goto

-- 1 break

sum = 10
i = 1
while true do
    sum = sum + 1
    if sum > 100 then
        break
    end
    i = i + 1
end

print("result = ".. sum .. "; with i = " .. i)


-- return  

-- 将 return 放在一个 do ... end 代码块中

local function foo()
    print("before")
    do return end
    print("after")  -- 这一行语句永远不会执行到
end
local function foo()
    print("before")
    return
    print("after")
end


s = foo()
print(s)

--before
--nil


local function foo()
    print("before")
    return
    print("after")
end


s = foo()
print(s)

--before
--after
--nil


-- goto

-- 有了 goto,我们可以实现 continue 的功能
-- [自定义log结构快]

for i=1, 3 do
    if i <= 2 then
        print(i, "yes logged")
        goto log
    end

    do return end
    :: log ::
    print("logged")
end

Lua 函数

Lua使用关键字 function 定义函数

function function_name (arc)  -- arc 表示参数列表,函数的参数列表可以为空
   -- body
end

-- 等价于

function_name = function (arc)
  -- body
end

-- 例子

local function max(a, b)  --定义函数 max,用来求两个数的最大值,并返回
   local temp       --使用局部变量 temp,保存最大值
   if(a > b) then
      temp = a
   else
      temp = b
   end
   return temp            --返回最大值
end

local m = max(-12, 20)    --调用函数 max,找去 -12 和 20 中的最大值
print(m)


  • 函数参数
-- 1) 按值传递
local function fun1(a, b)       --两个形参,多余的实参被忽略掉
   print(a, b)
end

local function fun2(a, b, c, d) --四个形参,没有被实参初始化的形参,用nil初始化
   print(a, b, c, d)
end

local x = 1
local y = 2
local z = 3

fun1(x, y, z)         -- z被函数fun1忽略掉了,参数变成 x, y
fun2(x, y, z)         -- 后面自动加上一个nil,参数变成 x, y, z, nil


-- 2) 变长参数

-- LuaJIT 2 尚不能 JIT 编译这种变长参数的用法,只能解释执行。所以对性能敏感的代码,应当避免使用此种形式
local function func( ... )                -- 形参为 ... ,表示函数采用变长参数

   local temp = {...}                     -- 访问的时候也要使用 ...
   local ans = table.concat(temp, " ")    -- 使用 table.concat 库函数对数
                                          -- 组内容使用 " " 拼接成字符串。
   print(ans)
end

func(1, 2)        -- 传递了两个参数
func(1, 2, 3, 4)  -- 传递了四个参数


-- 3)  具名参数

local function change(arg) -- change 函数,改变长方形的长和宽,使其各增长一倍
  arg.width = arg.width * 2
  arg.height = arg.height * 2
  return arg
end

local rectangle = { width = 20, height = 15 }
print("before change:", "width  =", rectangle.width,
                        "height =", rectangle.height)
rectangle = change(rectangle)
print("after  change:", "width  =", rectangle.width,
                        "height =", rectangle.height)



-- 4) 按引用传递

function change(arg) --change函数,改变长方形的长和宽,使其各增长一倍
  arg.width = arg.width * 2  --表arg不是表rectangle的拷贝,他们是同一个表
  arg.height = arg.height * 2
end                  -- 没有return语句了

local rectangle = { width = 20, height = 15 }
print("before change:", "width = ", rectangle.width,
                        " height = ", rectangle.height)
change(rectangle)
print("after change:", "width = ", rectangle.width,
                       " height =", rectangle.height)

  • 函数的返回值

允许函数返回多个值


function init()             --init 函数 返回两个值 1 和 "lua"
  return 1, "lua"
end

x = init()
print(x)

x, y, z = init()
print(x, y, z)

--output
1
1 lua nil


-- 如果你确保只取函数返回值的第一个值,可以使用括号运算符

local function init()
    return 1, "lua"
end

print((init()), 2)   -->output  1  2
print(2, (init()))   -->output  2  1

模块

Lua提供了一个名为 require 的函数用来加载模块

Lua 中创建一个模块最简单的方法是:创建一个 table, 并将所有需要导出的函数放入其中,最后返回这个 table 就可以了。 相当于将导出的函数作为 table 的一个字段, 在 Lua 中函数是第一类值,提供了天然的优势。


-- my.lua

local _M = {}

local function get_name()
    return "Lucy"
end

function _M.greeting()
    print("hello " .. get_name())
end

return _M


-- main.lua
local my_module = require("my")
my_module.greeting()     -->output: hello Lucy

对于需要导出给外部使用的公共模块,处于安全考虑,是要避免全局变量的出现

我们可以使用 lj-relengluacheck 工具完成全局变量的检测

#### String 库

string.byte(s [, i [, j ]])

返回字符 s[i]、s[i + 1]、s[i + 2]、······、s[j] 所对应的 ASCII 码。
i 的默认值为 1,即第一个字节,j 的默认值为 i 。

示例代码


print(string.byte("abc", 1, 3))
print(string.byte("abc", 3)) -- 缺少第三个参数,第三个参数默认与第二个相同,此时为 3
print(string.byte("abc"))    -- 缺少第二个和第三个参数,此时这两个参数都默认为 1

-->output
97    98    99
99
97
由于 string.byte 只返回整数,而并不像 string.sub 等函数那样(尝试)创建新的 Lua 字符串
因此使用 string.byte 来进行字符串相关的扫描和分析是最为高效的,尤其是在被 LuaJIT 2 所 JIT 编译之后。

string.char (...)

接收 0 个或更多的整数(整数范围:0~255),返回这些整数所对应的 ASCII 码字符组成的字符串。
当参数为空时,默认是一个 0。
print(string.char(96, 97, 98))
print(string.char())        -- 参数为空,默认是一个0,
                            -- 你可以用string.byte(string.char())测试一下
print(string.char(65, 66))

--> output
`ab

AB
此函数特别适合从具体的字节构造出二进制字符串。
这经常比使用 table.concat 函数和 .. 连接运算符更加高效。

string.upper(s)

接收一个字符串 s,返回一个把所有小写字母变成大写字母的字符串。
print(string.upper("Hello Lua"))  -->output  HELLO LUA

string.lower(s)

接收一个字符串 s,返回一个把所有大写字母变成小写字母的字符串。
print(string.lower("Hello Lua"))  -->output   hello lua

string.len(s)

接收一个字符串,返回它的长度。
print(string.len("hello lua")) -->output  9

print(#"hello lua") -->output  9
使用此函数是不推荐的。
应当总是使用 # 运算符来获取 Lua 字符串的长度。

由于 Lua 字符串的长度是专门存放的,并不需要像 C 字符串那样即时计算
,因此获取字符串长度的操作总是 O(1) 的时间复杂度。

string.find(s, p [, init [, plain]])

在 s 字符串中第一次匹配 p 字符串。
若匹配成功,则返回 p 字符串在 s 字符串中出现的开始位置和结束位置;
若匹配失败,则返回 nil。
 
第三个参数 init 默认为 1,并且可以为负整数,当 init 为负数时,
表示从 s 字符串的 string.len(s) + init + 1 索引处开始向后匹配字符串 p 。 
第四个参数默认为 false,当其为 true 时,只会把 p 看成一个字符串对待。
local find = string.find
print(find("abc cba", "ab"))
print(find("abc cba", "ab", 2))     -- 从索引为2的位置开始匹配字符串:ab
print(find("abc cba", "ba", -1))    -- 从索引为7的位置开始匹配字符串:ba
print(find("abc cba", "ba", -3))    -- 从索引为5的位置开始匹配字符串:ba
print(find("abc cba", "(%a+)", 1))  -- 从索引为1处匹配最长连续且只含字母的字符串
print(find("abc cba", "(%a+)", 1, true)) --从索引为1的位置开始匹配字符串:(%a+)

-->output
1   2
nil
nil
6   7
1   3   abc
nil

对于 LuaJIT 这里有个性能优化点,对于 string.find 方法,当只有字符串查找匹配时,
是可以被 JIT 编译器优化的,有关 JIT 可以编译优化清单,大家可以参考 http://wiki.luajit.org/NYI,性能提升是非常明显的,通常是 100 倍量级。这里有个的例子,大家可以参考 https://groups.google.com/forum/m/#!topic/openresty-en/rwS88FGRsUI。

string.format(formatstring, ...)

按照格式化参数 formatstring,返回后面 ... 内容的格式化版本
编写格式化字符串的规则与标准 c 语言中 printf 函数的规则基本相同:
它由常规文本和指示组成,这些指示控制了每个参数应放到格式化结果的什么位置,
及如何放入它们。一个指示由字符 % 加上一个字母组成,这些字母指定了如何格式化参数,
例如 d 用于十进制数、x 用于十六进制数、o 用于八进制数、f 用于浮点数、s 用于字符串等
在字符 % 和字母之间可以再指定一些其他选项,用于控制格式的细节。
print(string.format("%.4f", 3.1415926))     -- 保留4位小数
print(string.format("%d %x %o", 31, 31, 31))-- 十进制数31转换成不同进制
d = 29; m = 7; y = 2015                     -- 一行包含几个语句,用;分开
print(string.format("%s %02d/%02d/%d", "today is:", d, m, y))

-->output
3.1416
31 1f 37
today is: 29/07/2015

string.match(s, p [, init])

在字符串 s 中匹配(模式)字符串 p,若匹配成功,则返回目标字符串中与模式匹配的子串;否则返回 nil。
第三个参数 init 默认为 1,并且可以为负整数,当 init 为负数时,
表示从 s 字符串的 string.len(s) + init + 1 索引处开始向后匹配字符串 p。
print(string.match("hello lua", "lua"))
print(string.match("lua lua", "lua", 2))  --匹配后面那个lua
print(string.match("lua lua", "hello"))
print(string.match("today is 27/7/2015", "%d+/%d+/%d+"))

-->output
lua
lua
nil
27/7/2015
string.match 目前并不能被 JIT 编译,应 尽量 使用 ngx_lua 模块提供的 ngx.re.match 等接口。

string.gmatch(s, p)

返回一个迭代器函数,通过这个迭代器函数可以遍历到在字符串 s 中出现模式串 p 的所有地方。
s = "hello world from Lua"
for w in string.gmatch(s, "%a+") do  --匹配最长连续且只含字母的字符串
    print(w)
end

-->output
hello
world
from
Lua


t = {}
s = "from=world, to=Lua"
for k, v in string.gmatch(s, "(%a+)=(%a+)") do  --匹配两个最长连续且只含字母的
    t[k] = v                                    --字符串,它们之间用等号连接
end
for k, v in pairs(t) do
print (k,v)
end

-->output
to      Lua
from    world

此函数目前并不能被 LuaJIT 所 JIT 编译,而只能被解释执行。
应 尽量 使用 ngx_lua 模块提供的 ngx.re.gmatch 等接口。

string.rep(s, n)

返回字符串 s 的 n 次拷贝。
print(string.rep("abc", 3)) --拷贝3次"abc"

-->output  abcabcabc

string.sub(s, i [, j])

返回字符串 s 中,索引 i 到索引 j 之间的子字符串。
当 j 缺省时,默认为 -1,也就是字符串 s 的最后位置。
i 可以为负数。当索引 i 在字符串 s 的位置在索引 j 的后面时,将返回一个空字符串。
print(string.sub("Hello Lua", 4, 7))
print(string.sub("Hello Lua", 2))
print(string.sub("Hello Lua", 2, 1))    --看到返回什么了吗
print(string.sub("Hello Lua", -3, -1))

-->output
lo L
ello Lua

Lua

如果你只是想对字符串中的单个字节进行检查,使用 string.char 函数通常会更为高效。

string.gsub(s, p, r [, n])

将目标字符串 s 中所有的子串 p 替换成字符串 r。可选参数 n,表示限制替换次数。
返回值有两个,第一个是被替换后的字符串,第二个是替换了多少次。
print(string.gsub("Lua Lua Lua", "Lua", "hello"))
print(string.gsub("Lua Lua Lua", "Lua", "hello", 2)) --指明第四个参数

-->output
hello hello hello   3
hello hello Lua     2

此函数不能为 LuaJIT 所 JIT 编译,而只能被解释执行。

一般我们推荐使用 ngx_lua 模块提供的 ngx.re.gsub 函数。

string.reverse (s)

接收一个字符串 s,返回这个字符串的反转。
print(string.reverse("Hello Lua"))  --> output: auL olleH

table 库

table.getn 获取长度

取长度操作符写作一元操作 #

字符串的长度是它的字节数(就是以一个字符一个字节计算的字符串长度)。

对于常规的数组,里面从 1 到 n 放着一些非空的值的时候,它的长度就精确的为 n, 即最后一个值的下标。

如果数组有一个“空洞”(就是说,nil 值被夹在非空值之间), 那么 #t 可能是指向任何一个是 nil 值的前一个位置的下标

(就是说,任何一个 nil 值都有可能被当成数组的结束)。
这也就说明对于有“空洞”的情况,table 的长度存在一定的 不可确定性。
local tblTest1 = { 1, a = 2, 3 }
print("Test1 " .. table.getn(tblTest1))

local tblTest2 = { 1, nil }
print("Test2 " .. table.getn(tblTest2))

local tblTest3 = { 1, nil, 2 }
print("Test3 " .. table.getn(tblTest3))

local tblTest4 = { 1, nil, 2, nil }
print("Test4 " .. table.getn(tblTest4))

local tblTest5 = { 1, nil, 2, nil, 3, nil }
print("Test5 " .. table.getn(tblTest5))

local tblTest6 = { 1, nil, 2, nil, 3, nil, 4, nil }
print("Test6 " .. table.getn(tblTest6))
我们使用 Lua 5.1  LuaJIT 2.1 分别执行这个用例,结果如下:

# lua test.lua
Test1 2
Test2 1
Test3 3
Test4 1
Test5 3
Test6 1
# luajit test.lua
Test1 2
Test2 1
Test3 1
Test4 1
Test5 1
Test6 1

不要在 Luatable 中使用 nil 值,如果一个元素要删除,直接 remove,不要用 nil去代替。

table.concat (table [, sep [, i [, j ] ] ])

对于元素是 string 或者 number 类型的表 table,返回 table[i]..sep..table[i+1] ··· sep..table[j] 连接成的字符串。

填充字符串 sep 默认为空白字符串。

起始索引位置 i 默认为 1,结束索引位置 j 默认是 table 的长度。
如果 i 大于 j,返回一个空字符串。
local a = {1, 3, 5, "hello" }
print(table.concat(a))              -- output: 135hello
print(table.concat(a, "|"))         -- output: 1|3|5|hello
print(table.concat(a, " ", 4, 2))   -- output:
print(table.concat(a, " ", 2, 4))   -- output: 3 5 hello

table.insert (table, [pos ,] value)

在(数组型)表 table 的 pos 索引位置插入 value,其它元素向后移动到空的地方。
pos 的默认值是表的长度加一,即默认是插在表的最后。
local a = {1, 8}             --a[1] = 1,a[2] = 8
table.insert(a, 1, 3)   --在表索引为1处插入3
print(a[1], a[2], a[3])
table.insert(a, 10)    --在表的最后插入10
print(a[1], a[2], a[3], a[4])

-->output
3    1    8
3    1    8    10

table.maxn (table)

返回(数组型)表 table 的最大索引编号;
如果此表没有正的索引编号,返回 0。

当长度省略时,此函数通常需要 O(n) 的时间复杂度来计算 table 的末尾。
因此用这个函数省略索引位置的调用形式来作 table 元素的末尾追加,是高代价操作。
local a = {}
a[-1] = 10
print(table.maxn(a))
a[5] = 10
print(table.maxn(a))

-->output
0
5
此函数的行为不同于 # 运算符,因为 # 可以返回数组中任意一个 nil 空洞或最后一个 nil 之前的元素索引。
当然,该函数的开销相比 # 运算符也会更大一些。

table.remove (table [, pos])

在表 table 中删除索引为 pos(pos 只能是 number 型)的元素,
并返回这个被删除的元素,它后面所有元素的索引值都会减一。
pos 的默认值是表的长度,即默认是删除表的最后一个元素。
local a = { 1, 2, 3, 4}
print(table.remove(a, 1)) --删除速索引为1的元素
print(a[1], a[2], a[3], a[4])

print(table.remove(a))   --删除最后一个元素
print(a[1], a[2], a[3], a[4])

-->output
1
2    3    4    nil
4
2    3    nil    nil

table.sort (table [, comp])

按照给定的比较函数 comp 给表 table 排序,也就是从 table[1] 到 table[n],
这里 n 表示 table 的长度。 

比较函数有两个参数,如果希望第一个参数排在第二个的前面,
就应该返回 true,否则返回 false。 

如果比较函数 comp 没有给出,默认从小到大排序。
local function compare(x, y) --从大到小排序
   return x > y         --如果第一个参数大于第二个就返回true,否则返回false
end

local a = { 1, 7, 3, 4, 25}
table.sort(a)           --默认从小到大排序
print(a[1], a[2], a[3], a[4], a[5])
table.sort(a, compare) --使用比较函数进行排序
print(a[1], a[2], a[3], a[4], a[5])

-->output
1    3    4    7    25
25    7    4    3    1

时间日期函数

Lua 中,函数 time、date 和 difftime 提供了所有的日期和时间功能

OpenResty 的世界里,不推荐使用这里的标准时间函数,因为这些函数通常会引发不止一个昂贵的系统调用, 同时无法为 LuaJIT JIT 编译,对性能造成较大影响。

推荐使用 ngx_lua 模块提供的带缓存的时间接口,如 ngx.today, ngx.time, ngx.utctime, ngx.localtime, ngx.now, ngx.http_time,以及 ngx.cookie_time

  • os.time ([table])
print(os.time())    -->output  1438243393
a = { year = 1970, month = 1, day = 1, hour = 8, min = 1 }
print(os.time(a))   -->output  60
  • os.difftime (t2, t1)
local day1 = { year = 2015, month = 7, day = 30 }
local t1 = os.time(day1)

local day2 = { year = 2015, month = 7, day = 31 }
local t2 = os.time(day2)
print(os.difftime(t2, t1))   -->output  86400
  • os.date ([format [, time]])
print(os.date("today is %A, in %B"))
print(os.date("now is %x %X"))

-->output
today is Thursday, in July
now is 07/30/15 17:39:22

数学计算库

函数名 函数功能
math.rad(x) 角度x转换成弧度
math.deg(x) 弧度x转换成角度
math.max(x, ...) 返回参数中值最大的那个数,参数必须是number型
math.min(x, ...) 返回参数中值最小的那个数,参数必须是number型
math.random ([m [, n]]) 不传入参数时,返回 一个在区间[0,1)内均匀分布的伪随机实数;只使用一个整数参数m时,返回一个在区间[1, m]内均匀分布的伪随机整数;使用两个整数参数时,返回一个在区间[m, n]内均匀分布的伪随机整数
math.randomseed (x) 为伪随机数生成器设置一个种子x,相同的种子将会生成相同的数字序列
math.abs(x) 返回x的绝对值
math.fmod(x, y) 返回 x对y取余数
math.pow(x, y) 返回x的y次方
math.sqrt(x) 返回x的算术平方根
math.exp(x) 返回自然数e的x次方
math.log(x) 返回x的自然对数
math.log10(x) 返回以10为底,x的对数
math.floor(x) 返回最大且不大于x的整数
math.ceil(x) 返回最小且不小于x的整数
math.pi 圆周率
math.sin(x) 求弧度x的正弦值
math.cos(x) 求弧度x的余弦值
math.tan(x) 求弧度x的正切值
math.asin(x) 求x的反正弦值
math.acos(x) 求x的反余弦值
math.atan(x) 求x的反正切值
print(math.pi)           -->output  3.1415926535898
print(math.rad(180))     -->output  3.1415926535898
print(math.deg(math.pi)) -->output  180

print(math.sin(1))       -->output  0.8414709848079
print(math.cos(math.pi)) -->output  -1
print(math.tan(math.pi / 4))  -->output  1

print(math.atan(1))      -->output  0.78539816339745
print(math.asin(0))      -->output  0

print(math.max(-1, 2, 0, 3.6, 9.1))     -->output  9.1
print(math.min(-1, 2, 0, 3.6, 9.1))     -->output  -1

print(math.fmod(10.1, 3))   -->output  1.1
print(math.sqrt(360))      -->output  18.97366596101

print(math.exp(1))         -->output  2.718281828459
print(math.log(10))        -->output  2.302585092994
print(math.log10(10))      -->output  1

print(math.floor(3.1415))  -->output  3
print(math.ceil(7.998))    -->output  8

-- 避免每次程序启动时得到的都是相同的伪随机数序列,通常是使用当前时间作为种子

math.randomseed (os.time())   --把100换成os.time()
print(math.random())          -->output 0.88369396038697
print(math.random(100))       -->output 66
print(math.random(100, 360))  -->output 228


文件操作

Lua I/O 库提供两种不同的方式处理文件:隐式文件描述显式文件描述

这些文件 I/O 操作,在 OpenResty 的上下文中对事件循环是会产生阻塞效应

OpenResty 比较擅长的是高并发网络处理,在这个环境中,任何文件的操作,都将阻塞其他并行执行的请求。

实际中的应用,在 OpenResty 项目中应尽可能让网络处理部分, 文件 I/0 操作部分相互独立,不要揉和在一起

  • 隐式文件描述

设置一个默认的输入或输出文件,然后在这个文件上进行所有的输入或输出操作。 所有的操作函数由 io 表提供。

file = io.input("test1.txt")    -- 使用 io.input() 函数打开文件

repeat
    line = io.read()            -- 逐行读取内容,文件结束时返回nil
    if nil == line then
        break
    end
    print(line)
until (false)

io.close(file)                  -- 关闭文件

--> output
my test file
hello
lua


file = io.open("test1.txt", "a+")   -- 使用 io.open() 函数,以添加模式打开文件
io.output(file)                     -- 使用 io.output() 函数,设置默认输出文件
io.write("hello world")           -- 使用 io.write() 函数,把内容写到文件
io.close(file)

  • 显式文件描述

使用 file:XXX() 函数方式进行操作, 其中 fileio.open() 返回的文件句柄

file = io.open("test2.txt", "r")    -- 使用 io.open() 函数,以只读模式打开文件

for line in file:lines() do         -- 使用 file:lines() 函数逐行读取文件
   print(line)
end

file:close()

-->output
my test2
hello lua

  • 文件操作函数

io.open (filename [, mode])

按指定的模式 mode,打开一个文件名为 filename 的文件,
成功则返回文件句柄,失败则返回 nil 加错误信息。
模式 含义 文件不存在时
"r" 读模式 (默认) 返回nil加错误信息
"w" 写模式 创建文件
"a" 添加模式 创建文件
"r+" 更新模式,保存之前的数据 返回nil加错误信息
"w+" 更新模式,清除之前的数据 创建文件
"a+" 添加更新模式,保存之前的数据,在文件尾进行添加 创建文件

注意 "w" "wb" 的区别

"w" 表示文本文件。某些文件系统(如 Linux 的文件系统)认为 0x0A 为文本文件的换行符,Windows 的文件系统认为 0x0D0A 为文本文件的换行符。为了兼容其他文件系统(如从 Linux 拷贝来的文件),Windows 的文件系统在写文件时,会在文件中 0x0A 的前面加上 0x0D。使用 "w",其属性要看所在的平台。

"wb" 表示二进制文件。文件系统会按纯粹的二进制格式进行写操作,因此也就不存在格式转换的问题。(Linux 文件系统下 "w" 和 "wb" 没有区别)

file:close ()

关闭文件。
注意:当文件句柄被垃圾收集后,文件将自动关闭。
句柄将变为一个不可预知的值。

io.close ([file])

关闭文件,和 file:close() 的作用相同。
没有参数 file 时,关闭默认输出文件。

file:flush ()

把写入缓冲区的所有数据写入到文件 file 中。

io.flush ()

相当于 file:flush(),把写入缓冲区的所有数据写入到默认输出文件。

io.input ([file])

当使用一个文件名调用时,打开这个文件(以文本模式),并设置文件句柄为默认输入文件; 当使用一个文件句柄调用时,设置此文件句柄为默认输入文件; 当不使用参数调用时,返回默认输入文件句柄。

file:lines ()

返回一个迭代函数, 每次调用将获得文件中的一行内容, 当到文件尾时,将返回 nil,但不关闭文件。

io.lines ([filename])

打开指定的文件 filename 为读模式并返回一个迭代函数, 每次调用将获得文件中的一行内容, 当到文件尾时,将返回 nil,并自动关闭文件。若不带参数时 io.lines() 等价于 io.input():lines() 读取默认输入设备的内容,结束时不关闭文件。

io.output ([file])

类似于 io.input,但操作在默认输出文件上。

file:read (...)

按指定的格式读取一个文件。按每个格式将返回一个字符串或数字, 如果不能正确读取将返回 nil,若没有指定格式将指默认按行方式进行读取。格式:
格式 含义
"*n" 读取一个数字
"*a" 从当前位置读取整个文件。若当前位置为文件尾,则返回空字符串
"*l" 读取下一行的内容。若为文件尾,则返回nil。(默认)
number 读取指定字节数的字符。若为文件尾,则返回nil。如果number为0,则返回空字符串,若为文件尾,则返回nil

io.read (...)

相当于 io.input():read

io.type (obj)

检测 obj 是否一个可用的文件句柄。如果 obj 是一个打开的文件句柄,则返回 "file" 如果 obj 是一个已关闭的文件句柄,则返回 "closed file" 如果 obj 不是一个文件句柄,则返回 nil。

file:write (...)

把每一个参数的值写入文件。参数必须为字符串或数字,若要输出其它值,则需通过 tostring 或 string.format 进行转换。

io.write (...)

相当于 io.output():write。

file:seek ([whence] [, offset])

设置和获取当前文件位置,成功则返回最终的文件位置(按字节,相对于文件开头),失败则返回 nil 加错误信息。缺省时,whence 默认为 "cur",offset 默认为 0 。 参数 whence:
whence 含义
"set" 文件开始
"cur" 文件当前位置(默认)
"end" 文件结束

file:setvbuf (mode [, size])

设置输出文件的缓冲模式。
模式 含义
"no" 没有缓冲,即直接输出
"full" 全缓冲,即当缓冲满后才进行输出操作(也可调用flush马上输出)
"line" 以行为单位,进行输出

最后两种模式,size 可以指定缓冲的大小(按字节)

忽略 size 将自动调整为最佳的大小。
Buy me a 肥仔水!