posts - 262,  comments - 221,  trackbacks - 0
【一】Apache commons IO包之FileUtils

IO文件操作,可以说是除了JDBC操作之外,日常最常用的功能之一了。IO的读写方式如何,直接影响到系统的性能。很多时候系统的性能瓶颈往往不是出现在对象层面,而是出现在底层的IO层面上。

Apache commosn IO包在input, output包的基础上,提供了一个高效,方便的文件类处理工具:FileUtils,其功能涵盖了所有日常常用的IO操作,由于这个类的部分方法底层是基于Apache commons IO自己的读写流去实现的,所以在性能上会相对高于JDK自带的类(具体原因可以参考这篇文章:IO与文件读写---使用Apache commons io包提高读写效率)

根据Apache commons IO官方的说法,这个类可以提供如下功能:



总体上来说,主要有:

 ※ 资源的创建、删除
 ※ 资源的复制、移动
 ※ 资源的读写
 ※ 资源的比较
 ※ 资源的过滤
 ※ 资源的转换

【二】FileUtils的常用API及解析

①资源的创建、删除

 ※ 目录的创建: 
forceMkdir(File directory),这个方法可以在父目录不存在的情况下,连续创建多个目录。但如果同名的目录已经存在或者无权创建,则抛出异常

 ※ 文件的创建:
touch(File file),这个方法用于创建一个size为0的文件,然后迅速关闭输出流。如果文件已经存在则简单地修改一下文件的modify time

 ※ 目录/文件的删除:
void deleteDirectory(File directory),递归地删除目录及其下的所有内容。

boolean deleteQuietly(File file),相比于JDK的delete()方法有两个不同:首先它不需要被删除目录下的内容为空,其次它不会抛出任何IOException。

void forceDelete(File file),强行删除file对象,如果是目录对象则递归删除子目录。如果删除失败则显式地抛出IOException。

void forceDeleteOnExit(File file),当JVM退出时,把file对象删除。如果是目录对象则递归删除子目录。


②资源的复制、移动

 ※ 复制目录或文件
copyDirectory(File srcDir, File destDir, FileFilter filter, boolean preserveFileDate),这个方法用于把源目录及其下面的子目录,文件一起拷贝到目标位置(名称可改)。而且该方法允许在拷贝的过程中进行过滤,指定仅拷贝那些符合条件的资源。最后一个选项用来表明是否保留文件原有的创建、修改日期还是使用最新的日期。

copyDirectoryToDirectory(File srcDir, File destDir),这个方法和上面的区别在于:首先上面的方法是拷贝到destDir的位置,而这个方法是拷贝到destDir“之下”的位置。其次上面的方法可以在拷贝的同时改名,这个方法不能,拷贝后仍然使用原来的名称。

copyFile(File srcFile, File destFile, boolean preserveFileDate),类似于上面的copyDirectory方法。

copyFileToDirectory(File srcFile, File destDir, boolean preserveFileDate),类似与上面的copyDirectoryToDirectory方法。

 ※ 移动目录或文件
moveToDirectory(File srcFile, File destDir, boolean createDestDir),这个方法用于移动一个文件或者目录到指定位置。

③资源的读写

这一部分是FileUtils的精华部分。

 ※ 读入文件
FileUtils支持对文件以字节数组,字符串,行的方式读入。对应方法分别是:

byte[] readFileToByteArray(File file) 
String readFileToString(File file)
String readFileToString(File file, String encoding)
List readLines(File file)
List readLines(File file, String encoding)


这里我们关心的是对于大文件,FileUtils是如何读入的?看看下面的源代码:
/**
     * Reads the contents of a file into a String.
     * The file is always closed.
     *
     * 
@param file  the file to read, must not be <code>null</code>
     * 
@param encoding  the encoding to use, <code>null</code> means platform default
     * 
@return the file contents, never <code>null</code>
     * 
@throws IOException in case of an I/O error
     * 
@throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM
     
*/

    
public static String readFileToString(File file, String encoding) throws IOException {
        InputStream in 
= null;
        
try {
            in 
= openInputStream(file);
            
return IOUtils.toString(in, encoding);
        }
 finally {
            IOUtils.closeQuietly(in);
        }

    }

可以见到这个方法调用了IOUtils的toString方法,那么这个过程的底层细节是如何的呢?
public static String toString(InputStream input, String encoding)
            
throws IOException {
        StringWriter sw 
= new StringWriter();
        copy(input, sw, encoding);
        
return sw.toString();
    }


public static void copy(InputStream input, Writer output, String encoding)
            
