Java IO流讀寫檔案的幾個注意點

來源:互聯網
上載者:User

平時寫IO相關代碼機會挺少的,但卻都知道使用BufferedXXXX來讀寫效率高,沒想到裡面還有這麼多陷阱,這兩天突然被其中一個陷阱折騰一下:讀一個檔案,然後寫到另外一個檔案,前後兩個檔案居然不一樣?

     解決這個問題之後,總結了幾個注意點。

注意點一:Reader/Writer讀寫二進位檔案是有問題的

[java]
view plaincopyprint?
  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.         } 

public void copyFile1() {<br />File srcFile = new File("E://atest//atest.txt");<br />File dstFile = new File("E://btest//btest.txt");<br />BufferedReader in = null;<br />BufferedWriter out = null;<br />try {<br />in = new BufferedReader(new FileReader(srcFile));<br />out = new BufferedWriter(new FileWriter(dstFile));</p><p>String line = null;<br />while((line = in.readLine()) != null) {<br />out.write(line+"/r/n");<br />}<br />}catch (Exception e) {<br />// TODO: handle exception<br />e.printStackTrace();<br />}finally {<br />if(in != null) {<br />try {<br />in.close();<br />}catch (Exception e) {<br />// TODO: handle exception<br />e.printStackTrace();<br />}<br />}</p><p>if(out != null) {<br />try {<br />out.close();<br />}catch (Exception e) {<br />// TODO: handle exception<br />e.printStackTrace();<br />}<br />}<br />}

上面代碼使用BufferedReader一行一行地讀取一個檔案,然後使用BufferedWriter把讀取到的資料寫到另外一個檔案中。如果檔案是ASCCII形式的,則內容還是能夠正確讀取的。但如果檔案是二進位的,則讀寫後的檔案與讀寫前是有很大區別的。當然,把上面的readLine()換成read(char[])仍然不能正確讀寫二進位檔案的。讀寫二進位檔案請接著看下面注意點。

注意點二:read(byte[] b, int offset, int length)中的offset不是指全檔案的全文,而是位元組數組b的位移量

現在已經知道使用Reader/Writer不能正確讀取二進位檔案,這是因為Reader/Writer是字元流,那就改用位元組流ufferedInputStream/BufferedOutputStream,網上搜尋到的例子大概是這樣的:

[c-sharp]
view plaincopyprint?
  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.     } 

public void copyFile() {<br />File srcFile = new File("E://atest//atest.gif");<br />File dstFile = new File("E://atest//btest.gif");<br />BufferedInputStream in = null;<br />BufferedOutputStream out = null;<br />try {<br />in = new BufferedInputStream(new FileInputStream(srcFile));<br />out = new BufferedOutputStream(new FileOutputStream(dstFile));</p><p>byte[] b = new byte[1024];<br />while(in.read(b) != -1) {<br />out.write(b);<br />}<br />}catch (Exception e) {<br />// TODO: handle exception<br />e.printStackTrace();<br />}finally {<br />if(in != null) {<br />try {<br />in.close();<br />}catch (Exception e) {<br />// TODO: handle exception<br />e.printStackTrace();<br />}<br />}<br />if(out != null) {<br />try {<br />out.close();<br />}catch (Exception e) {<br />// TODO: handle exception<br />e.printStackTrace();<br />}<br />}<br />}<br />}

每次讀1024位元組,然後寫1024位元組。這看似挺正確的,但實際寫出來的檔案與原檔案是不同的。這樣就懷疑可能是讀寫沒有接上,因而把代碼改成下面的形式:

[java]
view plaincopyprint?
  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.             } 

byte[] b = new byte[1024];<br />int offset = 0;<br />int length = -1;<br />while((length = in.read(b, offset, 1024)) != -1) {<br />out.write(b, offset, length);<br />offset += length;<br />}

這是誤以為:先讀一段,寫一段,然後改變位移量,然後使用新的位移量再讀一段、寫一段,直到檔案讀寫完畢。但這是錯誤的,因為使用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這個數組上。由於二進位檔案使用比較工具時,只知道不同、但不能知道哪些不同(是否有更先進的比較工具?)。怎樣確定它的不同呢?方法很簡單:就把二進位檔案改成文字檔就能看出結果了(Reader/Writer這種字元流雖然不能正確讀寫二進位檔案,但InputStream/OutputStream這些位元組流能既能正確讀寫二進位檔案,也能正確讀寫文字檔)。由於使用了每次讀1K(1024位元組)的方式,所以會看到的結果是:寫後的檔案後面多出一段,這一段的長度與原檔案大小以及b數組的大小有關。為了進一步確定是什麼關係,把讀的檔案內容改為"1234567890123",而把b數組的大小改為10位元組,這時結果就出來了:寫後的檔案內容變成"12345678901234567890",就是讀了兩遍。多出的內容的根源在這裡:b數組的大小是10位元組,而要讀的內容長度是13位元組,那就要讀兩次,第一次讀了前10位元組,此時b數組內的元素為前10個字元;再讀第二次時,由於可讀內容只有3個字元,那b數組的內容只有前3個字元被改變了,後面7個字元仍然保持上一次讀取的內容。所以直接採用write(b)的方式,在第二次寫檔案時,內容就多寫了一段不是第二次讀取到的內容。

下面是正確的讀寫(即每次讀了多少內容,寫入的是多少內容,而不是寫入整個數組):

[java]
view plaincopyprint?
  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.     } 

public void copyFile() {<br />File srcFile = new File("E://atest//atest.txt");<br />File dstFile = new File("E://btest//btest.txt");<br />BufferedInputStream in = null;<br />BufferedOutputStream out = null;<br />try {<br />in = new BufferedInputStream(new FileInputStream(srcFile));<br />out = new BufferedOutputStream(new FileOutputStream(dstFile));</p><p>int len = -1;<br />byte[] b = new byte[10];<br />while((len = in.read(b)) != -1) {<br />out.write(b, 0, len);<br />}<br />}catch (Exception e) {<br />// TODO: handle exception<br />e.printStackTrace();<br />}finally {<br />if(in != null) {<br />try {<br />in.close();<br />}catch (Exception e) {<br />// TODO: handle exception<br />e.printStackTrace();<br />}<br />}<br />if(out != null) {<br />try {<br />out.close();<br />}catch (Exception e) {<br />// TODO: handle exception<br />e.printStackTrace();<br />}<br />}<br />}<br />}

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

flush()是把寫緩衝區內的內容全部”吐“到檔案上,如果沒有它,就有可能很多內容還存在於寫緩衝區內,而不是在檔案中,也就是還有丟失的可能。

close()中會調用flush()。它是檔案真正完成的標誌,檔案內容寫完成後不關閉檔案流,會導致一些”古怪“的問題。這個在網路中的流更能體現。

所以,寫檔案完成後注意關閉檔案讀寫流。

 

轉載路徑:http://blog.csdn.net/swingline/article/details/5656979

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.