字符串缓冲区

简介: 字符串缓冲区

假如我们正在开发一段处理字符串的程序,比如逐行地读取一个文件。典型的代码可能形如:

local buff = ""
for line in io.lines() do
  buff = buff .. line .. "\n"
end


虽然这段 Lua 语言代码看似能够正常工作,但实际上在处理大文件时却可能导致巨大的性能开销。例如,在一台电脑上用这段代码读取一个 4.5MB 大小的文件需要超过 30 秒的时间。我们来分析以下原因,假设每行有 20 个字节,当我们读取了大概 2500 行后, buff 就会变成一个 50KB 大小的字符串。在 Lua 语言中进行字符串连接 buff .. line .. "\n" 时,会创建一个 50020 字节的新字符串,然后从 buff 中复制 50000 字节中到这个新字符串。这样,对于后续的每一行, Lua 语言都需要移动大概 50KB 且还在不断增长的内存。因此,该算法的时间复杂度是二次方的。在读取了 100 行(仅 2K B)以后, Lua 语言就已经移动了至少 5MB 内存。当 Lua 语言完成了 350KB 的读取后,它已经至少移动了 50GB 的数据。(这个问题不是 Lua 语言特有的:在其他语言中,只要字符串是不可变值,就会出现类似的问题,其中最有名的例子就是 Java


在现实场景中,上述情况的发生是很小概率的事件,对于较小的字符串,上述循环并没什么问题。当读取整个文件时, Lua 语言提供了带有参数的函数 io.read("a") 来一次性地读取整个文件。不过,有时候我们必须面对这个问题。 Java 提供了 StringBuffer 类来解决这个问题,而在 Lua 语言中,我们可以把一个表当做字符串缓冲区,其关键是使用函数 table.concat ,这个函数会将指定列表中的所有字符串连接起来并返回连接后的结果。使用函数 concat 可以这样重写上述循环:

local t = {}
for line in io.lines() do
  t[#t + 1] = line .. "\n"
end
local s = table.concat(t)


之前的代码读取同样的文件需要超过半分钟,而上述实现则只需要不到 0.05 秒。(不过尽管如此,读取整个文件最好还是使用带有参数 "a"io.read 函数。)

继续优化,函数 concat 还有第二个可选参数,用于指定插在字符串间的分隔符。有了这个分隔符,我们就不必在每行后插入换行符了:

local t = {}
for line in io.lines() do
  t[#t + 1] = line
end
local s = table.concat(t, "\n") .. "\n"


虽然函数 concat 能够在字符串之间插入分隔符,但我们还需要增加最后一个换行符。最后一次字符串连接创建了结果字符串的一个副本,这个副本可能已经相当长了。虽然没有直接的选项能够让函数 concat 插入这个额外的分隔符,但可以想办法绕过,只需要在字符串 t 后面添加一个空字符串就行了:

t[#t + 1] = ""
s = table.concat(t, "\n")


现在,正如我们所期望的那样,函数 concat 会在结果字符串的最后添加一个换行符。

Iric
+关注
目录
打赏
0
0
1
0
12
分享
相关文章
|
9月前
|
文件的缓冲区
文件的缓冲区
108 1
|
9月前
|
文件缓冲区
文件缓冲区
78 0
字符串与内存函数
字符串与内存函数
60 0
|
8月前
|
字符串和内存函数(1)
字符串和内存函数(1)
66 7
|
8月前
字符串和内存函数(2)
字符串和内存函数(2)
50 5
|
9月前
字符串和内存函数(下)
字符串和内存函数(下)
50 0
【C 语言】文件操作 ( 使用 fseek 函数生成指定大小文件 | 偏移量 文件字节数 - 1 )
【C 语言】文件操作 ( 使用 fseek 函数生成指定大小文件 | 偏移量 文件字节数 - 1 )
531 0
【C 语言】文件操作 ( 使用 fseek 函数生成指定大小文件 | 偏移量 文件字节数 - 1 )