提高文件操作功能
作者:Steven Feuerstein
Oracle9i第2版中的UNT_FILE提高了文件输入/输出(I/O)功能。
有些人可能会说你可以在Oracle数据库中包含和表示整个世界,甚至是整个宇宙。这或许是真的,但我们中仍有一些人希望能够从我们的PL/SQL程序内部处理操作系统(OS)文件。有了这个愿望,开发人员在很长时间内就同UTL_FILE包之间有了一种爱憎交加的关系。
Oracle技术网站(OTN)上关于UTL_FILE的示例 |
几年来,UTL_FILE提供了一种在PL/SQL中读写文件的途径。但是,开发团体中的一些人希望能有进一步的发展,而不是仅停留在在文件中依次读写行的层次上。有些开发人员希望能够对文件进行删除、重命名以及执行其他与文件相关的典型操作,但是UTL_FILE并不支持这些操作。
Oracle9i第2版对这些讨厌的UTL_FILE限制进行了改进。本文探讨了Oracle9i第2版在UTL_FILE包中增加的一些非常有用的新功能,包括:
- UTL_FILE.FREMOVE 删除文件。
- UTL_FILE.FRENAME 重命名文件,事实上,还可以移动文件。
- UTL_FILE.FCOPY 将一个文件的全部或部分复制到另一个文件中。
- UTL_FILE.FGETATTR获取文件的长度之类的属性。
除了新程序之外,UTL_FILE现在还允许数据库定义的目录对象指定操作系统目录的位置。尽管本文不可能包括UTL_FILE的所有新特性,但它介绍的内容仍然足以使你对在本地PL/SQL代码中进行文件I/O操作的新特性感到兴奋。
对目录进行操作
在Oracle9i第2版之前,当调用FOPEN打开文件(用于读或写)时,UTL_FILE要求必须明确指出文件的位置。这不是最优的实施,因为它意味着开发人员必须在应用程序中的多个地方对那些位置进行硬编码(hard-code)。如果目录改变了,就必须进行很多麻烦的整理工作。
你现在可以在Oracle9i第2版中为目录的文件系统位置指定一个Oracle目录对象的名字。这种技巧"隐藏"了实际的操作系统位置。如果那个位置需要改变,只需要更新目录对象的定义,所有对FOPEN,FREMOVE,FRENAME和FCOPY的调用都不会受到影响。
为了创建一个目录对象,需要具备CREATE ANY DIRECTORY权限,然后就可以像下面的例子那样定义一个新的目录对象:
CREATE OR REPLACE DIRECTORY DEVELOPMENT_ DIR as '/dev/source'; CREATE OR REPLACE DIRECTORY TEST_DIR as '/test/source';
你应当知道当你创建目录对象时,Oracle数据库并不会验证你所指定的位置。另一点需要留意的是当你在一个调用(如调用UTL_FILE.FOPEN)中指定目录对象的名字时,它被看作一个区分大小写的字符串。换而言之,如果你不是用大写字母指定目录对象的名字,操作将会失败。
在创建了目录对象之后,你可以按下面的方法授权特定的用户使用目录对象:
GRANT READ ON DIRECTORY DEVELOPMENT_DIR to senior_developer;
最后,你可以查询ALL_DIRECTORIES的内容来确定在当前连接的模式中,哪些目录对象可用。这里有一个布尔函数会告诉你指定的目录对象是否可用:
CREATE OR REPLACE FUNCTION dir_available ( dir_in IN VARCHAR2, uppercase_in IN BOOLEAN := TRUE ) RETURN BOOLEAN IS v_dir VARCHAR2 (100) := dir_in; retval BOOLEAN; dummy CHAR (1); BEGIN IF uppercase_in THEN v_dir := UPPER (v_dir); END IF; SELECT 'x' INTO dummy FROM all_directories WHERE directory_name = l_dir; RETURN TRUE; EXCEPTION WHEN NO_DATA_FOUND THEN RETURN FALSE; END;
采用这种方法创建实用程序的一个好处是:你可以很轻松地增加关于目录大小写的高级操作,以避免格式错误,如忘记指定目录名字为大写等。
复制、删除和移动文件
在过去,利用UTL_FILE复制文件唯一的方法是编写大量代码来逐行读取一个文件的内容,然后再将其逐行写到一个新的文件。现在,你只需要让UTL_FILE来为你完成这项工作。在清单1中,我用UTL_FILE.FCOPY执行一个选择性的备份--一个从开发目录到存档目录的单一文件的复制。
你也可以用FCOPY仅复制一个文件的一部分。为此,你需要指明文件中希望复制的起始和结束行号。假设我有一个文本文件,其中包含有我儿子的保龄球联盟锦标赛各年冠军的名字。我从1990年开始记录这些名字,并希望将1996年之前的所有名字移到另一个文件。我可以利用下面程序的第5个和第6个参数来完成这个操作:
- Copy just a part of a file UTL_FILE.fcopy ( src_location => 'WINNERS_DIR', src_filename => 'names.txt', dest_location => 'OLD_NEWS_DIR', dest_filename => 'prevnames.txt', start_line => 1, end_line => 6 );
删除文件也是一件非常容易的事情。假设在将我的zip文件移到存档目录之后,为了释放一些磁盘空间,我希望将它删除。这是部署UTL_FILE.FREMOVE的好机会。注意,在清单2中,我还为新的UTL_FILE.DELETE_FAILED异常定义了一个明确的异常句柄。这种方法使我能对失败的删除操作进行标记(例如,因为我没有所需的权限而导致的失败。)
我还可以通过调用UTL_FILE.FRENAME程序将复制和删除操作合并为一步。这个方便的实用程序使我既能够在相同的目录中重命名文件,也能够对文件的位置和名字都进行重新命名(实际上就是移动文件)。清单3中的例子使用了FRENAME来移动文件archive.zip。
再次说明,当你使用FRENAME时,你应该定义一个异常句柄,它可以十分清楚地捕获重命名失败。获得一个文件的属性
这个文件有多大?某个特定的文件是否存在?我的文件的块大小是多少?有了操作系统命令的帮助,这些问题不再神秘。UTL_FILE. FGETATTR现在可以在一个本地程序调用中提供所有这些信息。也许利用FGETATTER的最好方法是建立你自己的函数--在内置函数上--来回答一个问题,如清单4中返回一个文件大小(长度)的例子。
有了适当的函数,我现在可以很容易地得到文件的大小,而不必为每个通过FGETATTER得到的属性声明一个变量,如下面的PL/SQL例子:
how_big := flength ('DEVELOPMENT_DIR', 'all_the_rules.pkg');
这个FLENGTH函数还包括查找一个文件块大小和确定一个文件是否存在的代码。你可以很容易地使用与查找文件长度(大小)相同的技巧(如清单4所示)创建函数,以得到块的大小,并返回一个简单的布尔值来确定文件是否存在。
在写文件时提高了控制能力
UTL_FILE的另一个新功能是PUT_LINE的“自动清洗(auto-flush)”特性。当你在程序中将数据写出到一个文件中时,它不会立即显示在那个文件中,以备读取。操作系统肯定会利用异步I/O,将多个写操作的结果输出到缓冲区,然后再将它们发送到磁盘上。
尽管异步I/O提高了性能,但对于那些需要立即看到一个文件(如日志文件)内容的程序员或支持人员来说,这是一个非常不方便的特性。
现在,UTL_FILE在UTL_FILE.PUT_LINE程序中包括了一个新的参数,以便开发人员可以指定她希望立即输出到磁盘的文本行。清单5中的程序说明了这一技巧。
Oracle对开发人员要求的响应
为使PL/SQL应用更为广泛,更为成功,它必须强有力地支持大量功能。与操作系统文件的交互当然是其中的一个关键部分。
Oracle又一次响应了开发团体的要求,并改进了PL/SQL的核心功能。Oracle9i第2版中的UTL_FILE现在对常用操作的支持已经达到了相当高的水平。
Steven Feuerstein (stevenfeuerstein@quest.com) 从1980年开始开发软件,他是PL/SQL语言的权威。Steven编写了六本关于PL/SQL的书,其中包括《PL/SQL最佳实践》(PL/SQL Best Practices)和《Oracle PL/SQL编程》(Oracle PL/SQL Programming)(都由 O'Reilly & Associates出版)。他是Quest Software公司的高级技术顾问。