File對象能夠表示存在的文件或文件夾,也能夠表示不存在的。java
咱們想要獲得文件的內容怎麼辦,File只是操做文件,文件的內容如何處理就須要使用io流技術了。linux
例如在C盤下有一個名稱爲a.txt的文本文件.想要經過Java程序讀出來文件中的內容,須要使用IO流技術.一樣想要將程序中的數據,保存到硬盤的文件中,也須要IO流技術.windows
讀和寫文件文件示例:設計模式
public class IoTest {api public static void main(String[] args) throws FileNotFoundException,數組 IOException {網絡 writFileTest();app
readFileTest();jvm }編輯器
private static void writFileTest() throws FileNotFoundException, IOException { // 建立文件對象 File file = new File("c:\\a.txt"); // 建立文件輸出流 FileOutputStream fos = new FileOutputStream(file); fos.write('g'); fos.write('z'); fos.write('i'); fos.write('t'); fos.write('c'); fos.write('a'); fos.write('s'); fos.write('t'); fos.close(); }
private static void readFileTest() throws FileNotFoundException, IOException { // 建立文件對象 File file = new File("c:\\a.txt"); // 建立文件輸入流 FileInputStream fis = new FileInputStream(file); // 有對多長,就讀多少字節。 for (int i = 0; i < file.length(); i++) { System.out.print((char) fis.read()); } fis.close(); } }
|
當完成流的讀寫時,應該經過調用close方法來關閉它,這個方法會釋放掉十分有限的操做系統資源.若是一個應用程序打開了過多的流而沒有關閉它們,那麼系統資源將被耗盡.
IO流簡介:(Input/Output)
I/O類庫中使用「流」這個抽象概念。Java對設備中數據的操做是經過流的方式。
表示任何有能力產出數據的數據源對象,或者是有能力接受數據的接收端對象。「流」屏蔽了實際的I/O設備中處理數據的細節。IO流用來處理設備之間的數據傳輸。設備是指硬盤、內存、鍵盤錄入、網絡等。
Java用於操做流的對象都在IO包中。IO流技術主要用來處理設備之間的數據傳輸。
因爲Java用於操做流的對象都在IO包中。因此使用IO流須要導包如:import java.io.*;
IO流的分類
流按操做數據類型的不一樣分爲兩種:字節流與字符流。
流按流向分爲:輸入流,輸出流(以程序爲參照物,輸入到程序,或是從程序輸出)
什麼是字節流
計算機中都是二進制數據,一個字節是8個2進制位.字節能夠表示全部的數據,好比文本,音頻,視頻.圖片,都是做爲字節存在的.也就是說字節流處理的數據很是多。
在文本文件中存儲的數據是以咱們能讀懂的方式表示的。而在二進制文件中存儲的數據是用二進制形式表示的。咱們是讀不懂二進制文件的,由於二進制文件是爲了讓程序來讀取而設計的。例如,Java的源程序(.java源文件)存儲在文本文件中,可使用文本編輯器閱讀,可是Java的類(字節碼文件)存儲在二進制文件中,能夠被Java虛擬機閱讀。二進制文件的優點在於它的處理效率比文本文件高。
咱們已經知道File對象封裝的是文件或者路徑屬性,可是不包含向(從)文件讀(寫)數據的方法。爲了實現對文件的讀和寫操做須要學會正確的使用Java的IO建立對象。
字節流的抽象基類:
輸入流:java.io.InputStream
輸出流:java.io.OutputStream
特色:
字節流的抽象基類派生出來的子類名稱都是以其父類名做爲子類名的後綴。
如:FileInputStream, ByteArrayInputStream等。
說明:
字節流處理的單元是一個字節,用於操做二進制文件(計算機中全部文件都是二進制文件)
案例:讀取"c:/a.txt"文件中的全部內容並在控制檯顯示出來。
注意:事先準備一個a.txt並放到c:/下,不要保存中文。
a, 使用read()方法實現。
b, 使用int read(byte[] b)方法實現。
寫代碼讀取"c:/a.txt"文件中的全部的內容並在控制檯顯示出來
實現:
查看api文檔(本身必定要動手)
InputStream 有read方法,一次讀取一個字節,OutputStream的write方法一次寫一個int。發現這兩個類都是抽象類。意味着不能建立對象,那麼須要找到具體的子類來使用。
經過查看api文檔,找到了FileInputStream類,該類正是咱們體驗Io流的一個輸入流。
實現;顯示指定文件內容。 明確使用流,使用哪一類流?使用輸入流,FileInputStream 第一步: 1:打開流(即建立流) 第二步: 2:經過流讀取內容 第三步: 3:用完後,關閉流資源 |
顯然流是Java中的一類對象,要打開流其實就是建立具體流的對象,因爲是讀取硬盤上的文件,應該使用輸入流。因此找到了InputStream類,可是InputStream是抽象類,須要使用它的具體實現類來建立對象就是FileInputStream。經過new 調用FileInputStream 的構造方法來建立對象。發現FileInputStream的構造方法須要指定文件的來源。查看構造方法,能夠接受字符串也能夠接受File對象。咱們經過構建File對象指定文件路徑。
使用流就像使用水管同樣,要打開就要關閉。因此打開流和關閉流的動做是比不可少的。如何關閉流?使用close方法便可,當完成流的讀寫時,應該經過調用close方法來關閉它,這個方法會釋放掉十分有限的操做系統資源.若是一個應用程序打開了過多的流而沒有關閉它們,那麼系統資源將被耗盡.
如何經過流讀取內容?
查找api文檔經過read方法,查看該方法,發現有返回值,而且是int類型的,該方法一次讀取一個字節(byte)
read方法()
一次讀取一個字節,讀到文件末尾返回-1.
仔細查看api文檔發現read方法若是讀到文件的末尾會返回-1。那麼就能夠經過read方法的返回值是不是-1來控制咱們的循環讀取。
/** * 根據read方法返回值的特性,若是獨到文件的末尾返回-1,若是不爲-1就繼續向下讀。 * */ private static void showContent(String path) throws IOException { // 打開流 FileInputStream fis = new FileInputStream(path);
int len = fis.read(); while (len != -1) { System.out.print((char)len); len = fis.read();
} // 使用完關閉流 fis.close(); } |
咱們習慣這樣寫:
/** * 根據read方法返回值的特性,若是獨到文件的末尾返回-1,若是不爲-1就繼續向下讀。 * */ private static void showContent(String path) throws IOException { // 打開流 FileInputStream fis = new FileInputStream(path);
int len; while ((len = fis.read()) != -1) { System.out.print((char) len); } // 使用完關閉流 fis.close(); } |
使用read(byte[] b) 方法。使用緩衝區(關鍵是緩衝區大小的肯定)
使用read方法的時候,流須要讀一次就處理一次,能夠將讀到的數據裝入到字節數組中,一次性的操做數組,能夠提升效率。
問題1:緩衝區大小
那麼字節數組如何定義?定義多大?
能夠嘗試初始化長度爲5的byte數組。經過read方法,往byte數組中存內容
那麼該read方法返回的是往數組中存了多少字節。
/** * 使用字節數組存儲讀到的數據 * */ private static void showContent2(String path) throws IOException { // 打開流 FileInputStream fis = new FileInputStream(path);
// 經過流讀取內容 byte[] byt = new byte[5]; int len = fis.read(byt); for (int i = 0; i < byt.length; i++) { System.out.print((char) byt[i]); }
// 使用完關閉流 fis.close(); } |
問題1: 緩衝區過小:
數據讀取不完.
測試發現問題,因爲數組過小,只裝了5個字節。而文本的字節大於數組的長度。那麼很顯然能夠將數組的長度定義大一些。例如1024個。
/** * 使用字節數組存儲讀到的數據 * */ private static void showContent2(String path) throws IOException { // 打開流 FileInputStream fis = new FileInputStream(path);
// 經過流讀取內容 byte[] byt = new byte[1024]; int len = fis.read(byt); for (int i = 0; i < byt.length; i++) { System.out.print(byt[i]); }
// 使用完關閉流 fis.close(); } |
問題三:緩衝區有默認值.
測試,打印的效果打印出了不少0,由於數組數組有默認初始化值,因此,咱們將數組的數據所有都遍歷和出來.如今須要的是取出數組中的部分數據.須要將循環條件修改仔細查看api文檔。發現該方法read(byte[] b)返回的是往數組中存入了多少個字節。就是數組實際存儲的數據個數。
/** * 使用字節數組存儲讀到的數據 * */ private static void showContent2(String path) throws IOException { // 打開流 FileInputStream fis = new FileInputStream(path);
// 經過流讀取內容 byte[] byt = new byte[1024]; int len = fis.read(byt); for (int i = 0; i <len; i++) { System.out.print(byt[i]); }
// 使用完關閉流 fis.close(); } |
總結:
問題一:爲何打印的不是字母而是數字,
是字母對應的碼值。
如何顯示字符,強轉爲char便可
問題二:注意:回車和換行的問題。
windows的換車和換行是"\r\n" 對應碼錶是13和10 。
使用read(byte[] b,int off,int len)
查看api文檔,
b顯然是一個byte類型數組,當作容器來使用
off,是指定從數組的什麼位置開始存字節
len,但願讀多少個
其實就是把數組的一部分當作流的容器來使用。告訴容器,從什麼地方開始裝要裝多少。
/** * 把數組的一部分當作流的容器來使用 * read(byte[] b,int off,int len) */ private static void showContent3(String path) throws IOException { // 打開流 FileInputStream fis = new FileInputStream(path);
// 經過流讀取內容 byte[] byt = new byte[1024]; // 從什麼地方開始存讀到的數據 int start = 5;
// 但願最多讀多少個(若是是流的末尾,流中沒有足夠數據) int maxLen = 6;
// 實際存放了多少個 int len = fis.read(byt, start, maxLen);
for (int i = start; i < start + maxLen; i++) { System.out.print((char) byt[i]); }
// 使用完關閉流 fis.close(); } |
需求2:測試skip方法
經過Io流,讀取"c:/a.txt"文件中的第9個字節到最後全部的內容並在控制檯顯示出來。
分析:其實就是要跳過文件中的一部分字節,須要查找API文檔。可使用skip方法skip(long n),參數跟的是要跳過的字節數。
咱們要從第9個開始讀,那麼要跳過前8個便可。
/** * skip方法 * * */ private static void showContent4(String path) throws IOException { // 打開流 FileInputStream fis = new FileInputStream(path);
// 經過流讀取內容 byte[] byt = new byte[1024]; fis.skip(8); int len = fis.read(byt); System.out.println(len); System.out.println("**********"); for (int i = 0; i < len; i++) { System.out.println((char) byt[i]); } // 使用完關閉流 fis.close(); } |
使用緩衝(提升效率),並循環讀取(讀完全部內容).
總結:讀完文件的全部內容。很顯然可使用普通的read方法,一次讀一個字節直到讀到文件末尾。爲了提升效率可使用read(byte[] byt);方法就是所謂的使用緩衝提升效率。咱們能夠讀取大文本數據測試(大於1K的文本文件.)
/** * 使用字節數組當緩衝 * */ private static void showContent5(String path) throws IOException { FileInputStream fis = new FileInputStream(path); byte[] byt = new byte[1024]; int len = fis.read(byt); System.out.println(len); String buffer = new String(byt, 0, len); System.out.print(buffer); } |
注意:如何將字節數組轉成字符串? 能夠經過建立字符串對象便可。
發現:一旦數據超過1024個字節,數組就存儲不下。
如何將文件的剩餘內容讀完?
咱們能夠經過經過循環保證文件讀取完。
/** * 使用字節數組當緩衝 * */ private static void showContent7(String path) throws IOException { FileInputStream fis = new FileInputStream(path); byte[] byt = new byte[1024]; int len = 0; while ((len = fis.read(byt)) != -1) { System.out.println(new String(byt, 0, len)); } } |
字節輸出流
案例:
1,寫代碼實現把"Hello World!"寫到"c:/a.txt"文件中。
a, c:/a.txt不存在時,測試一下。
b, c:/a.txt存在時,也測試一下。
要寫兩個版本:
a, 使用write(int b) 實現。
b, 使用write(byte[] b) 實現。
2,在已存在的c:/a.txt文本文件中追加內容:「Java IO」。
顯然此時須要向指定文件中寫入數據。
使用的就是能夠操做文件的字節流對象。OutputStream。該類是抽象類,須要使用具體的實現類來建立對象查看API文檔,找到了OutputStream的實現類FileOutputStream 建立FileOutputStream 流對象,必須指定數據要存放的目的地。經過構造函數的形式。建立流對象時,調用了系統底層的資源。在指定位置創建了數據存放的目的文件。
流程: 1:打開文件輸出流,流的目的地是指定的文件 2:經過流向文件寫數據 3: 用完流後關閉流 |
使用write(int b)方法,一次寫出一個字節.
在C盤下建立a.txt文本文件
import java.io.FileOutputStream; import java.io.IOException;
public class IoTest2 { public static void main(String[] args) throws IOException { String path = "c:\\a.txt"; writeTxtFile(path); }
private static void writeTxtFile(String path) throws IOException { // 1:打開文件輸出流,流的目的地是指定的文件 FileOutputStream fos = new FileOutputStream(path);
// 2:經過流向文件寫數據 fos.write('j'); fos.write('a'); fos.write('v'); fos.write('a'); // 3:用完流後關閉流 fos.close();
} }
|
當c盤下的a.txt不存在會怎麼樣?
測試:將c盤下的a.txt文件刪除,發現當文件不存在時,會自動建立一個,可是建立不了多級目錄。
注意:使用write(int b)方法,雖然接收的是int類型參數,可是write 的常規協定是:向輸出流寫入一個字節。要寫入的字節是參數 b 的八個低位。b 的 24 個高位將被忽略。
使用write(byte[] b),就是使用緩衝.提升效率.
上述案例中的使用了OutputStram 的write方法,一次只能寫一個字節。成功的向文件中寫入了內容。可是並不高效,如和提升效率呢?是否應該使用緩衝,根據字節輸入流的緩衝原理,是否能夠將數據保存中字節數組中。經過操做字節數組來提升效率。查找API文檔,在OutputStram類中找到了write(byte[] b)方法,將 b.length 個字節從指定的 byte 數組寫入此輸出流中。
如何將字節數據保存在字節數組中,以字符串爲例,」hello , world」 如何轉爲字節數組。顯然經過字符串的getBytes方法便可。
public class IoTest2 { public static void main(String[] args) throws IOException { String path = "c:\\a.txt"; writeTxtFile(path); }
private static void writeTxtFile(String path) throws IOException { // 1:打開文件輸出流,流的目的地是指定的文件 FileOutputStream fos = new FileOutputStream(path);
// 2:經過流向文件寫數據 byte[] byt = "java".getBytes(); fos.write(byt); // 3:用完流後關閉流 fos.close(); } }
|
仔細查看a.txt文本文件發現上述程序每運行一次,老的內容就會被覆蓋掉。,那麼如何不覆蓋已有信息,可以往a.txt裏追加信息呢。查看API文檔,發現FileOutputStream類中的構造方法中有一個構造能夠實現追加的功能FileOutputStream(File file, boolean append) 第二個參數,append - 若是爲 true,則將字節寫入文件末尾處,而不是寫入文件開始處
private static void writeTxtFile(String path) throws IOException { // 1:打開文件輸出流,流的目的地是指定的文件 FileOutputStream fos = new FileOutputStream(path,true);
// 2:經過流向文件寫數據 byte[] byt = "java".getBytes(); fos.write(byt); // 3:用完流後關閉流 fos.close(); } |
經過字節輸出流向文件中寫入一些信息,並使用字節輸入流把文件中的信息顯示到控制檯上。
public class IoTest3 { public static void main(String[] args) throws IOException { String path = "c:\\b.txt"; String content = "hello java";
writeFile(path, content);
readFile(path); }
public static void writeFile(String path, String content) throws IOException { // 打開文件輸出流 FileOutputStream fos = new FileOutputStream(path); byte[] buffer = content.getBytes(); // 向文件中寫入內容 fos.write(buffer); // 關閉流 fos.close();
}
public static void readFile(String path) throws IOException { FileInputStream fis = new FileInputStream(path); byte[] byt = new byte[1024]; int len = 0; while ((len = fis.read(byt)) != -1) { System.out.println(new String(byt, 0, len)); } // 關閉流 fos.close();
} }
|
注意輸出流的細節:
這個輸出流顯然只適合小數據的寫入,若是有大數據想要寫入,咱們的byte數組,該如何定義?
上述案例中咱們將輸入流和輸出流進行和綜合使用,若是嘗試進輸出流換成文本文件就能夠實現文件的拷貝了.
什麼是文件拷貝?很顯然,先開一個輸入流,將文件加載到流中,再開一個輸出流,將流中數據寫到文件中。就實現了文件的拷貝。
分析: 第一步:須要打開輸入流和輸出流 第二步:讀取數據並寫出數據 第三步:關閉流 public class IoTest3 {
public static void main(String[] args) throws IOException {
String srcPath = "c:\\a.txt"; String destPath = "d:\\a.txt"; copyFile(srcPath, destPath); }
public static void copyFile(String srcPath, String destPath) throws IOException {
}
} |
讀一個字節寫一個字節read 和write
public class IoTest3 {
public static void main(String[] args) throws IOException {
String srcPath = "c:\\a.txt"; String destPath = "d:\\a.txt"; copyFile(srcPath, destPath); }
public static void copyFile(String srcPath, String destPath) throws IOException { // 打開輸入流,輸出流 FileInputStream fis = new FileInputStream(srcPath); FileOutputStream fos = new FileOutputStream(destPath);
// 讀取和寫入信息 int len = 0; while ((len = fis.read()) != -1) { fos.write(len); }
// 關閉流 fis.close(); fos.close(); }
} |
文本文件在計算機中是以二進制形式存在的,能夠經過io流來拷貝,那麼圖片能不能拷貝呢?視頻呢?音頻呢?
public class IoTest3 {
public static void main(String[] args) throws IOException {
String srcPath = "c:\\秋.jpg"; String destPath = "d:\\秋.jpg"; copyFile(srcPath, destPath); }
public static void copyFile(String srcPath, String destPath) throws IOException { // 打開輸入流,輸出流 FileInputStream fis = new FileInputStream(srcPath); FileOutputStream fos = new FileOutputStream(destPath);
// 讀取和寫入信息 int len = 0; while ((len = fis.read()) != -1) { fos.write(len); }
// 關閉流 fis.close(); fos.close(); }
} |
測試通通經過,因此字節流能夠操做全部的文件。只是發現程序很慢,須要很長時間。特別是拷貝音頻和視頻文件時。
爲何?由於每次讀一個字節再寫一個字節效率很低。很顯然這樣效率低下的操做不是咱們想要的。有沒有更快更好的方法呢,是否可使用緩衝區來提升程序的效率呢。
使用字節數組做爲緩衝區
public static void copyFile2(String srcPath, String destPath) throws IOException { // 打開輸入流,輸出流 FileInputStream fis = new FileInputStream(srcPath); FileOutputStream fos = new FileOutputStream(destPath);
// 讀取和寫入信息 int len = 0;
// 使用字節數組,當作緩衝區 byte[] byt = new byte[1024]; while ((len = fis.read(byt)) != -1) { fos.write(byt); }
// 關閉流 fis.close(); fos.close(); }
|
問題1: 使用緩衝(字節數組)拷貝數據,拷貝後的文件大於源文件.
測試該方法,拷貝文本文件,仔細觀察發現和源文件不太一致。
打開文件發現拷貝後的文件和拷貝前的源文件不一樣,拷貝後的文件要比源文件多一些內容問題就在於咱們使用的容器,這個容器咱們是重複使用的,新的數據會覆蓋掉老的數據,顯然最後一次讀文件的時候,容器並無裝滿,出現了新老數據並存的狀況。
因此最後一次把容器中數據寫入到文件中就出現了問題。
如何避免?使用FileOutputStream 的write(byte[] b, int off, int len)
b 是容器,off是從數組的什麼位置開始,len是獲取的個數,容器用了多少就寫出多少。
public static void copyFile2(String srcPath, String destPath) throws IOException { // 打開輸入流,輸出流 FileInputStream fis = new FileInputStream(srcPath); FileOutputStream fos = new FileOutputStream(destPath);
// 讀取和寫入信息 int len = 0;
// 使用字節數組,當作緩衝區 byte[] byt = new byte[1024]; while ((len = fis.read(byt)) != -1) { fos.write(byt, 0, len); }
// 關閉流 fis.close(); fos.close(); }
|
使用緩衝拷貝視頻,能夠根據拷貝的需求調整數組的大小,通常是1024的整數倍。發現使用緩衝後效率大大提升。
上述案例中全部的異常都只是進行了拋出處理,這樣是不合理的。因此上述代碼並不完善,由於異常沒有處理。
當咱們打開流,讀和寫,關閉流的時候都會出現異常,異常出現後,後面的代碼都不會執行了。假設打開和關閉流出現了異常,那麼顯然close方法就不會再執行。那麼會對程序有什麼影響?
案例:
public class IoTest4 { public static void main(String[] args) throws IOException, InterruptedException { String path = "c:\\b.txt"; readFile(path); }
private static void readFile(String path) throws IOException, InterruptedException { FileInputStream fis = new FileInputStream(path); byte[] byt = new byte[1024]; int len = fis.read(byt); System.out.println(new String(byt, 0, len)); // 讓程序睡眠,沒法執行到close方法。 Thread.sleep(1000 * 10); fis.close(); } } |
在執行該程序的同時咱們嘗試去刪除b.txt文件。若是在該程序沒有睡醒的話,咱們是沒法刪除b.txt 文件的。由於b.txt還被該程序佔用着,這是很嚴重的問題,因此必定要關閉流。
目前咱們是拋出處理,一旦出現了異常,close就沒有執行,也就沒有釋放資源。那麼爲了保證close的執行該如何處理呢。
那麼就須要使用try{} catch(){}finally{}語句。try中放入可能出現異常的語句,catch是捕獲異常對象,fianlly是必定要執行的代碼
public class IoTest4 { public static void main(String[] args) throws IOException, InterruptedException { String path = "c:\\b.txt"; readFile(path); }
private static void readFile(String path) { FileInputStream fis = null; try { fis = new FileInputStream(path); byte[] byt = new byte[1024]; int len = fis.read(byt); System.out.println(new String(byt, 0, len)); } catch (IOException e) { // 拋出運行時異常 throw new RuntimeException(e); } finally { // 把close方法放入finally中保證必定會執行 // 先判斷是否空指針 if (fis != null) { try { fis.close(); } catch (Exception e) { throw new RuntimeException(e); }
}
}
} } |
文件拷貝的異常處理:
public static void copyFile(String srcPath, String destPath) {
FileInputStream fis = null; FileOutputStream fos = null; try { fis = new FileInputStream(srcPath); fos = new FileOutputStream(destPath);
byte[] byt = new byte[1024 * 1024]; int len = 0; while ((len = fis.read(byt)) != -1) {
fos.write(byt, 0, len); } } catch (IOException e) { throw new RuntimeException(e); } finally { if (fis != null) { try { fis.close(); } catch (IOException e) { throw new RuntimeException(e); } } if (fos != null) { try { fos.close(); } catch (IOException e) { throw new RuntimeException(e); } }
}
} |
注意:
在最後的close代碼中可能會有問題,兩個close,若是第一個close方法出現了異常,並拋出了運行時異常,那麼程序仍是中止了。下面的close方法就沒有執行到。
那麼爲了保證close的執行,將第二個放到fianlly中便可。
public static void copyFile(String srcPath, String destPath) {
FileInputStream fis = null; FileOutputStream fos = null; try { fis = new FileInputStream(srcPath); fos = new FileOutputStream(destPath);
byte[] byt = new byte[1024 * 1024]; int len = 0; while ((len = fis.read(byt)) != -1) {
fos.write(byt, 0, len); } } catch (IOException e) { throw new RuntimeException(e); } finally {
try { if (fis != null) { fis.close(); } } catch (IOException e) { throw new RuntimeException(e); } finally { if (fos != null) { try { fos.close(); } catch (IOException e) { throw new RuntimeException(e); } }
} }
} |
上述程序中咱們爲了提升流的使用效率,自定義了字節數組,做爲緩衝區.Java其實提供了專門的字節流緩衝來提升效率.
BufferedInputStream和BufferedOutputStream
BufferedOutputStream和BufferedOutputStream類能夠經過減小讀寫次數來提升輸入和輸出的速度。它們內部有一個緩衝區,用來提升處理效率。查看API文檔,發現能夠指定緩衝區的大小。其實內部也是封裝了字節數組。沒有指定緩衝區大小,默認的字節是8192。
顯然緩衝區輸入流和緩衝區輸出流要配合使用。首先緩衝區輸入流會將讀取到的數據讀入緩衝區,當緩衝區滿時,或者調用flush方法,緩衝輸出流會將數據寫出。
注意:固然使用緩衝流來進行提升效率時,對於小文件可能看不到性能的提高。可是文件稍微大一些的話,就能夠看到實質的性能提高了。
public class IoTest5 { public static void main(String[] args) throws IOException { String srcPath = "c:\\a.mp3"; String destPath = "d:\\copy.mp3"; copyFile(srcPath, destPath); }
public static void copyFile(String srcPath, String destPath) throws IOException { // 打開輸入流,輸出流 FileInputStream fis = new FileInputStream(srcPath); FileOutputStream fos = new FileOutputStream(destPath);
// 使用緩衝流 BufferedInputStream bis = new BufferedInputStream(fis); BufferedOutputStream bos = new BufferedOutputStream(fos);
// 讀取和寫入信息 int len = 0;
while ((len = bis.read()) != -1) { bos.write(len); }
// 關閉流 bis.close(); bos.close(); }
}
|
計算機並不區分二進制文件與文本文件。全部的文件都是以二進制形式來存儲的,所以,從本質上說,全部的文件都是二進制文件。因此字符流是創建在字節流之上的,它可以提供字符層次的編碼和解碼。例如,在寫入一個字符時,Java虛擬機會將字符轉爲文件指定的編碼(默認是系統默認編碼),在讀取字符時,再將文件指定的編碼轉化爲字符。
常見的碼錶以下:
ASCII: 美國標準信息交換碼。用一個字節的7位能夠表示。
ISO8859-1: 拉丁碼錶。歐洲碼錶,用一個字節的8位表示。又稱Latin-1(拉丁編碼)或「西歐語言」。ASCII碼是包含的僅僅是英文字母,而且沒有徹底佔滿256個編碼位置,因此它以ASCII爲基礎,在空置的0xA0-0xFF的範圍內,加入192個字母及符號,
藉以供使用變音符號的拉丁字母語言使用。從而支持德文,法文等。於是它依然是一個單字節編碼,只是比ASCII更全面。
GB2312: 英文佔一個字節,中文佔兩個字節.中國的中文編碼表。
GBK: 中國的中文編碼表升級,融合了更多的中文文字符號。
Unicode: 國際標準碼規範,融合了多種文字。全部文字都用兩個字節來表示,Java語言使用的就是unicode。
UTF-8: 最多用三個字節來表示一個字符。
(咱們之後接觸最多的是iso8859-1、gbk、utf-8)
查看上述碼錶後,很顯然中文的‘中’在iso8859-1中是沒有對映的編碼的。或者一個字符在2中碼錶中對應的編碼不一樣,例若有一些字在不一樣的編碼中是有交集的,例如bjg5 和gbk 中的漢字簡體和繁體多是同樣的,就是有交集,可是在各自碼錶中的數字不同。
例如
使用gbk 將中文保存在計算機中,
中 國
對映 100 200 若是使用big5 打開
可能 ? ...
不一樣的編碼對映的是不同的。
很顯然,咱們使用什麼樣的編碼寫數據,就須要使用什麼樣的編碼來對數據。
ISO8859-1:一個字節
GBK: 兩個字節包含了英文字符和擴展的中文 ISO8859-1+中文字符
UTF-8 萬國碼,推行的。是1~3個字節不等長。英文存的是1個字節,中文存的是3個字節,是爲了節省空間。
那麼咱們以前學習的流稱之爲字節流,以字節爲單位進行操做之情的操做全是英文,若是想要操做中文呢?
測試:將指定位置的文件經過字節流讀取到控制檯
public class TestIo { public static void main(String[] args) throws IOException { String path = "c:\\a.txt"; writFileTest(); readFileByInputStream(path); }
private static void readFileByInputStream(String path) throws IOException { FileInputStream fis = new FileInputStream(path);
int len = 0; while ((len = fis.read()) != -1) { System.out.print((char) len); } }
private static void writFileTest() throws FileNotFoundException, IOException { // 建立文件對象 File file = new File("c:\\a.txt"); // 建立文件輸出流 FileOutputStream fos = new FileOutputStream(file); fos.write("中國".getBytes()); fos.close(); } }
|
發現控制檯輸出的信息:
???ú 是這樣的東西,打開a.txt 文本發現漢字」中國」確實寫入成功。
那麼說明使用字節流處理中文有問題。
仔細分析,咱們的FileInputStream輸入流的read() 一次是讀一個字節的,返回的是一個int顯然進行了自動類型提高。那麼咱們來驗證一下「中國」對應的字節是什麼
使用:"中國".getBytes() 便可獲得字符串對應的字節數組。是[-42, -48, -71, -6]
一樣,將read方法返回值直接強轉爲byte ,發現結果也是-42, -48, -71, -6 。
代碼:
public class TestIo { public static void main(String[] args) throws IOException { String path = "c:\\a.txt"; writFileTest(); readFileByInputStream(path); //查看中國對應的編碼 System.out.println(Arrays.toString("中國".getBytes())); }
private static void readFileByInputStream(String path) throws IOException { FileInputStream fis = new FileInputStream(path); int len = 0; while ((len = fis.read()) != -1) { System.out.println((byte)len); } }
private static void writFileTest() throws FileNotFoundException, IOException { // 建立文件對象 File file = new File("c:\\a.txt"); // 建立文件輸出流 FileOutputStream fos = new FileOutputStream(file); fos.write("中國\r\n".getBytes()); fos.close(); }
}
|
那麼中國 對應的是-42, -48, -71, -6是4個字節。 那就是一箇中文佔2個字節,(這個和編碼是有關係的)
很顯然,咱們的中文就不可以再一個字節一個字節的讀了。因此字節流處理字符信息時並不方便那麼就出現了字符流。
字節流是 字符流是以字符爲單位。
體驗字符流:
public static void main(String[] args) throws IOException {
String path = "c:\\a.txt"; readFileByReader(path); } private static void readFileByReader(String path) throws IOException { FileReader fr = new FileReader(path); int len = 0; while ((len = fr.read()) != -1) { System.out.print((char) len); } } |
總結:字符流就是:字節流 + 編碼表,爲了更便於操做文字數據。字符流的抽象基類:
Reader , Writer。
由這些類派生出來的子類名稱都是以其父類名做爲子類名的後綴,如FileReader、FileWriter。
方法:
1,int read():
讀取一個字符。返回的是讀到的那個字符。若是讀到流的末尾,返回-1.
2,int read(char[]):
將讀到的字符存入指定的數組中,返回的是讀到的字符個數,也就是往數組裏裝的元素的個數。若是讀到流的末尾,返回-1.
3,close()
讀取字符其實用的是window系統的功能,就但願使用完畢後,進行資源的釋放
因爲Reader也是抽象類,因此想要使用字符輸入流須要使用Reader的實現類。查看API文檔。找到了FileReader。
1,用於讀取文本文件的流對象。
2,用於關聯文本文件。
構造函數:在讀取流對象初始化的時候,必需要指定一個被讀取的文件。
若是該文件不存在會發生FileNotFoundException.
public class IoTest1_Reader {
public static void main(String[] args) throws Exception { String path = "c:/a.txt"; // readFileByInputStream(path); readFileByReader(path); }
/** * 使用字節流讀取文件內容 * * @param path */ public static void readFileByInputStream(String path) throws Exception { InputStream in = new FileInputStream(path);
int len = 0; while ((len = in.read()) != -1) { System.out.print((char) len); }
in.close(); }
/** * 使用字符流讀取文件內容 */ public static void readFileByReader(String path) throws Exception { Reader reader = new FileReader(path); int len = 0; while ((len = reader.read()) != -1) { System.out.print((char) len); }
reader.close(); }
} |
Writer中的常見的方法:
1,write(ch): 將一個字符寫入到流中。
2,write(char[]): 將一個字符數組寫入到流中。
3,write(String): 將一個字符串寫入到流中。
4,flush():刷新流,將流中的數據刷新到目的地中,流還存在。
5,close():關閉資源:在關閉前會先調用flush(),刷新流中的數據去目的地。然流關閉。
發現基本方法和OutputStream 相似,有write方法,功能更多一些。能夠接收字符串。
一樣道理Writer是抽象類沒法建立對象。查閱API文檔,找到了Writer的子類FileWriter
1:將文本數據存儲到一個文件中。
public class IoTest2_Writer {
public static void main(String[] args) throws Exception { String path = "c:/ab.txt";
writeToFile(path); }
/** * 寫指定數據到指定文件中 * */ public static void writeToFile(String path) throws Exception { Writer writer = new FileWriter(path); writer.write('中'); writer.write("世界".toCharArray()); writer.write("中國");
writer.close(); } } |
2:追加文件:
默認的FileWriter方法新值會覆蓋舊值,想要實現追加功能須要
使用以下構造函數建立輸出流 append值爲true便可。
FileWriter(String fileName, boolean append)
FileWriter(File file, boolean append)
3:flush方法
若是使用字符輸出流,沒有調用close方法,會發生什麼?
private static void writeFileByWriter(File file) throws IOException { FileWriter fw = new FileWriter(file); fw.write('新'); fw.flush(); fw.write("中國".toCharArray()); fw.write("世界你好!!!".toCharArray()); fw.write("明天"); // 關閉流資源 //fw.close(); } |
程序執行完畢打開文件,發現沒有內容寫入.原來須要使用flush方法. 刷新該流的緩衝。
爲何只要指定claose方法就不用再flush方法,由於close也調用了flush方法.
一個文本文件中有中文有英文字母,有數字。想要把這個文件拷貝到別的目錄中。
咱們可使用字節流進行拷貝,使用字符流呢?確定也是能夠的。
public static void main(String[] args) throws Exception { String path1 = "c:/a.txt"; String path2 = "c:/b.txt";
copyFile(path1, path2); }
/** * 使用字符流拷貝文件 */ public static void copyFile(String path1, String path2) throws Exception { Reader reader = new FileReader(path1); Writer writer = new FileWriter(path2);
int ch = -1; while ((ch = reader.read()) != -1) { writer.write(ch); }
reader.close(); writer.close(); } |
可是這個一次讀一個字符就寫一個字符,效率不高。把讀到的字符放到字符數組中,再一次性的寫出。
public static void main(String[] args) throws Exception { String path1 = "c:/a.txt"; String path2 = "c:/b.txt";
copyFile(path1, path2); }
public static void copyFile3(String path1, String path2) throws Exception { Reader reader = new FileReader(path1); Writer writer = new FileWriter(path2);
int ch = -1; char [] arr=new char[1024]; while ((ch = reader.read(arr)) != -1) { writer.write(arr,0,ch); }
reader.close(); writer.close(); } |
字節流能夠拷貝視頻和音頻等文件,那麼字符流能夠拷貝這些嗎?
通過驗證拷貝圖片是不行的。發現丟失了信息,爲何呢?
計算機中的全部信息都是以二進制形式進行的存儲(1010)圖片中的也都是二進制
在讀取文件的時候字符流自動對這些二進制按照碼錶進行了編碼處理,可是圖片原本就是二進制文件,不須要進行編碼。有一些巧合在碼錶中有對應,就能夠處理,並非全部的二進制均可以找到對應的。信息就會丟失。因此字符流只能拷貝以字符爲單位的文本文件
(以ASCII碼爲例是127個,並非全部的二進制均可以找到對應的ASCII,有些對不上的,就會丟失信息。)
public static void main(String[] args) throws Exception { String path1 = "c:/a.txt"; String path2 = "c:/b.txt";
copyFile2(path1, path2); }
/** * 使用字符流拷貝文件,有完善的異常處理 */ public static void copyFile2(String path1, String path2) { Reader reader = null; Writer writer = null; try { // 打開流 reader = new FileReader(path1); writer = new FileWriter(path2);
// 進行拷貝 int ch = -1; while ((ch = reader.read()) != -1) { writer.write(ch); } } catch (Exception e) { throw new RuntimeException(e); } finally { // 關閉流,注意必定要能執行到close()方法,因此都要放到finally代碼塊中 try { if (reader != null) { reader.close(); } } catch (Exception e) { throw new RuntimeException(e); } finally { try { if (writer != null) { writer.close(); } } catch (Exception e) { throw new RuntimeException(e); } } } } |
查看Reader 發現Reader,操做的是字符,咱們就不須要進行編碼解碼操做,由字符流讀到二進制,自動進行解碼獲得字符,寫入字符自動編碼成二進制.
Reader有一個子類BufferedReader。子類繼承父類顯然子類能夠重寫父類的方法,也能夠增長本身的新方法。例如一次讀一行就是經常使用的操做.那麼BufferedReader 類就提供了這個方法,能夠查看readLine()方法具有 一次讀取一個文本行的功能。很顯然,該子類能夠對功能進行加強。
體驗BufferedReader
public class IoTest_BufferedReader { public static void main(String[] args) throws IOException { readFile("c:\\a.txt"); }
private static void readFile(String path) throws IOException { Reader read = new FileReader(path);
BufferedReader br = new BufferedReader(read);
String line = null; while ((line = br.readLine()) != null) { System.out.println(line); }
} }
|
注意:
在使用緩衝區對象時,要明確,緩衝的存在是爲了加強流的功能而存在,因此在創建緩衝區對象時,要先有流對象存在.
緩衝區的出現提升了對流的操做效率。原理:其實就是將數組進行封裝。
使用字符流緩衝區拷貝文本文件.
public class Demo7 { public static void main(String[] args) throws IOException { // 關聯源文件 File srcFile = new File("c:\\linux大綱.txt"); // 關聯目標文件 File destFile = new File("d:\\linux大綱.txt"); // 實現拷貝 copyFile(srcFile, destFile);
}
private static void copyFile(File srcFile, File destFile) throws IOException { // 建立字符輸入流 FileReader fr = new FileReader(srcFile); // 建立字符輸出流 FileWriter fw = new FileWriter(destFile);
// 字符輸入流的緩衝流 BufferedReader br = new BufferedReader(fr); // 字符輸出流的緩衝流 BufferedWriter bw = new BufferedWriter(fw);
String line = null; // 一次讀取一行 while ((line = br.readLine()) != null) { // 一次寫出一行. bw.write(line); // 刷新緩衝 bw.flush(); // 進行換行,因爲readLine方法默認沒有換行.須要手動換行 bw.newLine(); } // 關閉流 br.close(); bw.close(); } }
|
需求:想要在讀取的文件的每一行添加行號。
public class IoTest7_BufferedReader {
public static void main(String[] args) throws IOException { readFile("c:\\a.txt"); }
private static void readFile(String path) throws IOException { Reader read = new FileReader(path);
BufferedReader br = new BufferedReader(read); int count = 0; String line = null; while ((line = br.readLine()) != null) { count++; System.out.println(count+":"+line); }
} }
|
很容易的就能夠實現。若是每次使用BufferedReader 輸出時都須要顯示行號呢? 每次都加? 很顯然,咱們的BufferedReader繼承了Reader 對父類進行了功能的加強,那麼咱們也能夠繼承BufferedReader 重寫該類的readLine方法,進行功能的加強.
public class IoTest_BufferedReader { public static void main(String[] args) throws IOException { readFile("c:\\a.txt"); }
private static void readFile(String path) throws IOException { Reader read = new FileReader(path);
BufferedReader br = new MyBufferedReader(read); String line = null; while ((line = br.readLine()) != null) { System.out.println(line); }
} }
class MyBufferedReader extends BufferedReader { public MyBufferedReader(Reader read) { super(read); }
int count;
@Override public String readLine() throws IOException { String line = super.readLine(); if (line != null) { count++; return count + ":" + line;
} else { return null; }
} } |
需求:
要在輸出的一行前加上引號
能夠再定義一個BufferedReader的子類,繼承BufferedReader加強功能.
public class IoTest_BufferedReader { public static void main(String[] args) throws IOException { readFile("c:\\a.txt"); }
private static void readFile(String path) throws IOException { Reader read = new FileReader(path); BufferedReader br = new MyQutoBufferedReader(read); int count = 0; String line = null; while ((line = br.readLine()) != null) { System.out.println(line); count++; }
} }
// quotation 引號 class MyQutoBufferedReader extends BufferedReader {
public MyQutoBufferedReader(Reader reader) { super(reader); }
public String readLine() throws IOException { String line = super.readLine(); if (line != null) {
return "\"" + line + "\"";
} else { return null; }
} } |
需求三:
既想要顯示行號又想要顯示引號
發現,就須要再定義子類,發現這樣比較麻煩,代碼臃腫.並且代碼重複.
能夠換一種方式.以下:
其實就是一個新類要對原有類進行功能加強.
1. 在加強類中維護一個被加強的父類引用變量
2. 在加強類的構造函數中初始化1中的變量
3. 建立須要加強的方法,在剛方法中調用被被加強類的方法,並加以加強。
public class IoTest_BufferedReader { public static void main(String[] args) throws IOException { readFile("c:\\a.txt"); }
private static void readFile(String path) throws IOException { Reader read = new FileReader(path); BufferedReader bufferedReader = new BufferedReader(read); BufferedReader br = new MyQutoBufferedReader2(bufferedReader); br = new MyLineBufferedReader2(br); String line = null; while ((line = br.readLine()) != null) { System.out.println(line); } } }
// quotation 引號 class MyQutoBufferedReader2 extends BufferedReader { private BufferedReader bufferedReader;
public MyQutoBufferedReader2(BufferedReader bufferedReader) { super(bufferedReader); this.bufferedReader = bufferedReader; }
public String readLine() throws IOException { String line = super.readLine(); if (line != null) {
return "\"" + line + "\"";
} else { return null; }
} }
class MyLineBufferedReader2 extends BufferedReader { private BufferedReader bufferedReader;
public MyLineBufferedReader2(BufferedReader bufferedReader) { super(bufferedReader); this.bufferedReader = bufferedReader; }
int count;
@Override public String readLine() throws IOException { String line = super.readLine(); if (line != null) { count++; return count + ":" + line;
} else { return null; }
} } |
這就是裝飾器模式
裝飾器模式:
使用分層對象來動態透明的向單個對象中添加責任(功能)。
裝飾器指定包裝在最初的對象周圍的全部對象都具備相同的基本接口。
某些對象是可裝飾的,能夠經過將其餘類包裝在這個可裝飾對象的四周,來將功能分層。
裝飾器必須具備和他所裝飾的對象相同的接口。
JavaIO中的應用:
Java I/O類庫須要多種不一樣的功能組合,因此使用了裝飾器模式。
FilterXxx類是JavaIO提供的裝飾器基類,即咱們要想實現一個新的裝飾器,就要繼承這些類。
裝飾器與繼承:
問題:
修飾模式作的加強功能按照繼承的特色也是能夠實現的,爲何還要提出修飾設計模式呢?
繼承實現的加強類和修飾模式實現的加強類有何區別?
繼承實現的加強類:
優勢:代碼結構清晰,並且實現簡單
缺點:對於每個的須要加強的類都要建立具體的子類來幫助其加強,這樣會致使繼承體系過於龐大。
修飾模式實現的加強類:
優勢:內部能夠經過多態技術對多個須要加強的類進行加強
缺點:須要內部經過多態技術維護須要加強的類的實例。進而使得代碼稍微複雜。
也稱爲合併流。
序列流,對多個流進行合併。
SequenceInputStream 表示其餘輸入流的邏輯串聯。它從輸入流的有序集合開始,並從第一個輸入流開始讀取,直到到達文件末尾,接着從第二個輸入流讀取,依次類推,直到到達包含的最後一個輸入流的文件末尾爲止。
注意:
構造函數 SequenceInputStream(InputStream s1, InputStream s2) SequenceInputStream(InputStream s1, InputStream s2) |
合併兩個流
使用構造函數SequenceInputStream(InputStream s1, InputStream s2)
private static void testSequenceInputStream() throws IOException { FileInputStream fis1 = new FileInputStream("c:\\a.txt"); FileInputStream fis2 = new FileInputStream("c:\\b.txt");
SequenceInputStream s1 = new SequenceInputStream(fis1, fis2); int len = 0; byte[] byt = new byte[1024];
FileOutputStream fos = new FileOutputStream("c:\\z.txt");
while ((len = s1.read(byt)) != -1) { fos.write(byt, 0, len); } s1.close(); } |
合併多個流:
public static void testSequenceInputStream() throws Exception { InputStream in1 = new FileInputStream("c:/a.txt"); InputStream in2 = new FileInputStream("c:/b.txt"); InputStream in3 = new FileInputStream("c:/c.txt");
LinkedHashSet<InputStream> set = new LinkedHashSet<InputStream>(); set.add(in1); set.add(in2); set.add(in3); final Iterator<InputStream> iter = set.iterator();
SequenceInputStream sin = new SequenceInputStream( new Enumeration<InputStream>() { @Override public boolean hasMoreElements() { return iter.hasNext(); }
@Override public InputStream nextElement() { return iter.next(); } });
FileOutputStream out = new FileOutputStream("c:/z.txt");
for (int b = -1; (b = sin.read()) != -1;) { out.write(b); } sin.close(); out.close(); } |
案例:將map3歌曲文件進行切割拷貝,併合並.
public class Demo2 { public static void main(String[] args) throws IOException {
split(new File("c:\\a.mp3"), 10, new File("c:\\")); System.out.println("切割完畢");
LinkedHashSet<InputStream> hs = new LinkedHashSet<InputStream>(); hs.add(new FileInputStream(new File("c:\\part.1.mp3"))); hs.add(new FileInputStream(new File("c:\\part.2.mp3"))); hs.add(new FileInputStream(new File("c:\\part.3.mp3"))); hs.add(new FileInputStream(new File("c:\\part.4.mp3"))); merage(hs, new File("c:\\merage.mp3")); System.out.println("合併完畢"); }
private static void merage(LinkedHashSet<InputStream> hs, File dest) throws IOException {
final Iterator<InputStream> it = hs.iterator(); FileOutputStream fos = new FileOutputStream(dest); SequenceInputStream seq = new SequenceInputStream( new Enumeration<InputStream>() {
@Override public boolean hasMoreElements() {
return it.hasNext(); }
@Override public InputStream nextElement() { return it.next(); } }); byte[] byt = new byte[1024 * 1024]; int len = 0; while ((len = seq.read(byt)) != -1) { fos.write(byt, 0, len); } seq.close(); fos.close(); }
// 1. 切割文件 /* * 切割文件,切割份數, 切割後保存路徑 */ private static void split(File src, int count, File dir) throws IOException { FileInputStream fis = new FileInputStream(src); FileOutputStream fos = null; byte[] byt = new byte[1024 * 1024]; int len = 0; for (int i = 1; i <= count; i++) { len = fis.read(byt); if (len != -1) { fos = new FileOutputStream(dir + "part." + i + ".mp3"); fos.write(byt, 0, len); }
// fos.close();
} fis.close();
} } |
當建立對象時,程序運行時它就會存在,可是程序中止時,對象也就消失了.可是若是但願對象在程序不運行的狀況下仍能存在並保存其信息,將會很是有用,對象將被重建而且擁有與程序上次運行時擁有的信息相同。可使用對象的序列化。
對象的序列化: 將內存中的對象直接寫入到文件設備中
對象的反序列化: 將文件設備中持久化的數據轉換爲內存對象
基本的序列化由兩個方法產生:一個方法用於序列化對象並將它們寫入一個流,另外一個方法用於讀取流並反序列化對象。
ObjectOutput writeObject(Object obj) 將對象寫入底層存儲或流。 ObjectInput readObject() 讀取並返回對象。 |
因爲上述ObjectOutput和ObjectInput是接口,因此須要使用具體實現類。
ObjectOutput ObjectOutputStream被寫入的對象必須實現一個接口:Serializable 不然會拋出:NotSerializableException ObjectInput ObjectInputStream 該方法拋出異常:ClassNotFountException |
ObjectOutputStream和ObjectInputStream 對象分別須要字節輸出流和字節輸入流對象來構建對象。也就是這兩個流對象須要操做已有對象將對象進行本地持久化存儲。
案例:
序列化和反序列化Cat對象。
public class Demo3 { public static void main(String[] args) throws IOException, ClassNotFoundException { Cat cat = new Cat("tom", 3); FileOutputStream fos = new FileOutputStream(new File("c:\\Cat.txt")); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(cat); System.out.println(cat); oos.close(); // 反序列化 FileInputStream fis = new FileInputStream(new File("c:\\Cat.txt")); ObjectInputStream ois = new ObjectInputStream(fis); Object readObject = ois.readObject(); Cat cat2 = (Cat) readObject; System.out.println(cat2); fis.close(); }
class Cat implements Serializable { public String name; public int age;
public Cat() {
}
public Cat(String name, int age) {
this.name = name; this.age = age; }
@Override public String toString() { return "Cat [name=" + name + ", age=" + age + "]"; }
} |
例子關鍵點:
類經過實現 java.io.Serializable 接口以啓用其序列化功能。未實現此接口的類將沒法使其任何狀態序列化或反序列化。可序列化類的全部子類型自己都是可序列化的。序列化接口沒有方法或字段,僅用於標識可序列化的語義。
因此須要被序列化的類必須是實現Serializable接口,該接口中沒有描述任何的屬性和方法,稱之爲標記接口。
若是對象沒有實現接口Serializable,在進行序列化時會拋出:NotSerializableException 異常。
注意:
保存一個對象的真正含義是什麼?若是對象的實例變量都是基本數據類型,那麼就很是簡單。可是若是實例變量是包含對象的引用,會怎麼樣?保存的會是什麼?很顯然在Java中保存引用變量的實際值沒有任何意義,由於Java引用的值是經過JVM的單一實例的上下文中才有意義。經過序列化後,嘗試在JVM的另外一個實例中恢復對象,是沒有用處的。
以下:
首先創建一個Dog對象,也創建了一個Collar對象。Dog中包含了一個Collar(項圈)
如今想要保存Dog對象,可是Dog中有一個Collar,意味着保存Dog時也應該保存Collar。假如Collar也包含了其餘對象的引用,那麼會發生什麼?意味着保存一個Dog對象須要清楚的知道Dog對象的內部結構。會是一件很麻煩的事情。
Java的序列化機制能夠解決該類問題,當序列化一個對象時,Java的序列化機制會負責保存對象的全部關聯的對象(就是對象圖),反序列化時,也會恢復全部的相關內容。本例中:若是序列化Dog會自動序列化Collar。可是,只有實現了Serializable接口的類才能夠序列化。若是隻是Dog實現了該接口,而Collar沒有實現該接口。會發生什麼?
Dog類和Collar類
import java.io.Serializable;
public class Dog implements Serializable { private Collar collar; private String name;
public Dog(Collar collar, String name) {
this.collar = collar; this.name = name; }
public Collar getCollar() { return collar; }
}
class Collar { private int size;
public int getSize() { return size; }
public Collar(int size) { this.size = size; }
} |
序列化
import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream;
public class Demo4 { public static void main(String[] args) throws IOException { Collar coll = new Collar(10); Dog dog = new Dog(coll, "旺財");
FileOutputStream fis = new FileOutputStream(new File("c:\\dog.txt")); ObjectOutputStream os = new ObjectOutputStream(fis); os.writeObject(dog); } }
|
執行程序,出現了運行時異常。
Exception in thread "main" java.io.NotSerializableException: Collar |
因此咱們也必須將Dog中使用的Collar序列化。可是若是咱們沒法訪問Collar的源代碼,或者沒法使Collar可序列化,如何處理?
兩種解決方法:
一:繼承Collar類,使子類可序列化
可是:若是Collar是final類,就沒法繼承了。而且,若是Collar引用了其餘非序列化對象,也沒法解決該問題。
transient
此時就可使用transient修飾符,能夠將Dog類中的成員變量標識爲transient
那麼在序列化Dog對象時,序列化就會跳過Collar。
public class Demo4 { public static void main(String[] args) throws IOException, ClassNotFoundException { Collar coll = new Collar(10); Dog dog = new Dog(coll, "旺財"); System.out.println(dog.getCollar().getSize());
FileOutputStream fis = new FileOutputStream(new File("c:\\dog.txt")); ObjectOutputStream os = new ObjectOutputStream(fis); os.writeObject(dog);
// 反序列化 FileInputStream fos = new FileInputStream(new File("c:\\dog.txt")); ObjectInputStream ois = new ObjectInputStream(fos); Object readObject = ois.readObject(); Dog dog2 = (Dog) readObject; // Collar未序列化。 dog2.getCollar().getSize(); } }
|
這樣咱們具備一個序列化的Dog和非序列化的Collar。
此時反序列化Dog後,訪問Collar,就會出現運行時異常
10 Exception in thread "main" java.lang.NullPointerException |
注意:序列化不適用於靜態變量,由於靜態變量並不屬於對象的實例變量的一部分。靜態變量隨着類的加載而加載,是類變量。因爲序列化只適用於對象。
基本數據類型能夠被序列化
public class Demo5 { public static void main(String[] args) throws IOException { // 建立序列化流對象 FileOutputStream fis = new FileOutputStream(new File("c:\\basic.txt")); ObjectOutputStream os = new ObjectOutputStream(fis); // 序列化基本數據類型 os.writeDouble(3.14); os.writeBoolean(true); os.writeInt(100); os.writeInt(200);
// 關閉流 os.close();
// 反序列化 FileInputStream fos = new FileInputStream(new File("c:\\basic.txt")); ObjectInputStream ois = new ObjectInputStream(fos);
System.out.println(ois.readDouble()); System.out.println(ois.readBoolean()); System.out.println(ois.readInt()); System.out.println(ois.readInt());
fos.close(); } } |
serialVersionUID
用於給類指定一個UID。該UID是經過類中的可序列化成員的數字簽名運算出來的一個long型的值。
只要是這些成員沒有變化,那麼該值每次運算都同樣。
該值用於判斷被序列化的對象和類文件是否兼容。
若是被序列化的對象須要被不一樣的類版本所兼容。能夠在類中自定義UID。
定義方式:static final long serialVersionUID = 42L;
能夠和流相關聯的集合對象Properties.
Map
|--Hashtable
|--Properties
Properties:該集合不須要泛型,由於該集合中的鍵值對都是String類型。
1,存入鍵值對:setProperty(key,value);
2,獲取指定鍵對應的值:value getProperty(key);
3,獲取集合中全部鍵元素:
Enumeration propertyNames();
在jdk1.6版本給該類提供一個新的方法。
Set<String> stringPropertyNames();
4,列出該集合中的全部鍵值對,能夠經過參數打印流指定列出到的目的地。
list(PrintStream);
list(PrintWriter);
例:list(System.out):將集合中的鍵值對打印到控制檯。
list(new PrintStream("prop.txt")):將集合中的鍵值對存儲到prop.txt文件中。
5,能夠將流中的規則數據加載進行集合,並稱爲鍵值對。
load(InputStream):
jdk1.6版本。提供了新的方法。
load(Reader):
注意:流中的數據要是"鍵=值" 的規則數據。
6,能夠將集合中的數據進行指定目的的存儲。
store(OutputStram,String comment)方法。
jdk1.6版本。提供了新的方法。
store(Writer ,String comment):
使用該方法存儲時,會帶着當時存儲的時間。
注意:
Properties只加載key=value這樣的鍵值對,與文件名無關,註釋使用#
練習:記錄一個程序運行的次數,當知足指定次數時,該程序就不能夠再繼續運行了。
一般可用於軟件使用次數的限定。
public static void sysPropList() throws IOException { Properties prop = System.getProperties();
// prop.list(System.out);// 目的是控制檯。 // 需求是:將jvm的屬性信息存儲到一個文件中。 prop.list(new PrintStream("java.txt")); }
public static void sysProp() { Properties prop = System.getProperties();
Set<String> keys = prop.stringPropertyNames();
for (String key : keys) { System.out.println(key + ":" + prop.getProperty(key)); } } |
Properties類與配置文件
Map
|--Hashtable
|--Properties
注意:是一個Map集合,該集合中的鍵值對都是字符串。該集合一般用於對鍵值對形式的配置文件進行操做.
配置文件:將軟件中可變的部分數據能夠定義到一個文件中,方便之後更改,該文件稱之爲配置文件。
優點: 提升代碼的維護性。
Properties: 該類是一個Map的子類,提供了能夠快速操做配置文件的方法
load() : 將文件設備數據裝載爲Map集合數據
get(key): 獲取Map中的數據
getProperty()獲取Map中的數據特有方法
案例:
/* * 將配置文件中的數據經過流加載到集合中。 */ public static void loadFile() throws IOException { // 1,建立Properties(Map)對象 Properties prop = new Properties();
// 2.使用流加載配置文件。 FileInputStream fis = new FileInputStream("c:\\qq.txt");
// 3。使用Properties 對象的load方法將流中數據加載到集合中。 prop.load(fis);
// 遍歷該集合 Set<Entry<Object, Object>> entrySet = prop.entrySet(); Iterator<Entry<Object, Object>> it = entrySet.iterator(); while (it.hasNext()) { Entry<Object, Object> next = it.next(); Object key = next.getKey(); Object value = next.getValue(); } // 經過鍵獲取指定的值 Object object = prop.get("jack"); System.out.println(object);
// 經過鍵修改值 prop.setProperty("jack", "888888");
// 將集合中的數據寫入到配置文件中。 FileOutputStream fos = new FileOutputStream("c:\\qq.txt");
// 註釋: prop.store(fos, "yes,qq");
fos.close(); fis.close();
}
|
獲取記錄程序運行次數:
public class Demo6 { public static void main(String[] args) throws IOException { int count = 0; Properties pro = new Properties();
File file = new File("c:\\count.ini"); FileInputStream fis = null; if (!file.exists()) { file.createNewFile(); } fis = new FileInputStream(file); pro.load(fis); String str = pro.getProperty("count"); if (str != null) { count = Integer.parseInt(str); } if (count == 3) { System.out.println("使用次數已到,請付費"); System.exit(0); }
count++; System.out.println("歡迎使用本軟件" + "你已經使用了:" + count + " 次");
pro.setProperty("count", count + ""); FileOutputStream fos = new FileOutputStream(new File("c:\\count.ini")); pro.store(fos, "請保護知識產權");
fis.close(); fos.close();
} }
|
PrintStream能夠接受文件和其餘字節輸出流,因此打印流是對普通字節輸出流的加強,其中定義了不少的重載的print()和println(),方便輸出各類類型的數據。
PrintWriter
1,打印流。
PrintStream:
是一個字節打印流,System.out對應的類型就是PrintStream。
它的構造函數能夠接收三種數據類型的值。
1,字符串路徑。
2,File對象。
3,OutputStream。
public static void main(String[] args) throws IOException { PrintStream ps = System.out;
// 普通write方法須要調用flush或者close方法纔會在控制檯顯示 // ps.write(100); // ps.close();
// 不換行打印 ps.print(100); ps.print('a'); ps.print(100.5); ps.print("世界"); ps.print(new Object()); System.out.println("--------------"); // 換行 ps.println(100); ps.println('a'); ps.println(100.5); ps.println("世界"); ps.println(new Object());
// 重定向打印流 PrintStream ps2 = new PrintStream(new File("c:\\a.txt")); System.setOut(ps2); // 換行 ps2.println(100); ps2.println('a'); ps2.println(100.5); ps2.println("世界"); ps2.println(new Object());
// printf(); 格式化 ps2.printf("%d,%f,%c,%s", 100, 3.14, '中', "世界你好!!!"); ps2.printf("%4s和%8s 打價格戰", "京東", "蘇寧");
} } |
注意: 打印流的三種方法
void print(數據類型 變量)
println(數據類型 變量)
printf(String format, Object... args)
能夠自定數據格式
print 和println方法的區別在於,一個換行一個不換行
print 方法和write方法的卻別在於,print提供自動刷新.
普通的write方法須要調用flush或者close方法才能夠看到數據.
JDK1.5以後Java對PrintStream進行了擴展,增長了格式化輸出方式,可使用printf()重載方法直接格式化輸出。可是在格式化輸出的時候須要指定輸出的數據類型格式。
是一個字符打印流。構造函數能夠接收四種類型的值。
1,字符串路徑。
2,File對象。
對於1,2類型的數據,還能夠指定編碼表。也就是字符集。
3,OutputStream
4,Writer
對於3,4類型的數據,能夠指定自動刷新。
注意:該自動刷新值爲true時,只有三個方法能夠用:println,printf,format.
若是想要既有自動刷新,又可執行編碼。如何完成流對象的包裝?
PrintWrter pw =
new PrintWriter(new OutputSteamWriter(new FileOutputStream("a.txt"),"utf-8"),true);
若是想要提升效率。還要使用打印方法。
PrintWrter pw =
newPrintWriter(new BufferdWriter(new OutputSteamWriter(
newFileOutputStream("a.txt"),"utf-8")),true);
public static void testPrintWriter() throws Exception { PrintWriter pw = new PrintWriter("c:/b.txt", "gbk");
// pw.append("xxx"); // pw.println(55); // pw.println('c'); // pw.printf("%.1s與%4s打價格戰, %c", "京東","蘇寧", 'a');
pw.close();
} |
Scanner
public static void testScanner() throws Exception { // Scanner scanner = new Scanner(new File("c:/test.txt")); Scanner scanner = new Scanner(System.in);
System.out.println(scanner.nextInt()); System.out.println(scanner.nextBoolean());
scanner.close(); } |
ByteArrayInputStream
以及ByteArrayOutputStream
toByteArray();
toString();
writeTo(OutputStream);
public static void testByteArrayInputStream() throws Exception { InputStream in = new ByteArrayInputStream(new byte[] { 65, 66, 67 }); ByteArrayOutputStream out = new ByteArrayOutputStream();
for (int b = -1; (b = in.read()) != -1;) { out.write(b); }
in.close(); out.close();
System.out.println(Arrays.toString(out.toByteArray())); System.out.println(out); } |
CharArrayReader
CharArrayWriter
對於這些流,源是內存。目的也是內存。
並且這些流並未調用系統資源。使用的就是內存中的數組。
因此這些在使用的時候不須要close。
操做數組的讀取流在構造時,必需要明確一個數據源。因此要傳入相對應的數組。
對於操做數組的寫入流,在構造函數可使用空參數。由於它內置了一個可變長度數組做爲緩衝區。
public static void testCharArrayReader() throws Exception { CharArrayReader reader = new CharArrayReader(new char[] { 'A', 'b', 'c' }); CharArrayWriter writer = new CharArrayWriter();
for (int b = -1; (b = reader.read()) != -1;) { writer.write(b); }
reader.close(); writer.close();
System.out.println(writer.toCharArray()); } |
這幾個流的出現其實就是經過流的讀寫思想在操做數組。
相似的對象同理:
StringReader
StringWriter。
public static void testStringReader() throws Exception { StringReader reader = new StringReader("test 中國"); StringWriter writer = new StringWriter();
for (int b = -1; (b = reader.read()) != -1;) { writer.write(b); }
reader.close(); writer.close();
System.out.println(writer.toString()); } |
以及DataOutputStream
查看API文檔DataInputStream的信息。發現從底層輸入流中讀取基本 Java 數據類型。查看方法,有讀一個字節,讀一個char讀一個double 的方法,
DataInputStream 從數據流讀取字節,並將它們轉換爲正確的基本數據類型值或字符串。
該流有操做基本數據類型的方法.
有讀的,那麼一定有對應的寫的就是DataOutputStream 將基本類型的值或字符串轉換爲字節,而且將字節輸出到數據流。
DataInputStream類繼承FilterInputStream類,並實現了DataInput接口。DataOutputStream
類繼承FilterOutputStream 並實現了DataOutput 接口。
例如:
DataInputStream 操做基本數據類型的方法: int readInt():一次讀取四個字節,並將其轉成int值。 boolean readBoolean():一次讀取一個字節。 short readShort(); long readLong(); 剩下的數據類型同樣。 String readUTF():按照utf-8修改版讀取字符。注意,它只能讀writeUTF()寫入的字符數據。 DataOutputStream DataOutputStream(OutputStream): 操做基本數據類型的方法: writeInt(int):一次寫入四個字節。 注意和write(int)不一樣。write(int)只將該整數的最低一個8位寫入。剩餘三個8位丟棄。 writeBoolean(boolean); writeShort(short); writeLong(long); 剩下是數據類型也也同樣。 writeUTF(String):按照utf-8修改版將字符數據進行存儲。只能經過readUTF讀取。 |
測試: DataOutputStream
使用DataOutputStream寫數據文件。
public static void testDataInputStream() throws Exception { DataOutputStream out = new DataOutputStream(new FileOutputStream( "c:/a.txt"));
out.writeBoolean(true); out.writeByte(15); // 0x05 1 個字節 out.writeBytes("abc"); // 0x 0041 2個字節 out.writeChar('X'); // ?? out.writeChars("xyz"); out.writeLong(111); out.writeUTF("中國");
out.close();
DataInputStream in = new DataInputStream( new FileInputStream("c:/a.txt")); System.out.println(in.readBoolean()); System.out.println(in.readByte());
System.out.println(in.readByte()); System.out.println(in.readByte()); System.out.println(in.readByte());
System.out.println(in.readChar());
System.out.println(in.readChar()); System.out.println(in.readChar()); System.out.println(in.readChar());
System.out.println(in.readLong());
System.out.println(in.readUTF()); in.close(); } |