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()。它是文件真正完畢的標志。文件內容寫完畢后不關閉文件流,會導致一些”古怪“的問題。這個在網絡中的流更能體現。

所以,寫文件完畢后注意關閉文件讀寫流。




免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM