MongoDB 的文档以BSON 格式存储,支持二进制数据类型,所以可以把文件的二进
制格式的数据直接保存到MongoDB 的文档中。这是考虑数据的最自然方法,比传统的
行/列模型更具表现力和功能。但是MongoDB 数据库中每个文档的长度是有限制的,而
一般上传的图片、视频等文件又比较大,针对这种情况,MongoDB 提供了一种处理大文
件的规范———GridFS(GridFileSystem )。

GridFS 用于存储和检索MongoDB 中的大文件,它是文件存储的一种方式,特殊的
是它存储在MongoDB 的集合中。本章对MongoDB 数据库中GridFS 进行了介绍,并描
述了如何应用Java和Python使用GridFS 。

5.1 
GridFS 
概述
5.1.1 
GridFS 
概念
GridFS 是Mongo的一个子模块,在MongoDB 中用来存储和检索那些超过16MB 
(BSON 文件限制)的文件(如图片、音频、视频等)。它是文件存储的一种方式,特殊的是
它存储在MongoDB 的集合中。

GridFS 可以更好地存储大于16MB 的文件,它不会将文件存储在单个文档中,而是
将大文件对象分割成多个小的块,每个块将作为MongoDB 的一个文档被存储在文件片
段集合中。类似地,不大于块大小的文件仅使用所需的空间以及一些其他元数据。每个
文件的实际内容被存在文件片段中,和文件有关的Mea数据(_e,还
有用户自定义的属性)将会被存在Files集合中。
tfilename,contenttyp

当从GridFS 中获取文件时,MongoDB 的驱动程序负责将多个块组装成完整文件。
开发人员可以通过GridFS 进行范围查询,可以访问文件的任意部分(如跳到视频文件或


80 
者音频文件的任意位置)。
要注意的是,GridFS不是MongoDB 自身的特性,它只是一种将大型文件存储在
MongoDB的文件规范,所有官方支持的驱动均实现了GridFS规范。GridFS制定大文件
在数据库中如何处理,通过开发语言驱动来完成、通过API来存储检索大文件。
5.1.2 GridFS应用场景
GridFS通常在如下几种场景中应用。
(1)如果当前文件系统限制目录下文件的数量,可以使用GridFS在目录下存储任意
多的文件。
(2)如果需要访问大数据文件,但并不想将整个文件全部加载到内存中,可以使用分
段访问代替一次加载。也就是使用GridFS存储文件,并读取文件部分信息。
(3)如果希望在多个系统之间实现文件和元数据的同步,可以使用GridFS实现分布
式文件存储。
5.2 GridFS存储原理
5.2.1 GridFS存储结构 
GridFS将要存储的文件分成若干块,每一块作为一个单独的文档来存储,每块默认
大小为256KB。
MongoDB中集合的命名包括数据库名称和集合名称。在命名过程中会像如下方式
将数据库名和集合名通过“.”分隔开。 
<Database>.<Collenction> 
GridFS存储文件的集合也使用了这种命名方式。
MongoDB的GridFS使用两个集合来存储一个文件:fs.files与fs.chunks。fs.files 
集合用于存储上传到数据库的文档的信息,也就是文件的元数据。fs.chunks集合用于存
储上传文件的内容的二进制数据。一个块就相当于一个文档(较大文件会被拆分成多个
有序的块存储)。
fs.files存放的文件信息如例5-1所示。
【例5-1】 fs.files存放的文件信息。 
{ 
"filename": "test.txt", 
"chunkSize": NumberInt(261120), 
"uploadDate": ISODate("2014-04-13T11:32:33.557Z"), 
"md5": "7b762939321e146569b07f72c62cca4f", 
"length": NumberInt(646) 
}

81 
在例5-1中_id是唯一标识,length是文件总长度,chunkSize是块的大小,uploadDate是
时间戳。md5是文件内容的MD5校验和,其值由服务器端生成,用于计算上传块的MD5校
验和,用户可以校验MD5的值确保文件正确上传。contentType是文件类型,还可以添加其
他键来标识这个文件,如上传者的信息。
fs.chunks存放文件的数据如例5-2所示。
【例5-2】 fs.chunks存放文件的数据。 
{ 
"files_id": ObjectId("534a75d19f54bfec8a2fe44b"), 
"n": NumberInt(0), 
"data": "Mongo Binary Data" 
} 
在例5-2中_id是唯一标识,files_id是文件集合中的_id,n指文件的第n个块,data 
是文件的二进制数据。
5.2.2 GridFS存储过程
MongoDB使用GridFS存储文件时,会先将文件按照块的大小分为多块,最终将块
的信息存储在fs.chunks集合的多个文档中,将文件信息存储在fs.files集合的唯一一个
文档中。要注意的是,这之中fs.chunks集合中文档的file_id字段对应fs.files集合中的_ 
id字段。文档存储过程如图5-1所示。
图5-1 GridFS存储过程

