剛學Java的IO流部分時,書上說只能使用字節流去讀取圖片、視頻等非文本二進位文件,不能使用字符流,否則文件會損壞。所以我就一直記住這一點了,但是為什麼不能使用,這一直是我的一個疑惑。今天,我又想到了這個問題,所以乾脆就一鼓作氣把它解決了吧。
先來看一個關於圖片複製的程式碼範例: 注意:我的電腦是存在 D:/DB這個路徑的,如果你沒有,DB這個資料夾,必須建立一個。
package dragon; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.InputStreamReader; import java.nio.file.Path; import java.nio.file.Paths; public class ReadImage { public static void main(String[] args) throws IOException { String imgPath = "D:/DB/husky/kkk.jpeg"; String byteImgCopyPath = "D:/DB/husky/byteCopykkk.jpeg"; String charImgCopyPath = "D:/DB/husky/charCopykkk.jpeg"; Path srcPath = Paths.get(imgPath); Path desPath2 = Paths.get(byteImgCopyPath); Path desPath3 = Paths.get(charImgCopyPath); byteRead(srcPath.toFile(), desPath2.toFile()); System.out.println("字节复制执行成功!"); characterRead(srcPath.toFile(), desPath3.toFile()); System.out.println("字符复制执行成功!"); } static void byteRead(File src, File des) throws IOException { try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(src)); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(des))) { int hasRead = 0; byte[] b = new byte[1024]; while ((hasRead = bis.read(b)) != -1) { bos.write(b, 0, hasRead); } } } static void characterRead(File src, File des) throws IOException { try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(src), "UTF-8")); BufferedWriter writer = new BufferedWriter(new FileWriter(des))) { int hasRead = 0; char[] c = new char[1024]; while ((hasRead = reader.read(c)) != -1) { writer.write(c, 0, hasRead); } } } }
運行結果: 可見,使用字元流確實無法讀取圖片這樣的二進位文件,必須使用位元組流。
圖片大小變化: 可見,使用字元流後圖片大小變化了,使用位元組流則不會。
透過上面那個例子,我們可以看到確實是無法使用字元流複製文件,並且使用字元流複製文件後,文件的大小也會變化,這就引出我們今天要討論的標題了。
我們先來想一想,為什麼文字檔打開可以顯示文字? 我們都知道電腦處理的文件無論是文字或非文字的文件,最終在電腦內部都是以二進位的形式儲存的。
使用文字編輯器的16進位模式開啟一個文字檔:
#使用編輯器的16進制模式開啟上面程式使用的圖片檔:
比較兩張圖片中的數據,應該發現不了什麼差別吧,但是為什麼文字資料就可以顯示出文字呢?這是一個非常基礎的問題,大學裡面的基礎課都是講過這方面的內容–字符編碼表。我最開始學習的是C 語言,接觸最早的編碼表是ASCII(美國資訊交換標準代碼),後來學習java接觸的是Unicode(萬國碼,這個名字和它的起源很契合。我們目前最常使用的是UTF-8,是針對Unicode的一種可變長度字元編碼。)
注意: 使用UTF-8 也是分為含有BOM(Byte Order Mark,字節順序標記) 和沒有的兩種形式,而且混用會導致錯誤,感興趣的可以去了解一下。
字元編碼表的作用體現在編碼上,引述百科的一段話:
在顯示器上看見的文字、圖片等資訊在電腦裡面其實不是我們看見的樣子,即使你知道所有資訊都儲存在硬碟裡,把它拆開也看不見裡面有任何東西,只有些碟片。假設,你用顯微鏡把盤片放大,會看見盤片表面凹凸不平,凸起的地方被磁化,凹的地方是沒有被磁化;凸起的地方代表數字1,凹的地方代表數字0。硬碟只能用0和1來表示所有文字、圖片等資訊。那麼字母”A”在硬碟上是如何儲存的呢?可能小張電腦儲存字母」A」是1100001,而小王儲存字母」A」是11000010,這樣雙方交換資訊時就會誤解。例如小張把1100001發送給小王,小王不認為1100001是字母”A”,可能認為這是字母”X”,於是小王在用記事本訪問存儲在硬碟上的1100001時,在屏幕上顯示的就是字母”X”。也就是說,小張和小王使用了不同的編碼表。
所以字元編碼表就是二進位數字和字元之間的一一映射,例如65 (數字)代表A,所以下面這段程式碼會在螢幕上輸出A。
char c = 65; System.out.println(c);
我們使用一個循環來測試一下:
char c = 0; for (int i = 9999; i < 10009; i++) { c = (char) i; System.out.print(c+" "); }
測試結果:(當然了,這個取決於你的當前的字元編碼表,如果使用ASCII,估計就有意思了。)
这样就解释了前面那个问题(为什么文本文件打开可以显示文字?),我们之所以可以看见文本文件的字符是因为计算机按照我们文件的编码(ASCII、UTF-8或者GBK等),从字符编码表中找出来对应的字符。 所以,当我们使用记事本打开二进制文件会看到乱码,这就是原因。文件的复制过程也是复制的二进制数据,而不是真实的文字。
因此可以这样理解文件复制的过程:
字符流:二进制数据 --编码-> 字符编码表 --解码-> 二进制数据
字节流:二进制数据 —> 二进制数据
所以问题就是出现在编码和解码的过程中,既然是字符的编码表,那它就是包含所有的字符,但是字符的数量是有限的,这就意味着它不能表示一些超过编码表的字符,因为根本不存在表中。所以,JVM 会使用一些字符进行替换,基本上都是乱码(所以大小会发生变化),而且如果有一个数据恰好是-1,那么读取就会中断,引起数据丢失。
例如如下代码使用字符流读取就会错误:
String filename = "D:/DB/fos.txt"; //文件名 byte[] b = new byte[] {-1, -1}; //两个字节,127的二进制就是 1111 1111 //数据写入文件 try (FileOutputStream fos = new FileOutputStream(filename)) { fos.write(b, 0, b.length); //将两个127连续写入,就是 1111 1111 1111 1111 } File file = new File(filename); //输出文件的大小 System.out.println("file length: " + file.length()); char[] c = new char[2]; //使用字符流读取文件 try (FileReader reader = new FileReader(filename)) { int count = reader.read(c); //Java使用Unicode编码,读取的是从 0-65535 之间的数字。 System.out.println("以文本形式输出:" + new String(c, 0, count)+" "+count); for (char d : c) { System.out.println("字符为:" + d); } } System.out.println("表示字符:" + c[0]); //再写入文件 try (FileWriter writer = new FileWriter(filename)) { writer.write(c, 0, 2); } File f = new File(filename); System.out.println("file length: " + f.length());
结果:
说明: 我将两个1字节的-1写入(字节流)了文本文件(注意是字节:-1,不是字符:-1),然后再读取(字符流),再写入(字符流)就已经出现了问题。读取出的字符显示了一个奇怪的符号,而且它的值为:65533,这个值如果用字节表示的话,一个字节是不够的,所以文件的大小就会变化。在非文本的二进制数据中,出现这种情况都是正常的,因为本来就不是按照字符编码的。
因为字符都是正数,而非字符编码的话,字节数可能是负数(很可能),但是负数在字符看来就是正数,这也是为什么-1,被读成 65533的原因。可以看出来,读取就已经错误了。
注意: 这里的重点是对于使用字符流读取非文本文件,在读取-写入的过程中的问题。
以上是Java不能使用字元流讀取非文字二進位檔案的原因是什麼的詳細內容。更多資訊請關注PHP中文網其他相關文章!