zoukankan      html  css  js  c++  java
  • Java IO流读写文件的几个注意点

    
    平时写IO相关代码机会挺少的,但却都知道使用BufferedXXXX来读写效率高,没想到里面还有这么多陷阱,这两天突然被当中一个陷阱折腾一下:读一个文件,然后写到另外一个文件,前后两个文件竟然不一样?

         解决问题之后,总结了几个注意点。


    注意点一:Reader/Writer读写二进制文件是有问题的


    1. public void copyFile1() {  
    2.         File srcFile = new File("E://atest//atest.txt");  
    3.         File dstFile = new File("E://btest//btest.txt");  
    4.         BufferedReader in = null;  
    5.         BufferedWriter out = null;  
    6.         try {  
    7.             in = new BufferedReader(new FileReader(srcFile));  
    8.             out = new BufferedWriter(new FileWriter(dstFile));  
    9.               
    10.             String line = null;  
    11.             while((line = in.readLine()) != null) {  
    12.                 out.write(line+"/r/n");  
    13.             }  
    14.         }catch (Exception e) {  
    15.             // TODO: handle exception  
    16.             e.printStackTrace();  
    17.         }finally {  
    18.             if(in != null) {  
    19.                 try {  
    20.                     in.close();  
    21.                 }catch (Exception e) {  
    22.                     // TODO: handle exception  
    23.                     e.printStackTrace();  
    24.                 }  
    25.             }  
    26.               
    27.             if(out != null) {  
    28.                 try {  
    29.                     out.close();  
    30.                 }catch (Exception e) {  
    31.                     // TODO: handle exception  
    32.                     e.printStackTrace();  
    33.                 }  
    34.             }  
    35.         }  

    上面代码使用BufferedReader一行一行地读取一个文件。然后使用BufferedWriter把读取到的数据写到另外一个文件里。假设文件是ASCCII形式的,则内容还是可以正确读取的。但假设文件是二进制的。则读写后的文件与读写前是有非常大差别的。当然,把上面的readLine()换成read(char[])仍然不能正确读写二进制文件的。读写二进制文件请接着看以下注意点。

    注意点二:read(byte[] b, int offset, int length)中的offset不是指全文件的全文,而是字节数组b的偏移量

    如今已经知道使用Reader/Writer不能正确读取二进制文件,这是由于Reader/Writer是字符流。那就改用字节流ufferedInputStream/BufferedOutputStream,网上搜索到的样例大概是这种:

    1. public void copyFile() {  
    2.         File srcFile = new File("E://atest//atest.gif");  
    3.         File dstFile = new File("E://atest//btest.gif");  
    4.         BufferedInputStream in = null;  
    5.         BufferedOutputStream out = null;          
    6.         try {  
    7.             in = new BufferedInputStream(new FileInputStream(srcFile));  
    8.             out = new BufferedOutputStream(new FileOutputStream(dstFile));  
    9.               
    10.             byte[] b = new byte[1024];  
    11.             while(in.read(b) != -1) {  
    12.                 out.write(b);  
    13.             }  
    14.         }catch (Exception e) {  
    15.             // TODO: handle exception  
    16.             e.printStackTrace();  
    17.         }finally {  
    18.             if(in != null) {  
    19.                 try {  
    20.                     in.close();  
    21.                 }catch (Exception e) {  
    22.                     // TODO: handle exception  
    23.                     e.printStackTrace();  
    24.                 }  
    25.             }  
    26.             if(out != null) {  
    27.                 try {  
    28.                     out.close();  
    29.                 }catch (Exception e) {  
    30.                     // TODO: handle exception  
    31.                     e.printStackTrace();  
    32.                 }  
    33.             }  
    34.         }  
    35.     }  

    每次读1024字节。然后写1024字节。这看似挺正确的,但实际写出来的文件与原文件是不同的。这样就怀疑可能是读写没有接上,因而把代码改成以下的形式:

    1. byte[] b = new byte[1024];  
    2.             int offset = 0;  
    3.             int length = -1;  
    4.             while((length = in.read(b, offset, 1024)) != -1) {  
    5.                 out.write(b, offset, length);  
    6.                 offset += length;  
    7.             }  

    这是误以为:先读一段。写一段,然后改变偏移量,然后使用新的偏移量再读一段、写一段。直到文件读写完成。但这是错误的,由于使用BufferedXXX后,里面已经实现了这个过程。而read(byte[] b, int offset, int length)中的offset实际指的是把读到的数据存入到数组b时,从数组的哪个位置(即offset)開始放置数据;同理,write(byte[] b, int offset, int length)就是把b中的数据,从哪个位置(offset)開始写到文件里。

    注意点三:使用 length=read (b, 0, 1024)读数据时,应该使用write(b, 0, length)来写

    第二个注意点中的第一段代码的做法尽管在网上比較常见。可是有问题的。问题在哪呢?答案是:问题在byte[] b这个数组上。

    因为二进制文件使用比較工具时,仅仅知道不同、但不能知道哪些不同(是否有更先进的比較工具?)。

    如何确定它的不同呢?方法非常easy:就把二进制文件改成文本文件就能看出结果了(Reader/Writer这样的字符流尽管不能正确读写二进制文件,但InputStream/OutputStream这些字节流能既能正确读写二进制文件,也能正确读写文本文件)。因为使用了每次读1K(1024字节)的方式。所以会看到的结果是:写后的文件后面多出一段,这一段的长度与原文件大小以及b数组的大小有关。为了进一步确定是什么关系,把读的文件内容改为"1234567890123",而把b数组的大小改为10字节。这时结果就出来了:写后的文件内容变成"12345678901234567890",就是读了两遍。多出的内容的根源在这里:b数组的大小是10字节。而要读的内容长度是13字节。那就要读两次,第一次读了前10字节,此时b数组内的元素为前10个字符;再读第二次时,因为可读内容仅仅有3个字符,那b数组的内容仅仅有前3个字符被改变了,后面7个字符仍然保持上一次读取的内容。所以直接採用write(b)的方式,在第二次写文件时。内容就多写了一段不是第二次读取到的内容。

    以下是正确的读写(即每次读了多少内容。写入的是多少内容,而不是写入整个数组):

    1. public void copyFile() {  
    2.         File srcFile = new File("E://atest//atest.txt");  
    3.         File dstFile = new File("E://btest//btest.txt");  
    4.         BufferedInputStream in = null;  
    5.         BufferedOutputStream out = null;  
    6.         try {  
    7.             in = new BufferedInputStream(new FileInputStream(srcFile));  
    8.             out = new BufferedOutputStream(new FileOutputStream(dstFile));  
    9.               
    10.             int len = -1;  
    11.             byte[] b = new byte[10];  
    12.             while((len = in.read(b)) != -1) {  
    13.                 out.write(b, 0, len);  
    14.             }  
    15.         }catch (Exception e) {  
    16.             // TODO: handle exception  
    17.             e.printStackTrace();  
    18.         }finally {  
    19.             if(in != null) {  
    20.                 try {  
    21.                     in.close();  
    22.                 }catch (Exception e) {  
    23.                     // TODO: handle exception  
    24.                     e.printStackTrace();  
    25.                 }  
    26.             }  
    27.             if(out != null) {  
    28.                 try {  
    29.                     out.close();  
    30.                 }catch (Exception e) {  
    31.                     // TODO: handle exception  
    32.                     e.printStackTrace();  
    33.                 }  
    34.             }  
    35.         }  
    36.     }  

    注意点四:flush()和close()

    flush()是把写缓冲区内的内容所有”吐“到文件上,假设没有它。就有可能非常多内容还存在于写缓冲区内,而不是在文件里,也就是还有丢失的可能。

    close()中会调用flush()。它是文件真正完毕的标志。文件内容写完毕后不关闭文件流,会导致一些”古怪“的问题。这个在网络中的流更能体现。

    所以,写文件完毕后注意关闭文件读写流。



  • 相关阅读:
    控制TABLE的一些属性
    windows7中各个dll文件作用和用途
    left join on and与left join on where的区别
    号外,left join比连表语句快80倍
    JS跳转语句
    [jQuery,Dojo,MooTools]
    CYQ.Data 数据框架 V3.5 开源版本发布(源码提供下载)
    Scala开发小小总结
    性能测试工具原理与架构
    joomla1.5模块开发和插件开发详解带实例
  • 原文地址:https://www.cnblogs.com/gavanwanggw/p/6970500.html
Copyright © 2011-2022 走看看