本指南讨论如何从设备的本地存储读取和写入文件。
读写文件——以纯文本、JSON、XML、本地 SQLite 数据库等形式存在的数据——对于应用程序开发至关重要。您的应用程序可能需要读写文件的场景数不胜数,以下是一些常见情况:
保存/加载用户设置或其他统计信息。
将“应用程序状态”保存到文件并在以后的会话中读取它,以便用户即使退出应用程序也能返回到他们离开时的确切位置。
生成本地 HTML 文件并将其加载到原生 Web 视图中。
从远程服务器下载的文件中加载数据,并在应用程序中使用该数据。
system.pathForFile()
函数是 Corona 中所有文件操作的基础。 Lua 在读写文件时需要文件的完整路径,但 iOS 等移动操作系统通过“沙盒”机制隐藏了文件系统。这使得确定文件所在的确切位置变得困难。幸运的是,Corona 使用 system.pathForFile() 函数简化了这一点,该函数返回与 Lua 的文件 I/O 函数兼容的路径。
system.pathForFile( filename [, baseDirectory] )
baseDirectory
参数是一个可选常量,对应于文件所在的基本目录。如果未指定,则默认为 system.ResourceDirectory。有关更多信息,请参阅下面的系统目录部分。
在本指南中,“写入”与“保存”同义,因为您可能需要生成新文件以及写入现有文件。
以下是如何将数据写入文件的基本示例:
-- Data (string) to write local saveData = "My app state data" -- Path for the file to write local path = system.pathForFile( "myfile.txt", system.DocumentsDirectory ) -- Open the file handle local file, errorString = io.open( path, "w" ) if not file then -- Error occurred; output the cause print( "File error: " .. errorString ) else -- Write data to file file:write( saveData ) -- Close the file handle io.close( file ) end file = nil
system.pathForFile()
— 此函数返回要写入的文件 myfile.txt
的路径。另请注意,基本目录设置为 system.DocumentsDirectory
。这一点很重要,因为出于安全原因,您不能将数据写入/保存到 system.ResourceDirectory
。
io.open()
— 此函数以写入(或读取)模式打开文件。它返回一个新的文件句柄,设置为 file
。第二个参数 "w"
对应于文件将以其打开的“模式”。这决定了您将如何处理文件。在本例中,"w"
表示写入,并告诉 Corona 创建(写入)一个新文件,或者如果文件已存在则覆盖该文件。有关 I/O 模式的完整列表,请参阅 io.open() 文档。
file:write()
— 假设文件以兼容写入的模式打开,则 file:write()
方法会将指定的字符串写入文件句柄。在本例中,saveData
变量的内容将写入指定的文件。
io.close()
— 每当您执行文件操作并且完成文件句柄后,不要忘记调用 io.close()
。此方法关闭文件句柄并结束 I/O 进程。
要读取文件,只需获取文件路径,以读取模式打开 I/O,然后将内容设置为变量即可。
-- Path for the file to read local path = system.pathForFile( "myfile.txt", system.DocumentsDirectory ) -- Open the file handle local file, errorString = io.open( path, "r" ) if not file then -- Error occurred; output the cause print( "File error: " .. errorString ) else -- Read data from file local contents = file:read( "*a" ) -- Output the file contents print( "Contents of " .. path .. "\n" .. contents ) -- Close the file handle io.close( file ) end file = nil
system.pathForFile()
— 同样,您需要要从中读取的文件的路径。读取文件时,您可以指定四个系统目录中的任何一个(请参阅下面的系统目录)。例如,如果您将数据文件作为应用程序的一部分包含/捆绑,则可以从 system.ResourceDirectory
读取这些文件。
io.open()
— 此函数打开文件并返回文件句柄,在本例中设置为 file
。这次,第二个参数必须设置为 "r"
,对应于读取模式。有关 I/O 模式的完整列表,请参阅 io.open() 文档。
file:read()
— 假设文件以兼容读取的模式打开,则 file:read()
方法将读取文件的内容并将其设置为变量 savedData
。读取函数的参数指定了读取过程的格式。如果要读取文件的全部内容(保留换行符),请使用 "*a"
,如此示例所示。其他格式在 object:read() 文档中有解释。
io.close()
— 正如上文所强调的,每当您执行文件操作并且完成文件句柄后,请调用 io.close()
以关闭文件句柄并结束 I/O 进程。
读取数据时的另一个常见任务是处理文件的每一行,并将其用于某种用途。例如,如果您有一个文本文件,其中包含产品列表以及每种产品的价格,则可以按行循环浏览该文件,并单独输出或解析每一行(产品)。
file:lines()
函数与 for
循环一起可以完成此操作:
-- Path for the file to read local path = system.pathForFile( "myfile.txt", system.DocumentsDirectory ) -- Open the file handle local file, errorString = io.open( path, "r" ) if not file then -- Error occurred; output the cause print( "File error: " .. errorString ) else -- Output lines for line in file:lines() do print( line ) end -- Close the file handle io.close( file ) end file = nil
此过程将在每次迭代时返回下一行,并继续下去,直到没有更多可用行为止。可以使用字符串模式将每一行完整使用或解析成较小的元素。有关详细信息,请参阅字符串操作指南。
如上所述,如果您想访问除默认 system.ResourceDirectory
之外的目录,则 system.pathForFile()
需要一个 baseDirectory
参数。
以下是用于读/写的全部四个可用系统目录常量:
目录 | 权限 | 说明 |
---|---|---|
system.ResourceDirectory |
读取 | 此目录指的是核心项目目录,与 main.lua 文件位于同一位置。这是 system.pathForFile() 的默认基本目录。 |
system.DocumentsDirectory |
读/写 | 此目录适用于应用程序无法自行重新生成的文件,例如用户特定数据、“应用程序状态”数据或应用程序安装后生成的任何内容。此目录中的文件将在应用程序的整个生命周期中持续存在——也就是说,直到应用程序从设备中明确删除为止。在 iOS 上,此位置的文件将通过同步进行备份,除非您另行指定(请参阅下面的注释)。 |
system.TemporaryDirectory |
读/写 | 读/写 |
此目录适用于单会话数据。写入此位置的文件通常会在应用程序运行时持续存在,但操作系统保留随时删除此数据的权利。因此,不要将重要数据放在此目录中。 |
读/写 | system.CachesDirectory |
system.CachesDirectory
中的文件往往比 system.TemporaryDirectory
中的文件寿命更长,但这并不可靠,您不应该将重要数据放在此目录中。在 iOS 上,此位置的文件不会通过同步进行备份。
注释
在 Android 设备上,没有字面意义上的 system.ResourceDirectory
,因为所有资源文件都位于压缩的 APK 文件中。有关更多信息,请参阅下面的Android 文件限制。
在 Corona 模拟器中,system.DocumentsDirectory
和 system.TemporaryDirectory
的等效目录位于每个应用程序的沙盒文件夹中。您可以通过在模拟器中选择文件 → 显示项目沙盒来查看这些目录及其中的文件。
在 iOS 和 macOS 上,可以使用 native.setSync()
API 为 system.DocumentsDirectory
中的文件设置 iCloud 自动备份标志。有关更多信息,请参阅 native.setSync()。
当通过 system.pathForFile()
访问 system.ResourceDirectory
时,将 filename
参数设置为 nil
将返回目录路径,而不检查文件是否存在。
如果需要检查文件是否存在,请查看下一节中的测试文件是否存在。
local lfs = require( "lfs" ) -- Get raw path to documents directory local docs_path = system.pathForFile( "", system.DocumentsDirectory ) -- Change current working directory local success = lfs.chdir( docs_path ) -- Returns true on success local new_folder_path local dname = "images" if ( success ) then lfs.mkdir( dname ) new_folder_path = lfs.currentdir() .. "/" .. dname end
可以使用LuaFileSystem (LFS) 将子文件夹添加到 system.DocumentsDirectory
和 system.TemporaryDirectory
。
local catImage = display.newImage( "images/cat.png", system.DocumentsDirectory, 0, 0 )
以下是如何在 system.DocumentsDirectory
中创建 images
文件夹的方法:
您可以通过两种方式访问子文件夹中的文件,具体取决于您要对文件执行的操作。如果要显示图像或播放子文件夹中的声音,请将子文件夹名称与文件名连接起来,然后提供基本目录。例如,如果要显示 images
子文件夹中的 cat.png
文件,请执行以下操作:
local path = system.pathForFile( "images/readme.txt", system.DocumentsDirectory ) local file, errorString = io.open( path )
请注意,在需要 baseDirectory
参数的 API 调用中,例如 display.newImage()
、display.newImageRect()
、audio.loadSound()
等,您不需要使用 system.pathForFile()
。
如果要访问子文件夹中的文件以进行读写,请执行以下操作:
local function doesFileExist( fname, path ) local results = false -- Path for the file local filePath = system.pathForFile( fname, path ) if ( filePath ) then local file, errorString = io.open( filePath, "r" ) if not file then -- Error occurred; output the cause print( "File error: " .. errorString ) else -- File exists! print( "File found: " .. fname ) results = true -- Close the file handle file:close() end end return results end -- Check for file in "system.DocumentsDirectory" local results = doesFileExist( "images/cat.png", system.DocumentsDirectory ) -- Check for file in "system.ResourceDirectory" local results = doesFileExist( "images/cat.png" )
如果文件不存在,则返回 nil
— 这将引出下一个主题。
function copyFile( srcName, srcPath, dstName, dstPath, overwrite ) local results = false local fileExists = doesFileExist( srcName, srcPath ) if ( fileExists == false ) then return nil -- nil = Source file not found end -- Check to see if destination file already exists if not ( overwrite ) then if ( fileLib.doesFileExist( dstName, dstPath ) ) then return 1 -- 1 = File already exists (don't overwrite) end end -- Copy the source file to the destination file local rFilePath = system.pathForFile( srcName, srcPath ) local wFilePath = system.pathForFile( dstName, dstPath ) local rfh = io.open( rFilePath, "rb" ) local wfh, errorString = io.open( wFilePath, "wb" ) if not ( wfh ) then -- Error occurred; output the cause print( "File error: " .. errorString ) return false else -- Read the file and write to the destination directory local data = rfh:read( "*a" ) if not ( data ) then print( "Read error!" ) return false else if not ( wfh:write( data ) ) then print( "Write error!" ) return false end end end results = 2 -- 2 = File copied successfully! -- Close file handles rfh:close() wfh:close() return results end -- Copy "readme.txt" from "system.ResourceDirectory" to "system.DocumentsDirectory" copyFile( "readme.txt", nil, "readme.txt", system.DocumentsDirectory, true )
以下函数可用于测试文件夹或子文件夹中是否存在文件。只需记住在调用此函数之前将子文件夹名称附加到文件名即可。
以下函数将文件从一个文件夹复制到另一个文件夹。如果您需要将 system.ResourceDirectory
中捆绑的文件复制到 system.DocumentsDirectory
,这将非常有用。请注意,在使用此函数之前,必须创建目标子文件夹。
Corona 中的文件访问基于底层操作系统,因平台而异。在 iOS 设备上,您可以访问上述所有目录中的文件。但是,在 Android 上,没有字面意义上的 system.ResourceDirectory
,因为所有资源文件都位于压缩的 APK 文件中。
Corona 允许使用相应的 API 直接加载图像和音频文件,但它使用文件 I/O API 访问 Android 上的资源文件的能力有限。具体来说,以下类型无法从资源目录中读取:.html
、.htm
、.3gp
、.lua
、.m4v
、.mp4
、.png
、.jpg
和 .ttf
。
copyFile( "cat.png.txt", nil, "cat.png", system.DocumentsDirectory, true ) local catImage = display.newImage( "cat.png", system.DocumentsDirectory, 0, 100 )