中国搜索网站排名,织梦手机网站图片,西安公司建设网站,学生网页使用RedisTemplate执行lua脚本
在开发中#xff0c;我们经常需要与Redis数据库进行交互#xff0c;而Redis是一个基于内存的高性能键值存储数据库#xff0c;它支持多种数据结构#xff0c;并提供了丰富的命令接口。在某些情况下#xff0c;我们可能需要执行一些复杂的逻…使用RedisTemplate执行lua脚本
在开发中我们经常需要与Redis数据库进行交互而Redis是一个基于内存的高性能键值存储数据库它支持多种数据结构并提供了丰富的命令接口。在某些情况下我们可能需要执行一些复杂的逻辑操作这时可以使用Lua脚本来实现这些逻辑而Redis提供了执行Lua脚本的功能。在Spring应用程序中我们可以使用RedisTemplate来执行Lua脚本。
为什么使用Lua脚本
Redis本身提供了许多命令可以完成各种操作但有时候我们需要执行一些比较复杂的逻辑操作这时使用Lua脚本可以帮助我们在一次网络往返中完成多个命令操作减少了网络开销提高了执行效率。此外Lua脚本在Redis服务器端执行可以减少客户端与服务器之间的通信次数提高了性能。
结合Redis和lua脚本语言的特性如果在Redis里遇到如下需求就可以引入lua脚本。
重复执行相同类型的命令比如要缓存1到1000的数字到内存里。在高并发场景下减少网络调用的开销一次性执行多条命令。Redis会将lua脚本作为一个整体来执行天然具有原子性。
使用RedisTemplate执行Lua脚本
在Spring应用程序中我们可以通过RedisTemplate来执行Lua脚本。RedisTemplate是Spring Data Redis提供的用于与Redis数据库进行交互的模板类它封装了Redis的各种操作并提供了方便的方法来执行Lua脚本。
以下是使用RedisTemplate执行Lua脚本的一般步骤
添加Spring Data Redis依赖 首先确保你的Spring Boot项目中已经添加了Spring Data Redis依赖。你可以在项目的pom.xml文件中添加以下依赖
dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-redis/artifactId
/dependency配置RedisTemplate 在Spring Boot项目的配置文件中例如application.properties或application.yml配置Redis连接信息和RedisTemplate。以下是一个示例配置
spring.redis.hostlocalhost
spring.redis.port6379
spring.redis.passwordyour_redis_password在Java代码中你可以配置RedisTemplate bean如下所示
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;Configuration
public class RedisConfig {Beanpublic RedisTemplateString, Object redisTemplate(RedisConnectionFactory connectionFactory) {RedisTemplateString, Object template new RedisTemplate();template.setConnectionFactory(connectionFactory);template.setKeySerializer(new StringRedisSerializer());template.setValueSerializer(new StringRedisSerializer()); // 根据需要设置值的序列化器template.setEnableTransactionSupport(true); // 支持事务template.afterPropertiesSet();return template;}
}执行Lua脚本 现在你可以在Spring Boot服务中使用RedisTemplate执行Lua脚本。以下是一个示例
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Service;import java.util.Collections;
import java.util.List;Service
public class LuaScriptService {Autowiredprivate RedisTemplateString, Object redisTemplate;public String executeLuaScript() {// Lua脚本内容String luaScript return Hello, Lua!;// 创建RedisScript对象RedisScriptString script new DefaultRedisScript(luaScript, String.class);// 执行Lua脚本String result redisTemplate.execute(script, Collections.emptyList());return result;}
}在这个示例中我们首先定义了一个Lua脚本字符串并使用DefaultRedisScript创建了一个RedisScript对象。然后我们使用RedisTemplate的execute方法执行Lua脚本并传递一个空参数列表。
这只是一个简单的示例你可以根据需要编写更复杂的Lua脚本并使用RedisTemplate来执行它们。需要确保在执行Lua脚本时使用正确的参数和数据类型以便与Redis进行正确的交互。 -- 此脚本的功能是将传入的key增加1并且如果是第一次操作这个key则给其设置传入的过期时间。
local results {}
-- 这里的 for 循环中出现了两个循环变量分别表示索引和值ipars 是只遍历存在于数组里面的元素ARGV[idx]表示取参数值
for idx,key in ipairs(KEYS) dolocal value tonumber(ARGV[idx])local total redis.call(INCR, key)table.insert(results, total)if total 1 thenredis.call(EXPIRE, key, value)end
end
return results文件读取第一种方式
Configuration
public class RedisScriptConfig {Beanpublic RedisScriptListLong batchIncrWithExpireScript() {Resource scriptSource new ClassPathResource(redis-scripts/batchIncrWithExpire.lua);String script;try {script StreamUtils.copyToString(scriptSource.getInputStream(), StandardCharsets.UTF_8);} catch (IOException e) {throw new IllegalStateException(Unable to load lua script, e);}return new DefaultRedisScript(script, List.class);}}Component
Slf4j
public class RedisUtils {Resource(name batchIncrWithExpireScript)private RedisScriptListLong batchIncrWithExpireScript;public void batchIncrWithExpire(MapString, String keyValueMap) {// 准备键和值的列表ListString keys new ArrayList(keyValueMap.keySet());ListString values keyValueMap.values().stream().toList();// 执行Lua脚本ListLong results stringRedisTemplate.execute(batchIncrWithExpireScript, keys, values.toArray());}}文件读取第二种方式
要在Spring Boot项目中运行一个Lua脚本文件你可以按照以下步骤进行操作
创建Lua脚本文件 首先创建一个包含你的Lua脚本的文件例如myscript.lua并将其保存在项目的合适位置。在这个文件中你可以编写你的Lua脚本代码。加载Lua脚本文件 在Spring Boot服务中你需要加载Lua脚本文件并将其内容传递给RedisTemplate来执行。你可以使用Java的文件读取方法来加载Lua脚本文件的内容。执行Lua脚本 使用RedisTemplate执行加载的Lua脚本内容。你可以使用DefaultRedisScript来创建RedisScript对象并在执行时传递适当的参数。 以下是示例代码演示如何加载并执行Lua脚本文件
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Service;import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;Service
public class LuaScriptFileService {Autowiredprivate RedisTemplateString, Object redisTemplate;public String executeLuaScriptFromFile() throws IOException {// 加载Lua脚本文件Resource resource new ClassPathResource(redis-scripts/batchIncrWithExpire.lua);String luaScript new String(resource.getInputStream().readAllBytes(), StandardCharsets.UTF_8);// 创建RedisScript对象RedisScriptString script new DefaultRedisScript(luaScript, String.class);// 执行Lua脚本String result redisTemplate.execute(script, Collections.emptyList());return result;}
}在这个示例中我们首先加载Lua脚本文件的内容并将其存储在luaScript字符串中。然后我们使用DefaultRedisScript创建了RedisScript对象并在执行时传递了一个空参数列表。你需要替换path/to/myscript.lua为你的Lua脚本文件的实际路径。
现在你可以在Spring Boot服务中调用executeLuaScriptFromFile方法来执行Lua脚本文件中的内容。
请确保Lua脚本文件的路径和文件名正确并且具有适当的访问权限。此外根据需要你可以传递参数给Lua脚本并在Lua脚本中使用KEYS和ARGV来引用它们。
文件读取第三种方式
你可以直接使用DefaultRedisScript来读取Lua脚本文件而不需要手动加载文件内容。以下是如何使用DefaultRedisScript来执行Lua脚本文件的示例
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Service;import java.util.Collections;
import java.util.List;Service
public class LuaScriptFileService {Autowiredprivate RedisTemplateString, Object redisTemplate;public String executeLuaScriptFromFile() {Resource scriptSource new ClassPathResource(redis-scripts/batchIncrWithExpire.lua);RedisScriptListLong script RedisScript.of(scriptSource, List.class);// 执行Lua脚本String result redisTemplate.execute(script, Collections.emptyList());return result;}
}在这个示例中我们通过将Lua脚本文件的路径传递给DefaultRedisScript的构造函数来创建了RedisScript对象。然后我们可以使用execute方法来执行Lua脚本文件中的内容。这种方法更简洁省去了手动加载文件内容的步骤。
确保将redis-scripts/batchIncrWithExpire.lua替换为你实际的Lua脚本文件路径。此外根据需要你可以传递参数给Lua脚本并在Lua脚本中使用KEYS和ARGV来引用它们。
用一篇文章让你学会脚本语言Lua
Lua的数据结构、关键字、注释
任何一门语言都提供了不同类型的数据结构那么 Lua 中都有哪些数据结构呢
nil空boolean布尔类型分别是 true 和 falsenumber数值型整型和浮点型都属于 numberstring字符串table表function函数userdata用户数据thread线程
Lua 总共提供了以上 8 种数据类型目前只需要知道一下即可后面会一点一点介绍。 然后是 Lua 的关键字总共有 22 个如下所示。
and break do else elseif end false
goto for function if in local nil
not or repeat return then true until while这些关键字显然基本上都见过后续会慢慢遇到。 最后是 Lua 的注释Lua 也分单行注释和多行注释单行注释和 SQL 一样以 -- 开头多行注释则以 --[[ 开头、]] 结尾里面写注释。
-- 这是单行注释--[[这是多行注释并且开头的 -- 和 [[ 之间不可以有空格结尾是两个 ]]]--[[这也是多行注释不是两行单行注释--]]以上我们对 Lua 便有了一个初步认识下面来学习 Lua 的数据结构。
Lua 的数值
Lua 的数值类型为 number无论是整数还是浮点型类型都是 number。
-- Lua 和 Python 类似在创建变量时不需要指定类型解释器会自动根据赋的值来判断
a 123
b 3.14
print(a, b) -- 123 3.14-- Lua中每一行语句的结尾也不需要加分号直接换行即可
-- 当然加分号也可以跟 Python 是类似的
c 123;
d 456
print(c, d) -- 123 456-- 并且在 Python 中如果加上了分号那么两行赋值可以写一行
-- 比如 e 1; f 2这在 Lua 中也是可以的
e 1; f 2
print(e, f) -- 1 2-- 但 Lua 更加彪悍不加分号也可以
-- 如果在 Python 中这么写则肯定是报错的
g 3 h 4
print(g, h) -- 3 4-- 但是我们不建议将多行赋值语句写在同一行里面最好要分行写
-- 如果写在同一行那么应该使用 Lua 的多元赋值
a, b 1, 2
print(a, b) -- 1 2可能有人发现了我们在最上面已经创建 a 和 b 这两个变量了但是最后又创建了一次这一点和 Python 类似可以创建多个同名变量。比如创建 a 1然后又创建 a 2这是允许的只不过相当于发生了更新将 a 的值由 1 变成了 2当然即便赋值为其它类型也没问题。比如先创建 a 数值然后再将 a 的值换成字符串这一点和 Python 一样因为在 Lua 中全局变量是通过 table、也就是表来存储的。
这个 table 后面会详细说你暂时可以理解为哈希表或者当成 Python 的字典而且 Python 中全局变量也是通过字典存储的。
我们通过 Lua 的数值演示了在 Lua 中如何创建一个变量并且还介绍了 Lua 中全局变量的存储方式。然后是整数和浮点数的区分既然它们的类型都是 number那要怎么区分呢
a 123
b 123. -- . 后面不写东西的话默认是 .0
c .123 -- . 前面不写东西的话默认是 0.
print(a, b, c) -- 123 123.0 0.123-- Lua 中可以使用 type 函数检测变量的类型
print(type(a)) -- number
print(type(b)) -- number
print(type(c)) -- number-- 这个 type 是内置的它检测的是 lua 中的基础类型
-- 而我们说 Lua 不区分整型和浮点型如果想精确区分的话那么可以使用 math.type
-- 整型是 integer浮点型是 float
print(math.type(a)) -- integer
print(math.type(b)) -- float
print(math.type(c)) -- float
-- 如果一个数值中出现了小数点那么 math.type 得到的就是 float使用 type 和 math.type 得到的都是一个字符串另外我们是直接使用的 math.type这个 math 类似于一个外部包。比如 Python 也有 math 包只不过在 Lua 中不需要像 Python 那样显式导入直接用即可包括后面处理字符串用的包也是如此。
整数和浮点数之间的比较
整数和浮点数可以比较
print(3 3.0) -- true
print(-3 -3.0) -- true-- 我们看到如果小数点后面是 0那么是相等的这一点和 Python 一样
-- 另外 Lua 也支持科学计数法
print(3e3) -- 3000.0-- Lua 中 a ^ b 表示 a 的 b 次方
-- 如果运算中出现了浮点数或者发生了幂运算那么结果就是浮点
print(3 ^ 2) -- 9.0
print(3 * 3) -- 9-- Lua 也支持 16 进制
print(0x61) -- 97算术运算
算数运算没啥好说的如果是两个整数运算那么结果还是整数如果出现了浮点数那么结果为浮点数。
print(1 2, 1 2.) -- 3 3.0
print(1 * 2, 1 * 2.) -- 2 2.0
print(1 - 2, 1 - 2.) -- -1 -1.0
print(13 % 5, 13. % 5) -- 3 3.0但是除法例外两个数相除的结果一定为浮点数。
print(3 / 2, 4 / 2, 4 / 2.) -- 1.5 2.0 2.0Lua 还提供了地板除会对商向下取整这是在 Lua5.3 中引入的。
print(3 // 2, 4 // 2) -- 1 2
-- 另外如果里面出现了浮点那么即使是地板除也一样会得到小数
print(4 // 2.) -- 2.0-- 虽然是浮点但结果是 1.0, 相当于还是有向下取整的效果
print(4 // 2.5) -- 1.0当然 Lua 还有幂运算使用 ^ 表示。
print(3 ^ 4) -- 81.0
print(3e4) -- 30000.0只要出现了幂运算得到的一定是浮点数。
位运算 数值之间还有关系运算但比较简单这里就不赘述了。只是需要注意不等于在其它语言中都是 !而在 Lua 中是 ~。 位运算和主流编程语言也是比较类似尤其是 Python感觉 Lua 的很多设计都和 Python 比较相似。
-- 按位与
print(15 20) -- 4
-- 按位或 |
print(15 | 20) -- 31
-- 按位异或 ~, 在其它语言中是 ^在 Lua 中是 ~
print(15 ~ 20) -- 27
-- 取反, 取反的话也是 ~
print(~20) -- -21-- 左移
print(2 3) -- 16
-- 右移
print(16 2) -- 4以上这些操作符是在 5.3 当中才提供的如果是之前的版本则不能使用这些操作符。
数学库
Lua 也提供了一个数学库叫做 math里面定义了一些用于计算的函数比如sin、cos、tan、asin、floor、ceil 等等。 这个在用的时候直接通过 IDE 提示或者查询文档即可这里就不演示了。
Lua 的字符串
下面我们看看 Lua 的字符串字符串既可以使用双引号、也可以使用单引号。注意Lua 的字符串是不可变量不能本地修改如果想修改只能创建新的字符串。
name komeiji satori
print(name) -- komeiji satori-- 使用 # 可以获取其长度
print(#name, #name) -- 14 4-- 使用 .. 可以将两个字符串连接起来
print(aa .. bb) -- aabb
print(name: .. name) -- name: komeiji satori-- .. 的两边可以没有空格但为了规范建议前后保留一个空格
-- 另外 .. 前后还可以跟数字会将数字转成字符串
print(abc .. 3, 3 .. 4, 3 .. abc) -- abc3 34 3abc
-- 另外如果 .. 的前面是数字的话那么 .. 的前面必须有空格
-- 也就是写成类似于 3 .. 的形式不可以写 3..
-- 因为 3 后面如果直接出现了 . 那么这个 . 会被当成小数点来解释另外如果 .. 的前面是数字的话那么 .. 的前面必须有空格也就是写成类似于 3 .. 的形式不可以写 3..。因为 3 后面如果直接出现了 .那么这个 . 会被当成小数点来解释。
Lua 内部也支持多行字符串使用[[和]]表示。
msg [[你好呀你在什么地方呀你吃了吗
]]print(msg)
--[[你好呀你在什么地方呀你吃了吗]]字符串和数值的转换
Lua 中字符串可以和数值相加也可以相互转换。
-- 如果字符串和整数运算那么得到的是浮点数
-- 你可以认为只有整数和整数运算才有可能得到整数而字符串不是整数
print(10 2) -- 12.0
print(10.1 2) -- 12.1-- 调用 tonumber 函数可以将字符串显式地转为整数
print(type(tonumber(10))) -- number
print(tonumber(10) 2) -- 12-- 如果转化失败那么结果为 nil
print(tonumber(ff)) -- nil-- 当然有些时候我们的数字未必是 10 进制比如上面的 ff它可以是 16 进制
-- 如果需要进制那么就给 tonumber 多传递一个参数即可
print(tonumber(ff, 16)) -- 255
print(tonumber(11101, 2)) -- 29
print(tonumber(777, 8)) -- 511-- 8 进制允许出现的最大数是 7所以转化失败结果为 nil
print(tonumber(778, 8)) -- nil-- 数值转成字符串则是 tostring
print(tostring(100) 100) -- true
print(tostring(100) 100) -- false
print(tostring(3.14) 3.14) -- true所以数值和字符串是可以相加的当然相减也可以会将字符串转成浮点数。也可以判断是否相等或者不相等这个时候会根据类型判断不会隐式转化了由于两者类型不一样直接不相等。但两者无法比较大小只能判断是否相等或者不等因为 2 15 但 “2” “15”所以为了避免混淆在比较的时候 Lua 不会隐式转换、加上类型不同也无法比较大小因此直接抛异常。
字符串标准库
Lua 处理字符串还可以使用一个叫 string 的标准库这个标准库也是内嵌在解释器里面我们直接通过 string.xxx 即可使用。下面就来看看 string 这个标准库都提供了哪些函数吧补充一下 Lua 的字符串是以字节为单位的不是以字符为单位的。因此 string 的大部分函数不适合处理中文除了少数例外如果要处理中文可以使用后面介绍的 utf8。
-- 查看字符串的长度
print(string.len(abc), #abc) -- 3 3
-- 一个汉字占三个字节默认是以字节为单位的计算的是字节的个数
print(string.len(古明地觉), #古明地觉) -- 12 12-- 重复字符串 n 次
print(string.rep(abc, 3)) -- abcabcabc
-- 如果是单纯的重复字符串的话也可以对中文操作因为不涉及字符的局部截取
print(string.rep(古明地觉, 3)) -- 古明地觉古明地觉古明地觉-- 字符串变小写可以用于中文但是没意义
print(string.lower(aBc)) -- abc
print(string.lower(古明地觉)) -- 古明地觉-- 同理还有转大写
print(string.upper(aBc)) -- ABC
print(string.upper(古明地觉)) -- 古明地觉-- 字符串翻转这个不适合中文
print(string.reverse(abc)) -- cba
print(string.reverse(古明地觉)) -- 谜厘椏
-- 我们看到中文出现了乱码原因就是这个翻转是以字节为单位从后向前翻转
-- 而汉字占 3 个字节需要以 3 个字节为单位翻转-- 字符串截取注意Lua 中索引是从 1 开始的
-- 结尾也可以写成 -1并且字符串截取包含首尾两端
print(string.sub(abcd, 1, -1)) -- abcd
print(string.sub(abcd, 2, -2)) -- bc
-- 可以只指定开头不指定结尾但是不可以开头结尾都不指定
print(string.sub(abcd, 2)) -- bcd-- 同样不适合中文除非你能准确计算字节的数量
print(string.sub(古明地觉, 1, 3)) -- 古
print(string.sub(古明地觉, 1, 4)) -- 古
-- 超出范围就为空字符串
print(string.sub(古明地觉, 100, 400) ) -- true-- 将数字转成字符
print(string.char(97)) -- a
-- 如果是多个数字那么在转化成字符之后会自动拼接成字符串
print(string.char(97, 98, 99)) -- abc-- 字符转成数字默认只转换第 1 个
print(string.byte(abc)) -- 97
-- 可以手动指定转换第几个字符
print(string.byte(abc, 2)) -- 98
print(string.byte(abc, -1)) -- 99
-- 超出范围那么返回 nil
print(string.byte(abc, 10) nil) -- nil
-- 转换多个字符也是可以的这里转化索引为 1 到 -1 之间的所有字符
print(string.byte(abc, 1, -1)) -- 97 98 99
-- 越界也没事有几个就转化几个
print(string.byte(abc, 1, 10)) -- 97 98 99
-- 另外这里是返回了多个值我们也可以用多个变量去接收
a, b, c string.byte(abc, 1, 10)
print(a, b, c) -- 97 98 99-- 关乎 Lua 返回值由于涉及到了函数我们后面会说
-- 字符串的格式化格式化的风格类似于 C
print(string.format(name %s, age %d, number %03d, 古明地觉, 17, 1)) -- name 古明地觉, age 17, number 001-- 字符串的查找会返回两个值分别是开始位置和结束位置
print(string.find(abcdef, de)) -- 4 5
-- 不存在则为 nil
print(string.find(abcdef, xx)) -- nil-- 字符串的全局替换这个替换可以用中文返回替换之后的字符串和替换的个数
print(string.gsub(古名地觉 名名 那么可爱, 名, 明)) -- 古明地觉 明明 那么可爱 3
-- 我们同样可以使用返回值去接
new_str, count string.gsub(古名地觉 名名 那么可爱, 名, 明)
print(new_str) -- 古明地觉 明明 那么可爱关于处理 ASCII 字符string 库为我们提供了以上的支持可以看到支持的东西还是比较少的因为 Lua 的源码总共才两万多行这就决定了它没办法提供过多的功能。Lua 主要是用来和别的语言结合的并且 string 库提供的东西也不少了。
下面来看看 utf-8我们说 string 库不是用来处理 unicode 字符的如果处理 unicode 字符的话需要使用 utf8 这个库。
-- Lua 中存储 unicode 字符使用的编码是 utf-8
-- 计算长度
print(utf8.len(古明地觉)) -- 4-- 类似于 string.byte这两个可以通用
print(utf8.codepoint(古明地觉, 1, -1)) -- 21476 26126 22320 35273-- 类似于 string.char这两个可以通用
print(utf8.char(21476, 26126, 22320, 35273)) -- 古明地觉-- 截取使用 string.sub但不同字符占的字节大小可能不一样这时候怎么截取呢
-- 可以通过 utf8.offset 计算出偏移到第 n 个字符的字节量
print(string.sub(古明地觉, utf8.offset(古明地觉, 2))) -- 明地觉
print(string.sub(古明地觉, utf8.offset(古明地觉, -2))) -- 地觉-- 遍历遍历使用了 for 循环我们后面说现在先看一下
for i, c in utf8.codes(古a明b地c觉) doprint(i, c, utf8.char(c))--[[1 21476 古4 97 a5 26126 明8 98 b9 22320 地12 99 c13 35273 觉]]
end以上便是 Lua 处理字符串的一些操作 尽管功能提供的不是非常的全面但这与 Lua 的定位有关。
Lua的控制结构
控制结构主要有两种条件语句和循环语句。
条件语句
-- 单独的 if
if condition thenstatement
end-- if 和 else
if condition thenstatement
elsestatement
end-- if elseif else
-- 注意是 elseif不是 else ifelse 和 if 之间需要连起来
if condition thenstatement
elseif condition thenstatement
elseif condition thenstatement
elsestatement
end if 和 elseif 后面必须加上一个 then类似于 Python 中必须加上一个冒号一样但是 else 则不需要 then。另外每个 if 语句在结尾处必须有一个 end来标志这个 if 语句块的结束。不过既然结尾有 end那么 Lua 中也就不需要缩进了但 Python 则是必须严格遵循缩进规范而 Lua 则不被缩进约束。但还是那句话为了代码的可读性还是建议按照 Python 的规范来编写。
a 85if a 60 thenprint(不及格)
elseif a 85 thenprint(及格)
elseif a 100 thenprint(优秀)
elseif a 100 thenprint(满分)
elseprint(无效的分数)
end
-- 优秀循环while语句
while condition dostatement
endrepeat … until
repeat … until 说白点就是一直重复做直到满足某个条件停下来。
i 1
sum 0-- 不断的执行 sum sum 1 和 i i 1直到满足 i 10 的时候停下来
repeatsum sum ii i 1
until i 10print(string.format(sum %d, sum)) -- sum 45循环 for 语句
break
break 用于跳出循环体可以用于 for、while、repeat注意没有 continue。
and 和 or
如果需要多个判断条件那么可以使用 and 和 or 进行连接。
username zpli
password 123456if username zpli and password 123456 thenprint(欢迎来到避难小屋)
elseprint(用户名密码不对)
end
-- 欢迎来到避难小屋-- 另外 Lua 中还有 not 表示取反得到布尔值
-- 这里着重强调一点在 Lua 中只有 false 和 nil 才为假其它全部为真
-- 这里和 Python 不一样在 Python 中 0、 是假但在 Lua 中是真
print(not 0) -- false
print(not ) -- false
print(not not ) -- true
-- 0 和 为真所以使用 not 得到假两个 not 得到真以上我们就介绍了 Lua 的控制结构比较简单。
Lua的表
下面来看看 Lua 的表Table表是 Lua 语言中最主要事实上也是唯一的数据结构表既可以当做数组来用也可以当成哈希表来用。这个和 Python 的字典非常类似比如我们之前查看变量类型的 math.type本质上就是以字符串 “type” 来检索表 math。而在 Python 中比如调用 math.sin本质也是从 math 模块的属性字典里面查找 key 为 “sin” 对应的 value。
然后看看在 Lua 中如何创建表。
-- 类似于 Python 的字典Lua 中创建表直接使用大括号即可
t {}
-- 返回的是表的一个引用
print(t) -- table: 00000000010b9160
-- 类型为 table
print(type(t) table) -- true在这里我们需要介绍一下 Lua 的变量在 Lua 中分为全局变量和局部变量这两者我们会在函数中细说。总之目前创建的都是全局变量其有一个特点
-- 对于没有创建的变量可以直接打印结果是一个 nil
print(a) -- nil-- c 这个变量没有创建因此是 nil那么 d 也是 nil
d c
print(d) -- nil-- 所以我们看到程序中明明没有这个变量但是却可以使用只不过结果为 nil
-- 那如果我们将一个已经存在的变量赋值为 nil是不是等于没有创建这个变量呢
-- 答案是正确的如果将一个变量赋值为 nil那么代表这个变量对应的内存就会被回收
name shiina mashiro
name nil -- shiina mashiro 这个字符串会被回收之所以介绍全局变量这个特性是因为在表中nil 是一个大坑我们往下看。
a {}a[name] 古明地觉
a[age] 16-- 打印 a 只是返回一个引用
print(a) -- table: 00000000000290e0
print(a[name], a[age]) -- 古明地觉 16-- 更改表的元素
-- table 类似于哈希表key 是不重复的所以重复赋值相当于更新
a[age] a[age] 1
print(a[age]) -- 17-- 全局变量也是通过 table 存储的我们可以给一个变量不断地赋值赋上不同类型的值
a[age] 18
print(a[age]) -- 18
a[age] 十六
print(a[age]) -- 十六-- 创建 table 返回的是一个引用
b a
-- 此时的 b 和 a 指向的是同一个 table修改 b 会影响到 a
b[name] satori
print(a[name]) -- satori-- 赋值为 nil等价于回收对象
a nil
-- 但是只将 a 赋值为nil显然还不够因为还有 b 在指向上面的 table
b nil
-- 这样的话table 就被回收了Lua 的 table 既可以做哈希表也可以当做数组有兴趣可以看 Lua 的源代码非常的精简。下面来看看 table 如何当成数组来使用
a {}for i 1, 10 doa[i] i * 2
endprint(a[3]) -- 6-- table 的底层是一个结构体里面实现了哈希表和数组两种结构
-- 如果 key 是整型那么会通过数组的方式来存储如果不是会使用哈希表来存储
-- 注意如果当成数组使用那么索引也是从 1 开始的-- 此时是通过哈希表存储的
a[x] 233
print(a[x]) -- 233-- 除了a[x]这种方式还可以使用a.x这两者在 Lua 中是等价的
print(a.x) -- 233-- a[name] 和 a.name 是等价的但是和 a[name] 不是等价的
-- 因为 name 是一个变量而 name x所以结果是 a[x] 或者 a.x
a[name] 椎名真白
name x
print(a[name], a.name, a[name]) -- 椎名真白 椎名真白 233然后是关于整数和浮点数的一个坑来看一下。
a {}a[2] 123
print(a[2.0]) -- 123a[2.0] 456
print(a[2]) -- 456-- 所以这两者是等价的因为 2.0 会被隐式转化为 2事实上在 Python 的字典中也有类似的现象
-- d {}; d[True] 1; d[1] 2; d[1.0] 3; print(d)
-- 上面那行代码在 Python 里面执行一下看看会发生什么-- 但对于字符串则不一样因为 2 和 2 不相等
a {}
a[2] 123
a[2] 456
print(a[2], a[2]) -- 123 456-- 如果访问表中一个不存在的 key 呢
print(a[xxx]) -- nil-- 我们看到得到的是一个 nil
-- 显然我们想到了如果将一个 key 对应的值显式地赋值为 nil那么也等价于删除这个元素
a[2] nil 表构造器
估计有人目前对 table 即可以当数组又可以当哈希表会感到困惑别着急我们会慢慢说。目前创建表的时候都是创建了一张空表其实在创建的时候是可以指定元素的。
a {a, b, c }
print(a[1], a[2], a[3]) -- a b c
-- 我们看到如果不指定 key那么表的元素是通过数组存储的这种存储方式叫做 列表式(list-style)
-- 索引默认是 1 2 3 4...-- 此外还可以这么创建
b {namemashiro, age18 }
print(b[name], b[age]) -- mashiro 18
-- 第二种方式是通过哈希表存储的这种存储方式叫做记录式(record-style)-- 但如果存储的 key 是数字或者特殊字符那么需要使用 [] 包起来
b {[] add, [3] xxx} -- 必须使用 [] 和 [3]不能是单独的 和 3
-- 同理获取也只能是 b[] 和 b[3]不可以是 b. 和 b.3
print(b[], b[3]) -- add xxx-- 表也是可以嵌套的
a[table] b
print(a[table][]) -- add-- 此外两种方式也可以混合使用
mix {a, namemashiro, b, age18 }
print(mix[1], mix[2]) -- a b
print(mix[name], mix[age]) -- mashiro 18-- 这里有必要详细说明一下即使是混合使用
-- 如果没有显式地指定 key、也就是列表式那么会以数组的形式存储索引默认是 1 2 3...
-- 所以 a[1] 是 a, a[2] 是 b-- 如果是这种情况呢
mix {a, [2] 1 }
print(mix[2]) -- 1
mix {a, b, [2] 1 }
print(mix[2]) -- b
-- 解释一下首先对于单个标量来说默认就是用数组存储的索引就是 1 2 3...
-- 但我们在通过记录式设置的时候对应的 key 使用的如果也是数组的索引那么记录式中设置的值会被顶掉
--[[
比如mix {a, [2] 1 } 数组的最大索引是 1所以 [2] 1 是没有问题的
但是 mix {a, b, [2] 1 }数组最大索引是 2所以 [2] 1 会被顶掉因为冲突了
]]-- 事实上 mix {a, b, [2] 1 } 这种方式就等价于 mix {[1] a, [2] b, [2] 1 }
-- 如果 key 是整型那么也通过数组存储, 否则通过哈希表存储
-- 只不过我们手动指定 [2] 1 会先创建然后被 [2] b 顶掉了
a {a, [1] 1 }
print(a[1]) -- a
a {[1] 1, a}
print(a[1]) -- a
-- 无论顺序如何a[1] 都会是 a估计有人还有疑问那就是a {}; a[1] 1; a[100] 100或者a {1, [100] 100}如果这样创建的话那中间的元素是什么因为我们说 key 是整型则以数组存储而数组又是连续的存储的空间而我们只创建了两个元素索引分别是 1 和 100那么其它元素是以什么形式存在呢带着这些疑问我们先往下看。
数组、列表和序列
现在我们知道了如果想表示常见的数组、或者列表那么只需要使用整型作为索引即可。而且在 Lua 的 table 中可以使用任意数字作为索引只不过默认是从 1 开始的Lua 中很多其它机制也遵循此惯例。
但是table的长度怎么算呢我们知道对字符串可以使用 #同理对 table 也是如此。
a {1, 2, 3, name mashiro, a }
print(#a) -- 4
-- 但是我们看到结果为 4可明明里面有 5 个元素啊
-- 因为 # 计算的是索引为整型的元素的个数更准确的说 # 计算的是使用数组存储的元素的个数a {[0] 1, 2, 3, 4, [-1]5}
print(#a) -- 3
-- 此时的结果是 3因为 0 和 -1 虽然是整型但它们并没有存储在数组里
-- 因为 Lua 索引默认从 1 开始如果想要被存储的数组里面那么索引必须大于 0a {1, 2, [3.0]xxx, [4.1] aaa }
print(#a) -- 3
-- 这里同样是 3因为 3.0 会被隐式转化为 3因此数组里面有 3 个元素但是 4.1 不会所以我们看到# 计算的是存储在数组里面的元素也就是 table 中索引为正整数的元素但真的是这样吗
首先对于数组中存在nil的 table使用 # 获取长度是不可靠的它只适用于数组中所有元素都不为 nil 的 table。事实上将 # 应用于获取 table 长度一直饱受争议以前很多人建议如果数组中存在 nil那么使用 # 操作符直接抛出异常或者说扩展一下 # 的语义。然而这些建议都是说起来容易做起来难主要是在 Lua 中数组实际上是一个 table而 table 的长度不是很好理解。
我们举例说明
a {1, 2, 3, 4 }
a[2] nil
-- 很容易得出这是一个长度为 4第二个元素为 nil 的 table
print(#a) -- 4-- 但是下面这个例子呢没错就是我们之前说的
b {}
b[1] 1
b[100] 100
-- 是否应该认为这是一个具有 100 个元素其中 98 个元素为 nil 的 table 呢
-- 如果我们再将 a[100] 设置成 nil该列表长度又是多少呢是 100、99 还是 1 呢
print(#b) -- 1
-- Lua 作者的想法是像 C 语言使用 \0 作为字符串的结束一样Lua 可以使用 nil 来隐式地表示 table 的结束
-- 可问题是 a 的第二个元素也是 nil 啊为什么长度是 4 呢-- 总之在 table 中出现了 nil那么 # 的结果是不可控的
-- 有可能你多加一个 nil结果就变了。当然不要去探究它的规律因为这没有意义
-- 总之不要在 table 中写 nil在 table 中写 nil 是原罪。不管是列表式、还是记录式都不要写 nil因为设置为 nil就表示删除这个元素-- 回到 b 这个 table 中我们说它的长度为 1
print(#b) -- 1
-- 但是数组中确实存在索引为 100 的元素
print(b[100]) -- 100所以对 b 这个 table其中数组到底是怎么存储的其实没必要纠结就当成索引为 2 到索引为 99 的元素全部是 nil 即可但计算长度的时候是不准的总之 table 中最好不要出现 nil。
遍历表
我们可以使用 for 循环去遍历 table。
a {a, b, namemashiro, c, age18, d }-- for 循环除了 for i start, end, step 这种方式之外还可以作用在表上面
-- 只不过需要使用 pairs 将 table 包起来for k, v in pairs(t)
for index, value in pairs(a) doprint(index, value)--[[1 a2 b3 c4 dage 18name mashiro]]
end
-- 这里的 for 循环中出现了两个循环变量分别表示索引和值
-- 如果只有一个变量那么得到的是索引或者哈希表的 key
-- 然后遍历的时候先遍历数组按照索引从小到大输出然后遍历哈希表不保证顺序-- 除了 pairs还有 ipairsipars 是只遍历存在于数组里面的元素
a {[4] a, [3] b, namemashiro, c, age18, [2] d }
for index, value in ipairs(a) doprint(index, value)--[[1 c2 d3 b4 a]]
end
-- 打印按照索引从小到大打印但是不建议这么创建table如果 table 中出现了 nil那么使用 for 循环去遍历会发生什么奇特的现象呢
-- 不过在此之前还是先来看一个坑向的
a {[3] 1, a, b, c }
-- 这个时候 a[3] 是多少呢
print(a[3]) -- c
-- 我们说只要是列表式都是从 1 开始所以 [3] 1 最终会被 [3] c 所顶掉
-- 上面的赋值等价于 a {[3] 1, [1] a, [2] b, [3] c}
-- 因为如果不指定 key那么 Lua 会按照 1 2 3 4 ··· 自动给一个 key准确来说是索引因为它们存在数组中-- 再来看看 table 中出现了 nilfor 循环会如何表现
a {a, nil, b, c }
print(#a) -- 4for index, value in ipairs(a) doprint(index, value)--[[1 a]]
end
-- 长度虽然是 4当然我们知道这不准但在遍历的时候一旦遇到 nil 就会终止遍历
-- 当然这个 nil 要是数组中的 nil不是哈希表中的 nil
-- 但如果是 pairs那么会遍历值不为 nil 的所有记录
a {a, nil, b, c, namenil, age18}
for index, value in pairs(a) doprint(index, value)--[[1 a3 b4 cage 18]]
end
-- 但我们看到值 b 对应的索引是 3尽管前面的是 nil但毕竟占了一个坑所以 b 对应的索引是 3-- 当然我们还可以使用获取长度、数值遍历的方式当然前提是 table 中不能出现 nil
a {a, b, 123, xx }
for idx 1, #a doprint(a[idx])--[[ab123xx]]
end表标准库
表的标准库提供一些函数用于对表进行操作注意这个标准库也叫 table。
a {10, 20, 30 }
print(a[1], a[2], a[3]) -- 10 20 30-- 使用 table.insert 可以插入一个值
-- 接收参数为table 插入位置 插入的值
table.insert(a, 2, xxx)
print(a[1], a[2], a[3], a[4]) -- 10 xxx 20 30
-- 如果不指定位置那么默认会添加在结尾
-- 此时传递两个参数即可table 插入的值
table.insert(a, 古明地觉)
print(a[#a]) -- 古明地觉-- 既然有 insert那么就会有 remove
-- 接收参数table 移除的元素的位置(索引)
print(a[1], a[2], a[3], a[4], a[5]) -- 10 xxx 20 30
table.remove(a, 3)
print(a[1], a[2], a[3], a[4], a[5]) -- 10 xxx 30 古明地觉 nil-- 我们看到使用 remove 之后后面的元素会依次向前移动因此无需担心会出现 nil 什么的
-- 不过这也说明了remove 的效率不是很高因为涉及到元素的移动
-- 但 table 中的函数都是 C 实现的也是很快的因此也不用太担心-- 另外在 lua5.3 中还提供了一个 move 函数
-- table.move(table, start, end, target)表示将 table 中 [start, end] 之间的元素移动到索引为 target 的位置上
-- 也是 start 位置的元素跑到 target 处start 1 - target 1、 end - target end - start
t {1, 2, 3, 4}
table.move(t, 2, #t, 3)
print(t[1], t[2], t[3], t[4], t[5]) -- 1 2 2 3 4
-- 很好理解{1 2 3 4} 中索引为 [2, #t]移动到索引为 3 的位置上因此结果是1 2 2 3 4结果会多出一个-- 这里的 move 实际上是将一个值从一个地方拷贝 copy 到另一个地方
-- 另外我们除了可以将元素移动到 table 本身之外还可以移动到另一个 table
t1 {a, b, c, d }
t2 {x, y }
-- 表示将 t1 中 [2, #t1] 的元素移动到 t2 中索引为 2 的地方
table.move(t1, 2, #t1, 2, t2)
for idx 1, #t2 doprint(t2[idx])--[[xbcd]]
end-- table 标准库中还提供了 concat 函数会将表里面的元素拼接起来
a {1, 2, xxx, 3, aaa }
print(table.concat(a)) -- 12xxx3aaa来个思考题吧
a b
b at {a b, [a] b }
print(t.a, t[a], t[t.b], t[t[b]])-- 上面的 print 会打印出什么呢我们分析一下首先看 t 这个表其中 a b 无需多说
-- 关键是 [a] b我们说 a 和 b 都是变量并且 a b、b a, 所以结果等价于 [b] a, 即b a
-- 因此这里的 t 可以看做是 {a b, b a}-- 那么 t.a 显然是 bt[a]等于t[b]因此结果是 a
-- t.b 结果是 a那么 t[t.b] 等于是 t[a]所以结果是 b
-- t[b] - t[a] - b那么 t[t[b]] - t[b] - a因此结果是 a
-- 所以 print 会打印出: b a b a-- 下个问题
a {}
a.a a
print(a) -- table: 0000000000d98ef0
print(a.a) -- table: 0000000000d98ef0
print(a.a.a) -- table: 0000000000d98ef0-- 打印的都是一样的我们说 Lua 中的 table 返回的一个引用
-- a.a a本身显然陷入了套娃的状态以上就是 Lua 的表总的来说并不复杂只是要注意里面不要出现 nil 就好。然后 table 采用了两种数据结构数组和哈希表它即可以当成数组来用也可以当成哈希表来用当然也可以混合使用。如果 key 是整数那么存在数组中否则存在哈希表中。