文件读写

本指南讨论如何从设备的本地存储读取和写入文件。

概述

读写文件——以纯文本、JSON、XML、本地 SQLite 数据库等形式存在的数据——对于应用程序开发至关重要。您的应用程序可能需要读写文件的场景数不胜数,以下是一些常见情况:

获取文件路径

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

读取文件

要读取文件,只需获取文件路径,以读取模式打开 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

读取行

读取数据时的另一个常见任务是处理文件的每一,并将其用于某种用途。例如,如果您有一个文本文件,其中包含产品列表以及每种产品的价格,则可以按行循环浏览该文件,并单独输出或解析每一行(产品)。

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.DocumentsDirectorysystem.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.DocumentsDirectorysystem.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 )

Android 文件限制

以下函数可用于测试文件夹或子文件夹中是否存在文件。只需记住在调用此函数之前将子文件夹名称附加到文件名即可。

以下函数将文件从一个文件夹复制到另一个文件夹。如果您需要将 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 )