MongoDB GridFS 文件网格系统

GridFS 文件网格系统是用于超出 16MB 以上的大型 BSON 文档而设计的文件存取系统。Perfect-GridFS 函数库提供两种不同的对象类用于访问 MongoDB 的 GridFS 文件系统:

  • GridFS:提供文件列表、检索、删除、上载、下载和删除整个文件系统的功能
  • GridFile:提供文件信息、下载、局部读写和设置文件读写位置的功能

macOS 编译注意事项

请⚠️注意⚠️本模块需要 MongoDB 的 C语言模块在1.7.1版本之后的特殊修订,详见BSON-API 无法设置文件属性问题的修订

GridFS 类

GridFS 程序基于 MongoClient 类之上。首先,请确定已经启动了一个 Monge Client 对象的实例,然后调用该实例的 gridFS() 方法,并在调用时注意提供必要的数据库名,以及可选的前缀参数:

// 假设 let client = try MongoClient(uri: "mongodb://...")
let gridfs = try client.gridfs(database: "test")

默认情况下可以忽略 prefix 前缀参数,但是也可以根据需要自行设置:

let gridfs = try client.gridfs(database: "test", prefix: "clip")

GridFS 类方法

GridFS 对象类提供以下方法

  • list(filter 可选参数): 列出服务器上所有文件 / 部分文件(如果包含了可选的过滤器参数)
  • drop(): 删除数据库上的文件系统以及所哟文件
  • search(filename): 在服务器上搜索一个名为 filename 的文件
  • delete(filename): 从服务器上删除一个名为 filename 的文件
  • upload(from, to): 把本地文件 from 上传到服务器上并命名为 to
  • download(from, to): 把服务器文件 from 下载到本地路径 to
  • close(): 关闭文件系统

文件清单

list() 方法能够返回一个 [GridFile] 数组。如果希望缩小清单范围,可以使用一个类型为 BSON 的 filter 参数:

func list(filter: BSON? = nil) throws -> [GridFile]

默认情况下,list() 并不要求输入过滤器参数,但是实际上内部程序自动生成了一个默认的 bson 结构,并将查询结果设置为“按照字母顺序排序”:

let list = try gridfs.list()
print("\(list.count) file(s) found")

删除文件系统

使用 drop() 方法能够要求 GridFS 删除当前数据库上的整个文件系统以及所有相关文件。

gridfs.drop()

检索文件

请使用 search() 方法检索文件并获取文件信息、或在检索结果基础之上执行更加复杂的大文件操作(见 GridFile 对象类)。如果文件没找到,则会返回一个 MongoClientError 异常:

let f = try gridfs.search(name: "file.name.on.server")
// 打印文件对象内部序列号
print(f.id)
// 输出文件长度,以字节计算
print(f.length)

删除文件

调用 delete() 方法能够根据文件名称删除文件。如果文件不存在,则会返回MongoClientError 异常:

try gridfs.delete(name: "file.name.on.server")

上载文件

输入本地路径名称和预期服务器名称则可以上传一个文件,如果失败会报错:

