RedisTemplate执行lua脚本及Lua 脚本语言详解

使用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脚本的一般步骤:

  1. 添加Spring Data Redis依赖: 首先,确保你的Spring Boot项目中已经添加了Spring Data Redis依赖。你可以在项目的pom.xml文件中添加以下依赖:
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  1. 配置RedisTemplate: 在Spring Boot项目的配置文件中(例如application.properties或application.yml)配置Redis连接信息和RedisTemplate。以下是一个示例配置:
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=your_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 {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new StringRedisSerializer()); // 根据需要设置值的序列化器
        template.setEnableTransactionSupport(true); // 支持事务
        template.afterPropertiesSet();
        return template;
    }
}
  1. 执行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 {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    public String executeLuaScript() {
        // Lua脚本内容
        String luaScript = "return 'Hello, Lua!'";

        // 创建RedisScript对象
        RedisScript<String> 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) do
    local value = tonumber(ARGV[idx])
    local total = redis.call('INCR', key)
    table.insert(results, total)
    if total == 1 then
        redis.call('EXPIRE', key, value)
    end
end
return results

文件读取第一种方式

@Configuration
public class RedisScriptConfig {
  
  	@Bean
    public RedisScript<List<Long>> 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 RedisScript<List<Long>> batchIncrWithExpireScript;
  
  	public void batchIncrWithExpire(Map<String, String> keyValueMap) {
        // 准备键和值的列表
        List<String> keys = new ArrayList<>(keyValueMap.keySet());
        List<String> values = keyValueMap.values().stream().toList();
        // 执行Lua脚本
        List<Long> results = stringRedisTemplate.execute(batchIncrWithExpireScript, keys, values.toArray());
    }
  
}

文件读取第二种方式

要在Spring Boot项目中运行一个Lua脚本文件,你可以按照以下步骤进行操作:

  1. 创建Lua脚本文件: 首先,创建一个包含你的Lua脚本的文件(例如,myscript.lua),并将其保存在项目的合适位置。在这个文件中,你可以编写你的Lua脚本代码。
  2. 加载Lua脚本文件: 在Spring Boot服务中,你需要加载Lua脚本文件并将其内容传递给RedisTemplate来执行。你可以使用Java的文件读取方法来加载Lua脚本文件的内容。
  3. 执行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 {

    @Autowired
    private RedisTemplate<String, 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对象
        RedisScript<String> 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脚本中使用KEYSARGV来引用它们。

文件读取第三种方式

你可以直接使用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 {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    public String executeLuaScriptFromFile() {
      
      	Resource scriptSource = new ClassPathResource("redis-scripts/batchIncrWithExpire.lua");
        RedisScriptList<Long> 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脚本中使用KEYSARGV来引用它们。

用一篇文章让你学会脚本语言Lua

Lua的数据结构、关键字、注释

任何一门语言都提供了不同类型的数据结构,那么 Lua 中都有哪些数据结构呢?

  • nil:空
  • boolean:布尔类型,分别是 true 和 false
  • number:数值型,整型和浮点型都属于 number
  • string:字符串
  • 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.0

Lua 还提供了地板除,会对商向下取整,这是在 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觉") do
    print(i, c, utf8.char(c))
    --[[
        1	21476	古
        4	97	    a
        5	26126	明
        8	98	    b
        9	22320	地
        12	99	    c
        13	35273	觉
    ]]
end

以上便是 Lua 处理字符串的一些操作, 尽管功能提供的不是非常的全面,但这与 Lua 的定位有关。

Lua的控制结构

控制结构主要有两种:条件语句和循环语句。

条件语句

-- 单独的 if
if condition then
    statement
end

-- if 和 else
if condition then
    statement
else
    statement
end

-- if elseif else
-- 注意:是 elseif,不是 else if,else 和 if 之间需要连起来
if condition then
    statement
elseif condition then
    statement
elseif condition then
    statement
else
    statement
end    

if 和 elseif 后面必须加上一个 then,类似于 Python 中必须加上一个冒号一样,但是 else 则不需要 then。另外每个 if 语句,在结尾处必须有一个 end,来标志这个 if 语句块的结束。不过既然结尾有 end,那么 Lua 中也就不需要缩进了,但 Python 则是必须严格遵循缩进规范,而 Lua 则不被缩进约束。但还是那句话,为了代码的可读性还是建议按照 Python 的规范来编写。

a = 85

if a < 60 then
    print("不及格")
elseif a < 85 then
    print("及格")
elseif a < 100 then
    print("优秀")
elseif a == 100 then
    print("满分")
else
    print("无效的分数")
end
-- 优秀

循环while语句

while condition do
    statement
end

repeat … until

repeat … until 说白点就是一直重复做,直到满足某个条件停下来。

i =1 
sum = 0

-- 不断的执行 sum = sum + 1 和 i = i + 1,直到满足 i >= 10 的时候停下来
repeat
    sum = sum + i
    i = i + 1
until i >= 10

print(string.format("sum = %d", sum))  -- sum = 45

循环 for 语句

break

break 用于跳出循环体,可以用于 for、while、repeat,注意:没有 continue。

and 和 or

如果需要多个判断条件,那么可以使用 and 和 or 进行连接。

username = "zpli"
password = "123456"

if username == "zpli" and password == "123456" then
    print("欢迎来到避难小屋")
else
    print("用户名密码不对")
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 do
    a[i] = i * 2
end

print(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])  -- 123

a[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 = {name="mashiro", age=18 }
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', name='mashiro', 'b', age=18 }
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 开始,如果想要被存储的数组里面,那么索引必须大于 0

a = {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", name="mashiro", "c", age=18, "d" }

-- for 循环除了 for i = start, end, step 这种方式之外,还可以作用在表上面
-- 只不过需要使用 pairs 将 table 包起来:for k, v in pairs(t)
for index, value in pairs(a) do
    print(index, value)
    --[[
    1	    a
    2	    b
    3	    c
    4	    d
    age	    18
    name    mashiro
    ]]
end
-- 这里的 for 循环中出现了两个循环变量,分别表示索引和值
-- 如果只有一个变量,那么得到的是索引,或者哈希表的 key
-- 然后遍历的时候先遍历数组(按照索引从小到大输出),然后遍历哈希表(不保证顺序)



-- 除了 pairs,还有 ipairs,ipars 是只遍历存在于数组里面的元素
a = {[4] = "a", [3] = "b", name="mashiro", "c", age=18, [2] = "d" }
for index, value in ipairs(a) do
    print(index, value)
    --[[
    1	c
    2	d
    3	b
    4	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 中出现了 nil,for 循环会如何表现
a = {'a', nil, 'b', 'c' }
print(#a)  -- 4

for index, value in ipairs(a) do
    print(index, value)
    --[[
    1   a
    ]]
end
-- 长度虽然是 4(当然我们知道这不准),但在遍历的时候一旦遇到 nil 就会终止遍历
-- 当然这个 nil 要是数组中的 nil,不是哈希表中的 nil
-- 但如果是 pairs,那么会遍历值不为 nil 的所有记录
a = {'a', nil, 'b', 'c', name=nil, age=18}
for index, value in pairs(a) do
    print(index, value)
    --[[
    1	a
    3	b
    4	c
    age	18
    ]]
end
-- 但我们看到值 "b" 对应的索引是 3,尽管前面的是 nil,但毕竟占了一个坑,所以 "b" 对应的索引是 3


-- 当然我们还可以使用获取长度、数值遍历的方式,当然前提是 table 中不能出现 nil
a = {'a', 'b', 123, 'xx' }
for idx = 1, #a do
    print(a[idx])
    --[[
    a
    b
    123
    xx
    ]]
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 do
    print(t2[idx])
    --[[
    x
    b
    c
    d
    ]]
end

-- table 标准库中还提供了 concat 函数,会将表里面的元素拼接起来
a = {1, 2, "xxx", 3, "aaa" }
print(table.concat(a))  -- 12xxx3aaa

来个思考题吧

a = "b"
b = "a"

t = {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 显然是 "b",t[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 是整数,那么存在数组中,否则存在哈希表中。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/949551.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于Python 的宠物管理系统(源码+部署)

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

stm32第一次烧录或者上电运行卡死问题分析

问题描述 单片机烧录代码&#xff08;刚上电&#xff09;无法立即运行&#xff0c;必须要复位一次或多次才能运行&#xff1b;跟踪调试会进入HardFault_Handler中断。 问题分析 烧录配置如下图&#xff0c;首先排除配置问题那么该问题就比较让人头大了&#xff0c;理论上&am…

ESP32-C3 AT WiFi AP 启 TCP Server 被动接收模式 + BLE 共存

TCP 被动接收模式&#xff0c;每次发的数据会先存到缓冲区&#xff0c;参见&#xff1a;ATCIPRECVTYPE 指令说明。 即每包数据不会实时报告 IPD 接收情况&#xff0c;如果需要查询缓冲区的数据&#xff0c;先用 ATCIPRECVLEN? 指令查询被动接收模式下套接字数据的长度 。获取…

【LeetCode Hot100 二分查找】搜索插入位置、搜索二维矩阵、搜索旋转排序数组、寻找两个正序数组的中位数

二分查找 搜索插入位置搜索二维矩阵在排序数组中查找元素的第一个和最后一个位置寻找旋转排序数组中的最小值搜索旋转排序数组寻找两个正序数组的中位数&#xff08;hard&#xff09; 搜索插入位置 给定一个排序数组和一个目标值&#xff0c;在数组中找到目标值&#xff0c;并…

ChatGPT 主流模型GPT-4/GPT-4o mini的参数规模是多大?

微软论文又把 OpenAI 的机密泄露了&#xff1f;&#xff1f;在论文中明晃晃写着&#xff1a; o1-preview 约 300B&#xff1b;o1-mini 约 100BGPT-4o 约 200B&#xff1b;GPT-4o-mini 约 8BClaude 3.5 Sonnet 2024-10-22 版本约 175B微软自己的 Phi-3-7B&#xff0c;这个不用约…

Docker 安装Elasticsearch搜索引擎 搜索优化 词库挂载 拼音分词 插件安装

介绍 允许用户快速索引和搜索大量的文本数据。通过使用倒排索引&#xff0c;它能够在海量数据中高效检索相关信息。提供灵活的查询语言&#xff0c;可以做全文搜索、模糊搜索、数据统计等&#xff0c;用来代替MYSQL的模糊搜索&#xff0c;MYSQL的模糊搜索不支持使用索引从而导…

Scala_【5】函数式编程

第五章 函数式编程函数和方法的区别函数声明函数参数可变参数参数默认值 函数至简原则匿名函数高阶函数函数作为值传递函数作为参数传递函数作为返回值 函数闭包&柯里化函数递归控制抽象惰性函数友情链接 函数式编程 面向对象编程 解决问题时&#xff0c;分解对象&#xff…

jenkins入门7 --发送邮件1

jenkins发送邮件配置&#xff08;全局配置&#xff09;_jenkins 怎么发送邮件-CSDN博客 本文通过163发送邮件 1、首先163设置选择pop3/smtp/imap,开启服务&#xff0c;获取授权码 2、jenkins下载邮件插件 登录Jenkins管理界面&#xff0c;点击“Manage Jenkins”。 选择“Man…

git 常用命令和本地合并解决冲突

目录 一、常用命令 二、本地可视化合并分支解决冲突 一、常用命令 最近&#xff0c;使用mac电脑&#xff0c;无法直接使用小乌龟进行可视化操作&#xff0c;现在记录一些常用命令。 拉取&#xff1a; git clone <git url> 仅拉起某个单独分支&#xff1a; git clo…

彻底学会Gradle插件版本和Gradle版本及对应关系

看完这篇&#xff0c;保你彻底学会Gradle插件版本和Gradle版本及对应关系&#xff0c;超详细超全的对应关系表 需要知道Gradle插件版本和Gradle版本的对应关系&#xff0c;其实就是需要知道Gradle插件版本对应所需的gradle最低版本&#xff0c;详细对应关系如下表格&#xff0…

我的创作纪念日——《惊变128天》

我的创作纪念日——《惊变128天》 机缘收获日常成就憧憬 机缘 时光飞逝&#xff0c;转眼间&#xff0c;我已在这条创作之路上走过了 128 天。回顾起 2024 年 8 月 29 日&#xff0c;我满怀忐忑与期待&#xff0c;撰写了第一篇技术博客《讲解LeetCode第1题&#xff1a;两数之和…

医学图像分析工具02:3D Slicer || 医学影像可视化与分析工具 支持第三方插件

3D Slicer 是一款功能全面的开源医学影像分析软件&#xff0c;广泛应用于影像处理、三维建模、影像配准和手术规划等领域。它支持多种医学影像格式&#xff08;如 DICOM、NIfTI&#xff09;和丰富的插件扩展&#xff0c;是神经科学、放射学和生物医学研究中不可或缺的工具。 在…

【每日学点鸿蒙知识】Hap 安装失败、ArkTS 与C++ 数组转换、渐变遮罩效果等

1、在启动调试或运行应用/服务时&#xff0c;安装HAP出现错误&#xff0c;提示“error: install failed due to older sdk version in the device”错误信息。 这是由于编译打包所使用的SDK版本与设备镜像版本不匹配。不匹配的场景包括&#xff1a; 场景一&#xff1a;设备上…

分布式搜索引擎之elasticsearch基本使用3

分布式搜索引擎之elasticsearch基本使用3 1.部署单点es 1.1.创建网络 因为我们还需要部署kibana容器&#xff0c;因此需要让es和kibana容器互联。这里先创建一个网络&#xff1a; docker network create es-net1.2.加载镜像 这里我们采用elasticsearch的7.12.1版本的镜像&…

在macOS上安装MySQL

macOS的MySQL有多种不同的形式&#xff1a; 1、本机包安装程序&#xff0c;它使用本机macOS安装程序&#xff08;DMG&#xff09;引导您完成MySQL的安装。有关详细信息&#xff0c;请参阅第2.4.2节&#xff0c;“使用本机包在macOS上安装MySQL”。您可以将包安装程序与macOS一…

Apache HTTPD 换行解析漏洞(CVE-2017-15715)

漏洞简介 pache HTTPD是一款HTTP服务器&#xff0c;它可以通过mod_php来运行PHP网页。其2.4.0~2.4.29版本中存在一个解析漏洞&#xff0c;在解析PHP时&#xff0c;1.php\x0A将被按照PHP后缀进行解析&#xff0c;导致绕过一些服务器的安全策略。 漏洞环境 vulhub/httpd/CVE-2…

jenkins入门4 --window执行execute shell

1、启动关闭jenkins 在Windows环境下&#xff0c;如果你需要关闭Jenkins服务&#xff0c;可以通过以下几种方式&#xff1a; 1、使用Windows服务管理器&#xff1a; 打开“运行”对话框&#xff08;Win R&#xff09;&#xff0c;输入services.msc&#xff0c;然后回车。 在服…

conda安装及demo:SadTalker实现图片+音频生成高质量视频

1.安装conda 下载各个版本地址&#xff1a;https://repo.anaconda.com/archive/ win10版本&#xff1a; Anaconda3-2023.03-1-Windows-x86_64 linux版本&#xff1a; Anaconda3-2023.03-1-Linux-x86_64 Windows安装 环境变量 conda -V2.配置conda镜像源 安装pip conda…

医学图像分析工具01:FreeSurfer || Recon -all 全流程MRI皮质表面重建

FreeSurfer是什么 FreeSurfer 是一个功能强大的神经影像学分析软件包&#xff0c;广泛用于处理和可视化大脑的横断面和纵向研究数据。该软件由马萨诸塞州总医院的Martinos生物医学成像中心的计算神经影像实验室开发&#xff0c;旨在为神经科学研究人员提供一个高效、精确的数据…

vite打包报错“default“ is not exported by “node_modules/dayjs/dayjs.min.js“

vite打包最开始报的错是&#xff1a; 查找各种解决办法后&#xff0c;第一次尝试如下&#xff1a; npm i rollup/plugin-commonjs npm i vite-plugin-require-transform但继续报错&#xff1a; 最后解决办法为&#xff1a; 忽略掉node_modules 在vite.config.ts里修改代码 …