82 
5.3 GridFS基本操作
5.3.1 使用Shell操作MongoDBGridFS 
MongoDB提供mongofiles工具,可以使用命令行来操作GridFS。其实有5个主要
命令,分别为:put(存储命令),get(获取命令),list(列表命令),find(查找命令)和delete 
(删除命令)。这些命令都是按照filename操作GridFS中存储的文件的。
1.存储数据
上传文件时,如果文件大于块的大小,则把文件分割成多个块,再把这些块保存到fs. 
chunks中,最后再把文件信息存到fs.files中。如果使用GridFS的put命令来存储MP3 
文件,调用MongoDB安装目录下bin的mongofiles.exe工具。打开命令提示符,进入到
MongoDB的安装目录的bin目录中,找到mongofiles.exe,并输入例5-3的代码。
【例5-3】 使用GridFS的put命令来存储MP3文件的指令。 
>mongofiles -d gridfs put song.mp3 
在例5-3中,-dgridfs指定存储文件的数据库名称,如果不存在该数据库,MongoDB 
会自动创建。song.mp3是音频文件名。
需要注意的是,GridFS不会自动处理MD5值相同的文件。也就是说,如果对同一个
文件进行两次put操作,那么GridFS在存储过程中将会使其对应两个不同的存储,这在
数据存储过程中是一种严重的浪费。所以想要存储MD5值相同的文件,可以提前通过
API进行扩展处理之后存储到GridFS当中。
2.获取数据
下载文件使用get方法。具体指令如例5-4所示。
【例5-4】 使用get方法下载文件。 
mongofiles -d gridfs -l "下载到的位置" get song.mp3 
3.查看数据
同时可以使用list查看文件列表,使用search查找文件,或是使用delete删除文件。
代码如例5-5所示。其中,-d指定数据库实例,-l[--local]表示上传/下载时的本地文件
名,默认与GridFS上的文件名一致。
【例5-5】 使用list查看文件列表,使用search查找文件,或者使用delete删除文件。 
mongofiles -d gridfs list 
mongofiles -d gridfs search song.mp3 
mongofiles -d gridfs delete song.mp3

83 
读取文件时,先根据查询的条件,在fs.files中找到对应的文档,得到_id的值,再根据
这个值到fs.chunks中查找所有files_id为_id的块,并按n排序,最后依次读取块中data 
对象的内容,还原成原来的文件。
4.查找数据
GridFS在上传文件过程中是先把文件数据保存到fs.chunks,最后再把文件信息保
存到fs.files中,所以如果在上传文件过程中失败,有可能在fs.chunks中出现垃圾数据。
这些垃圾数据可以定期清理掉。使用“db.fs.files.find()”命令来查看数据库中文件的
文档。可
以看到fs.chunks集合中所有的区块,如果得到了文件的_id值,就可以根据这个
_id获取块数据,如例5-6所示。
【例5-6】 根据这个_id获取块数据。 
>db.fs.chunks.find({files_id:ObjectId(_id)}) 
5.删除数据
MongoDB不会自动释放已经占用的硬盘空间,即使删除数据库中的集合也无法将
占用的磁盘空间释放,这就需要开发人员手动释放磁盘空间。常用的释放磁盘空间的方
式有两种。
1)修复数据库以回收磁盘空间
通过修复数据库来回收磁盘空间,即在MongoShell中运行“db.repairDatabase()” 
命令或者“db.runCommand({repairDatabase:1})”命令。通过修复数据库方法回收磁
盘时需要注意,待修复磁盘的剩余空间必须大于等于存储数据集占用空间加上2GB,否
则无法完成修复。因此使用GridFS大量存储文件必须提前考虑设计磁盘回收方案,以
解决MongoDB磁盘回收问题。
2)dump&restore方式
使用dump&restore方式也就是先删除MongoDB数据库中需要清除的数据,然后
使用MongoDump备份数据库。备份完成后,删除MongoDB 的数据库,使用Mongo 
Restore工具恢复备份数据到数据库。当使用“db.repairDatabase()”命令没有足够的磁
盘剩余空间时,可以采用dump&restore方式回收磁盘资源。如果MongoDB是副本集
模式,dump&restore方式可以做到对外持续服务,在不影响MongoDB正常使用下回收
磁盘资源。MongoDB使用副本集,实践使用dump&restore方式回收磁盘资源,70GB的
数据在2h之内完成数据清理及磁盘回收,并且整个过程不影响MongoDB对外服务,同
时可以保证处理过程中数据库增量数据的完整。
5.3.2 使用Java操作MongoDBGridFS 
使用Java操作MongoDBGridFS与操作MongoDB相近。以下代码展示了Java对
MongoDBGridFS进行增删改查的操作。在使用Java操作MongoDBGridFS之前,首先