let gridfile = try gridfs.upload(from: "/path/to/local.file", to: "name.on.server.type", contentType: "primaryType/subType", md5: "MD5 string", metaData: BSON(json: "meta data in a json string"), aliases: BSON(json: "aliases in a json string")
print(gridfile.length)

参数:

  • from: 待上传的本地文件路径
  • to: 目标存放在服务器上的文件名
  • contentType: 文件类型,可选。默认值为 text/plain
  • md5: 可选字符串,代表文件的MD5校验值。⚠️请注意⚠️可以使用Perfect COpenSSLPerfect COpenSSL Linux Edition 计算字符串的MD5
  • metaData: 可选的BSON()格式,代表文件的元数据属性
  • aliases: 可选的BSON()格式,代表文件的别名

下载文件

只需确定目标服务器文件名称以及本地存储路径即可实现文件下载。如果文件不存在或者网络出问题,则会抛出MongoClientError异常:

do {
    let bytes = try gridfs.download(from: "name.on.server", to: "/local/path/downloaded.name")
    print("总共下载了 \(bytes) 个字节")
}catch(let err) {
    print("下载中出现错误: \(err)")
}

关闭文件系统连接

由于 Swift 的高效垃圾回收机制,实用中并不要求手工关闭文件系统连接。但是用户当然可以在使用完成后强制关闭文件系统:

defer {
    gridfs.close()
}//end defer

GridFile 对象类

GridFS 的方法 search()list() 会分别返回 GridFile类对象 和 [GridFile] 数组。GridFile 用于管理文件信息以及处理更复杂的大文件操作,如文件位置游标及局部内容分片读写等等。

GridFile 属性(只读):

  • id: oid 标识字符串。
  • md5: md5 校验字符串。
  • aliases: BSON 类型的属性,代表文件别名。
  • contentType: 代表文件内容类型的字符串。
  • length: 64位整型的文件长度。
  • uploadDate: 64位整型时间戳,Unix纪元格式。
  • fileName: 存储在服务器上的文件名。
  • metaData: BSON 类型的元数据。

GridFile 方法:

除了上述属性之外,GridFile 还包括以下方法:

  • download(): 直接下载整个文件
  • tell(): 获取当前文件游标指向位置,UInt64类型整数
  • seek(cursor, 可选 whence): 根据可选参数 whence 确定文件游标新位置
  • paritallyRead(amount, 可选 timeout): 在规定的可选时间内,从当前游标位置开始读取文件部分内容并返回到一个二进制缓冲区[UInt8]
  • partiallyWrite(bytes, optional timeout): 在规定的可选时间内,在当前游标位置写入一个二进制缓冲区
  • close(): 关闭文件连接

download() 下载文件

GridFile对象的下载方法 download() 是文件系统 GridFS.download()方法的基础,后者在内部调用前者。如果已经获取了GridFile 对象句柄,那么可以直接设置本地路径来下载该文件:

do {
    let bytes = try gridfile.download(to: "/local/path/downloaded.name") 
    print("共下载了 \(bytes) 字节")
}catch(let err) {
    print("下载时出现异常: \(err)")
}

tell() 获取游标

tell() 方法用于取得当前文件实例的缓冲区游标,返回类型为64位无符号整数:

let position = gridfile.tell()
print(“当前文件指向 \(position)”)

seek() 设置游标

seek() 方法用于设置 GridFile对象的游标位置,可选参数whence用于说明相对位置所用的参考点。.begin为默认参考点,代表从文件头开始计算相对位置;而.current表示偏移量从目前游标所在位置开始计算;.end则代表从文件尾部开始计算偏移量:

enum Whence {
    // 从文件头开始计算偏移量
    case begin
    // 从当前游标所在文件位置计算相对偏移量
    case current
    // 从文件尾部最后一个字节开始反向计算相对偏移量
    case end
 }//end whence

func seek(cursor: Int64, whence:Whence = .begin) throws

比如,下面的代码将文件游标指向了从文件头开始1兆字节所在位置:

try gridfile.seek(1048576)

partiallyRead() 部分读取

为了能够在线处理大型文件,GridFile 提供了文件局部读写功能。首先是部分读取函数:

func partiallyRead(amount: UInt32, timeout:UInt32 = 0) throws -> [UInt8] 

参数 amount 是计划读取的字节数量,而参数 timeout 表示等待执行该操作所需要的时间毫秒数。如果读取失败,则会抛出 MongoClientError 异常。请注意 ⚠️NOTE⚠️ 任何关于文件的操作,如游标设置函数 seek()、部分读取和部分写入函数paritallyRead()partiallyWrite()都会改变文件游标位置。请参考下列程序理解文件游标的概念:

let file = try gridfs.search("a.large.file")
let cursor = file?.tell()
print("在没有任何操作以前,游标取值应该是零。当前游标值 = \(cursor)")
let oneMegaByte = 1048576
// 将游标指向距离文件起始位置100兆字节处
try file?.seek(cursor: Int64(oneMegaByte * 100))
// 然后从这个位置读取1兆数据
let bytes = try file?.partiallyRead(amount: UInt32(oneMegaByte))

partiallyWrite() 部分写入

方法 partiallyWrite() 部分写入是与上面的 partiallyRead()相反的操作。如果写入失败,则会抛出一个 MongoClientError 异常:

public func partiallyWrite(bytes:[UInt8], timeout:UInt32 = 0) throws -> Int

第一个参数bytes是待写入文件的缓冲区,而可选参数timeout则是等待写入操作的毫秒数时间,如果忽略超时参数则函数在调用后立刻返回。 ⚠️再次注意⚠️,任何关于文件的操作,如游标设置函数 seek()、部分读取和部分写入函数paritallyRead()partiallyWrite()都会改变文件游标位置:

// 将文件游标指向距离文件尾部1K字节处:
try file?.seek(cursor: 1024, whence: .end)
let buffer:[UInt8] = [1,2,3,4,5]
// 写入一组数据
let totalWritten = try file?.partially(bytes: buffer)
print(totalWritten)

如果成功的话,上述代码会在距离文件尾部1K位置写入5个字节。

close() 关闭文件

由于 Swift 的高效垃圾回收机制,实用中并不要求手工关闭文件连接。但是用户当然可以在使用完成后强制关闭文件:

defer {
    gridfile.close()
}//end defer

关于性能

大型文件读写通常都是非常耗时的,因此,请尝试使用线程管理Threading.dispatch {},避免上传下载文件时阻塞主程序,或者尽量使用小片文件的读写partiallyRead()/Write()。关于线程的更多信息,请查看Perfect Threading