throws IOException {
        
if (encoding == null{
            copy(input, output);
        }
 else {
            InputStreamReader in = new InputStreamReader(input, encoding);
            copy(in, output);
        }

    }


/**
     * Copy bytes from a large (over 2GB) <code>InputStream</code> to an
     * <code>OutputStream</code>.
     * <p>
     * This method buffers the input internally, so there is no need to use a
     * <code>BufferedInputStream</code>.
     * 
     * 
@param input  the <code>InputStream</code> to read from
     * 
@param output  the <code>OutputStream</code> to write to
     * 
@return the number of bytes copied
     * 
@throws NullPointerException if the input or output is null
     * 
@throws IOException if an I/O error occurs
     * 
@since Commons IO 1.3
     
*/

    
public static long copyLarge(InputStream input, OutputStream output)
            
throws IOException {
        
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
        long count = 0;
        
int n = 0;
        
while (-1 != (= input.read(buffer))) {
            output.write(buffer, 
0, n);
            count 
+= n;
        }

        
return count;
    }

可以看到,Apache commons IO的底层是采用InputStreamReader来读的,而且是连续不断地在内存中构造String对象,如果有一个2G文件,那么将会在内存中构造一个2G的String对象。

 ※ 写入文件
void writeLines(File file, String encoding, Collection lines, String lineEnding),这个用法用于将内存中一个集合的内容持久化到本地文件,以行的方式写入每一个集合元素。可以指定编码和换行符。

这个方法的适用场景类似于:将内存中的一批“客户信息集合”导出到文件中
public static void writeLines(File file, String encoding, Collection lines, String lineEnding) throws IOException {
        OutputStream out 
= null;
        
try {
            out 
= openOutputStream(file);
            IOUtils.writeLines(lines, lineEnding, out, encoding);
        }
 finally {
            IOUtils.closeQuietly(out);
        }

    }


public static void writeLines(Collection lines, String lineEnding,
            OutputStream output, String encoding) 
throws IOException {
        
if (encoding == null{
            writeLines(lines, lineEnding, output);
        }
 else {
            
if (lines == null{
                
return;
            }

            
if (lineEnding == null{
                lineEnding 
= LINE_SEPARATOR;
            }

            
for (Iterator it = lines.iterator(); it.hasNext(); ) {
                Object line 
= it.next();
                
if (line != null{
                    output.write(line.toString().getBytes(encoding));
                }

                output.write(lineEnding.getBytes(encoding));
            }

        }

    }

void writeStringToFile(File file, String data, String encoding),这个方法将字符串一次性写入文件
public static void write(String data, OutputStream output, String encoding)
            
throws IOException {
        
if (data != null{
            
if (encoding == null{
                write(data, output);
            }
 else {
                output.write(data.getBytes(encoding));
            }

        }

    }

④资源的比较

 ※ 文件时间比较
boolean isFileNewer(File file, Date/File/Long) 和boolean isFileOlder(File file, Date/File/Long)两种方法,这两种方法的内部都是采用文件的last modify time进行比较的。

 ※ 文件内容比较
boolean contentEquals(File file1, File file2)采用逐字节比较的方式,在正式比较内容之前,会先比较以下项目:存在性、类型、长度、链接指向,最后才是比较内容
InputStream input1 = null;
        InputStream input2 
= null;
        
try {
            input1 
= new FileInputStream(file1);
            input2 
= new FileInputStream(file2);
            
return IOUtils.contentEquals(input1, input2);

        }
 finally {
            IOUtils.closeQuietly(input1);
            IOUtils.closeQuietly(input2);
        }


    
public static boolean contentEquals(InputStream input1, InputStream input2)
            
throws IOException {
        
if (!(input1 instanceof BufferedInputStream)) {
            input1 
= new BufferedInputStream(input1);
        }

        
if (!(input2 instanceof BufferedInputStream)) {
            input2 
= new BufferedInputStream(input2);
        }


        
int ch = input1.read();
        
while (-1 != ch) {
            
int ch2 = input2.read();
            
if (ch != ch2) {
                
return false;
            }

            ch 
= input1.read();
        }


        
int ch2 = input2.read();
        
return (ch2 == -1);
    }

⑤资源的过滤

FileUtils提供了两种类型的方法让使用者可以轻松地过滤文件、目录。它们分别是:

 ※ 基于Iterator形式的过滤
iterateFiles(File directory, IOFileFilter fileFilter, IOFileFilter dirFilter),这个方法用于从指定的目录下过滤文件。通过fileFilter我们可以指定要过滤的文件类型,通过dirFilter我们可以指定是否对子目录进行同样的过滤。如果为null则子目录不参与过滤。

iterateFiles(File directory, String[] extensions, boolean recursive),这个方法用于从指定的目录下过滤文件。通过extensions我们可以指定要过滤的文件扩展名,通过recurisve我们可以指定是否对子目录进行同样的过滤。如果为false则子目录不参与过滤。

 ※ 基于Collection形式的过滤
listFiles(File directory, IOFileFilter fileFilter, IOFileFilter dirFilter),这个方法和基于Iterator形式的过滤相同,只是返回的值是一个集合。

listFiles(File directory, String[] extensions, boolean recursive)
,这个方法和基于Iterator形式的过滤相同,只是返回的值是一个集合。

⑥资源的转换


 从URL形式到File形式的转换
File toFile(URL url),这个转换会首先将file://这个Prefix去掉,然后使用normalize方法对路径进行规范。

 ※ 从File形式到URL形式的转换
URL[] toURLs(File[] files),这个转换或对文件的路径加上file://这个prefix,然后对路径进行规范。


-------------------------------------------------------------
生活就像打牌,不是要抓一手好牌,而是要尽力打好一手烂牌。
posted on 2010-03-08 15:07 Paul Lin 阅读(6527) 评论(1)  编辑  收藏 所属分类: J2SE


FeedBack:
# re: 【Java基础专题】IO与文件读写---使用Apache commons IO简化文件读写
2015-09-24 10:29 | 阿斯兰
不错  回复  更多评论
  

只有注册用户登录后才能发表评论。


网站导航:
 
<2010年3月>
28123456
78910111213
14151617181920
21222324252627
28293031123
45678910

常用链接

留言簿(21)

随笔分类

随笔档案

BlogJava热点博客

好友博客

搜索

  •  

最新评论

阅读排行榜

评论排行榜