84 
要建立连接,确定端口和数据库。其中,localhost和27017 分别表示IP 和端口号, 
MongoDB指数据库名称。在建立连接后,良好的习惯是通过异常测试确保后文的操作
只在连接正常的情况下进行。建立Mongo连接如例5-7所示。
【例5-7】 建立连接,确定端口和数据库。 
private static Mongo mg = null; 
private static DB db = null; 
private static GridFS myFS = null; 
public MongoTest(String ip, int port, String dbName){ 
try { 
mg = new Mongo(ip, port); 
db = mg.getDB(dbName); 
myFS = new GridFS(db); 
} catch (Exception e) { 
e.printStackTrace(); 
} 
}
public static void main(String[] args) { 
MongoTest mongodb = new MongoTest("localhost", 27017, "mongodb"); 
} 
1.查询数据
成功连接到数据库后,可以使用getFileList()方法查看数据库中现有的文件集合。
使用Java语句查看MongoDB数据库中的文件集合的方式如例5-8所示。
【例5-8】 查看数据库中的文件集合。 
public void queryGridFS() { 
DBCursor cursor = myFS.getFileList(); 
While(cursor.hasNext()){ 
System.out.println(cursor.next()); 
} 
}
public static void main(String[] args) { 
MongoTest mongodb = new MongoTest("localhost", 27017, "mongodb"); 
mongodb.queryGridFS(); 
} 
例5-8中的queryGridFS()方法用于查看GridFS 中存储的数据,在方法中使用
getFileList()接口找到数据列表,并将文件逐条打印。之后的main()方法中通过建立连

85 
接后使用该方法获取查看数据方法的结果。
2.存储数据
要想使用Java存储数据到GridFS,可以使用save()方法。例5-9展示了使用save() 
方法存储数据的流程。
【例5-9】 存储数据。 
private static String oid=null; 
public void saveGridFS(String localPath){ 
try{ 
File f = new File(localPath); 
GridFSInputFile inputFile = myFS.createFile(f); 
inputFile.save(); 
oid = inputFile.getId().toString(); 
System.out.println("Save Success"); 
} catch { 
e.printStackTrace(); 
} 
}
public static void main(String[] args) { 
MongoTest mongodb = new MongoTest("localhost", 27017, "mongodb"); 
mongodb.saveGridFS("C://Users//abc//Desktop//test.png"); 
} 
在例5-9的saveGridFS()方法中,首先输入数据存放的本地位置,之后使用save()方
法存储输入的数据,最后使用getId()方法将该数据存储在GridFS中的id,将这个id存
储到oid变量中,供之后查找数据使用。
3.查找数据
可以使用之前保存的id,通过findOne()方法查找数据库中的对应数据。查找数据
的方法如例5-10所示。
【例5-10】 查找数据。 
public void findGridFS(String oid){ 
GridFSDBFile inputFile = myFS.findOne(new BasicDBObject( "_id", new 
ObjectID(oid))); 
} 
从GridFS中读取的数据可以直接存储到本地,也可以存储到其他主机上。具体代
码如例5-11所示。

86 
【例5-11】 将数据存储到本地。 
public void readGridFS2Local(String oid, String localPath) { 
try { 
GridFSDBFile inputFile = myFS.findOne(new BasicDBObject( "_id", new 
ObjectID(oid))); 
inputFile.writeTo(localPath); 
System.out.println("Save Success"); 
} catch (Expection e) { 
e.printStackTrace(); 
} 
} 
例5-11的readGridFS2Local()方法的功能是读取GridFS中的数据并将其存入本地
数据库,方法的参数包括oid(要读取数据的id)和localPath(存储到本地的路径)。在方
法体中,通过try…catch确保创建和写入过程出现的异常可以被及时发现,之后使用
findOne()方法找到对应的数据,将数据存储在变量inputFile中后使用writeTo()方法将
数据存储到本地对应的路径中。
例5-12的readGridFS2Client()方法将数据存储到其他主机。
【例5-12】 将数据存储到其他主机。 
public void readGridFS2Client(String oid, String ip, int port, String username, 
String passwd) { 
try { 
GridFSDBFile inputFile = myFS.findOne(new BasicDBObject("_id", new 
ObjectID(oid))); 
InputStream inputStream = inputFile.getInputStream(); 
FTPClient fc = new FTPClient(); 
fc.connect(ip, port); 
fc.login(username, passwd); 
fc.setBufferSize(1024); 
fc.setFileType(FTP.BINARY_FILE_TYPE); 
fc.enterLocalPassiveMode(); 
inputStream.close(); 
fc.logout(); 
fc.disconnect(); 
System.out.println("Save Success"); 
} catch (Expection e) { 
e.printStackTrace(); 
} 
} 
将数据存储到其他主机的readGridFS2Client()方法中首先使用findOne()方法查找

87 
指定数据并存储在变量inputFile中,之后新建一个FTPClient()类型的变量用于存储另
一个数据库的信息,分别使用connect()和login()接口连接另一个数据库后,将数据存入
该数据库,之后退出远程主机,断开与另一客户端的连接,整个存储过程完成。
在main方法中测试例5-11和例5-12的方法如例5-13所示。
【例5-13】 测试类。 
public static void main(String[] args) { 
MongoTest mongodb = new MongoTest("localhost", 27017, "mongodb"); 
mongodb.readGridFS2Local(oid, "C://Users//abc//Desktop//test1.png"); 
mongodb.readGridFS2Client(oid, ip, port, username, passwd); 
} 
main函数中首先建立连接,之后分别向readGridFS2Local()方法和readGridFS2Client() 
方法中传入对应的参数,查看使用Java读GridFS的数据保存到指定位置的效果。
4.删除数据
删除GridFS中的数据也需要先找到所需删除的文件,之后使用oid使用remove() 
方法。具体代码如例5-14所示。
【例5-14】 删除数据。 
public void removeGridFS(String oid) { 
myFS.remove(new BasicDBObject("_id", new ObjectId(oid))); 
System.out.println("Remove Success"); 
}
public static void main(String[] args) { 
MongoTest mongodb = new MongoTest("localhost", 27017, "mongodb"); 
mongodb.queryGridFS(); 
mongodb.removeGridFS(oid); 
mongodb.queryGridFS(); 
} 
例5-14的removeGridFS()方法传入一个oid参数作为文件id。函数体使用remove() 
方法删除对应id的数据,之后显示删除成功。
最后,即可使用close()语句关闭数据库连接。具体代码如例5-15所示。
【例5-15】 关闭MongoDB数据库。 
mg.close(); 
5.3.3 使用Python操作MongoDBGridFS 
类似Java中的操作,使用Python也可以对MongoDBGridFS进行一系列操作。下

88 
列代码很好地展示了Python对MongoDBGridFS进行增删改查的操作。
例5-16的代码建立连接,确定端口和数据库。
【例5-16】 建立连接并确定端口和数据库。 
UploadCache = "uploadcache" 
dbURL = "mongodb://192.168.20.120:27010" 
1.上传文件
例5-17是一个使用Python上传文件到MongoDBGridFS的函数。
【例5-17】 upLoadFile()方法。 
def upLoadFile(self, file_coll, file_name, data_link): 
client = pymongo.MongoClient(self.dbURL) 
db = client["xddq_device_maintenance"] 
filter_condition = {"filename": file_name, "url": data_link, 'version':2} 
gridfs_col = GridFS(db, collection=file_coll) 
file_ = "0" 
query = {"filename": ""} 
query["filename"] = file_name 
if gridfs_col.exists(query): 
print('文件已经存在') 
else: 
with open(file_name, 'rb') as file_r: 
file_data = file_r.read() 
file_ = gridfs_col.put(data=file_data,**filter_condition) 
print(file_) 
return file_ 
在例5-17的upLoadFile()方法的参数中,file_coll是集合名;file_name是文件名,属
于自定义属性字段;data_link是文件链接,也属于自定义属性字段。
在方法中首先定义变量存储数据库和待读入数据,之后通过判断文件是否存在限制
只读入存在的文件。存入文件使用put()方法。当文件已经在数据库中时,方法会返回
提醒“文件已经存在”,否则返回files_id,也就是上传的文件存储后其对应的id。
2.下载文件
接下来是下载文件。下载文件可以选择按照不同的方式下载,如例5-18按照文件名
下载的downLoadFile()方法。
【例5-18】 downLoadFile()方法。 
def downLoadFile(self, file_coll, file_name, out_name, ver=-1):