一、InputStream與OutputStream
1. 輸入與輸出
我們編寫的程序除了自身會定義一些數據信息外,經常還會引用外界的數據,或是將自身的數據發送到外界。比如,我們編寫的程序想讀取一個文本文件,又或者我們想將程序中的某些數據寫入到一個文件中。這時我們就要使用輸入與輸出。
輸入: 讀
輸出: 寫
流: 單向
什么是輸入:輸入是一個從外界進入到程序的方向,通常我們需要“讀取”外界的數據時,使用輸入。所以輸入是用來讀取數據的。
什么是輸出:輸出是一個從程序發送到外界的方向,通常我們需要”寫出”數據到外界時,使用輸出。所以輸出是用來寫出數據的。
2. 節點流與處理流
低級 高級
按照流是否直接與特定的地方 (如磁盤、內存、設備等) 相連,分為節點流和處理流兩類。
節點流:可以從或向一個特定的地方(節點)讀寫數據。
處理流:是對一個已存在的流的連接和封裝,通過所封裝的流的功能調用實現數據讀寫。
處理流的構造方法總是要帶一個其他的流對象做參數。一個流對象經過其他流的多次包裝,稱為流的鏈接。
高級流不能獨立存在 用來處理其他流的
目的:簡化讀寫操作
3. InputStream與OutputStream常用方法
以字節為單位讀取數據的流: 字節輸入流
InputStream是所有字節輸入流的父類,其定義了基礎的讀取方法,常用的方法如下:
int read()
讀取一個字節,以int形式返回,該int值的”低八位”有效,若返回值為-1則表示EOF。
int read(byte[] d)
嘗試最多讀取給定數組的length個字節並存入該數組,返回值為實際讀取到的字節量。
OutputStream是所有字節輸出流的父類,其定義了基礎的寫出方法,常用的方法如下:
void write(int d)
寫出一個字節,寫的是給定的int的”低八位”
void write(byte[] d)
void write(byte[] d intoffset intlen)
將給定的字節數組中的所有字節全部寫出
這兩種流是抽象類 不能實例化
二、 文件流
1. 創建FIS對象
FileInputStream是文件的字節輸入流,我們使用該流可以以字節為單位讀取文件內容。
FileInputStream有兩個常用的構造方法:
FileInputStream(File file):
創建用於讀取給定的File對象所表示的文件FIS
例如:
File file = new File("demo.dat");
FileInputStream fis
= new FileInputStream(file);//創建一個用於讀取demo.dat文件的輸入流
FileInputStream(String name):
創建用於讀取給定的文件系統中的路徑名name所指定的文件的FIS
例如
FileInputStream fis
//創建一個用於讀取demo.dat文件的輸入流
= new FileInputStream("demo");
2. 創建FOS對象(重寫模式)
FileOutputStream是文件的字節輸出流,我們使用該流可以以字節為單位將數據寫入文件。
構造方法:
FileOutputStream(File file)
創建一個向指定 File 對象表示的文件中寫入數據的文件輸出流。
例如:
FIle file = new File("demo.dat");
FileOutputStream fos = new FileOutputStream(file);
FileOutputStream(String filename):
創建一個向具有指定名稱的文件中寫入數據的輸出文 件流。
例如:
FileOutputStream fos = new FileOutputStream("demo.dat");
這里需要注意,若指定的文件已經包含內容,那么當使用FOS對其寫入數據時,會將該文件中原有數據全部清除。
3. 創建FOS對象(追加模式)
通過上一節的構造方法創建的FOS對文件進行寫操作時會覆蓋文件中原有數據。若想在文件的原有數據之后追加新數據則需要以下構造方法創建FOS
構造方法:
FileOutputStream(File file,boolean append)
創建一個向指定 File 對象表示的文件中寫入數據的文件輸出流。
例如:
File file = new File("demo.dat");
FileOutputStream fos = new FileOutputStream(file,true);
FileOutputStream(String filename,boolean append):
創建一個向具有指定名稱的文件中寫入數據的輸出文 件流。
例如:
FileOutputStream fos = new FileOutputStream("demo.dat",true);
以上兩個構造方法中,第二個參數若為true,那么通過該FOS寫出的數據都是在文件末尾追加的。
1 /** 2 * 使用文件字節輸出流向文件中寫出數據 3 * @author Administrator 4 * 5 */ 6 public class TestFOS1 { 7 public static void main(String[] args) throws IOException { 8 /* 9 * 創建一個文件字節輸出流用於向fos.dat文件中 10 * 寫出字節數據 11 */ 12 /* 13 * 添加布爾值參數 true 變成追加型操作 14 * FileOutputStream(File file,boolean append) 15 FileOutputStream(String str,boolean append) 16 */ 17 FileOutputStream fos = new FileOutputStream("fos.dat",true); 18 //寫一個字符串 19 fos.write("hello world".getBytes("GBK")); 20 21 //流用完了要記得關閉 22 fos.close(); 23 25 } 26 }
4. read()和write(int d)方法
FileInputStream繼承自InputStream,其提供了以字節為單位讀取文件數據的方法read。
int read()
從此輸入流中讀取一個數據字節,若返回-1則表示EOF(End Of File)
FileOutputStream繼承自OutputStream,其提供了以字節為單位向文件寫數據的方法write。
void write(int d)
將指定字節寫入此文件輸出流。,這里指寫給定的int值的”低八位”
例如
FileOutputStream fos = new FileOutputStream("demo.dat"); fos.write('A');//這里要注意,char占用2個字節,但這里只寫入了1個字節。
5. read(byte[] d)和write(byte[] d)方法
FileInputStream也支持批量讀取字節數據的方法:
int read(byte[] b)
從此輸入流中將最多 b.length 個字節的數據讀入一個 byte 數組中
FileOutputStream也支持批量寫出字節數據的方法:
void write(byte[] d)
將 b.length 個字節從指定 byte 數組寫入此文件輸出流中。
例如:
FileOutputStream fos = new FileOutputStream("demo.txt"); byte[] data = "HelloWorld".getBytes(); fos.write(data);//會將HelloWorld的所有字節寫入文件。 void write(byte[] d,int offset,int len)
將指定 byte 數組中從偏移量 off 開始的 len 個字節寫入此文件輸出流。
例如:
FileOutputStream fos = new FileOutputStream("demo.txt"); byte[] data = "HelloWorld".getBytes(); fos.write(data,5,5);//只會將world這5個字節寫入文件。
** * 使用文件輸入流將數據從文件中讀取 * @author Administrator * */ class TestFIS{ public static void main(String[] args) throws IOException { /* * 使用文件字節輸入流讀取fos.dat文件 */ FileInputStream fis =new FileInputStream("fos.dat"); int d = -1; while((d=fis.read())!=-1){ char c=(char)d; System.out.print(c); } } }
/** * 使用文件的輸入流與輸出流實現文件復制工作 */ class TestCopy{ /* * 1.創建文件輸入流用於讀取原文件 * 2.創建文件輸出流用於向目標文件中寫 * 3.循環從源文件中讀取每一個字節 * 4.將讀取到的每一個字節寫入目標文件 * 5.關閉兩個流 */ public static void main(String[] args) throws IOException { FileInputStream fis =new FileInputStream("fos.dat"); FileOutputStream fos = new FileOutputStream("fos1.dat",true); int d=-1; while((d=fis.read())!=-1){ //char c= (char)d; fos.write(d); } fis.close(); fos.close(); } }
/** * 使用文件流的批量讀和批量寫 復制文件 */ class TestCopy2{ public static void main(String[] args) throws IOException { FileInputStream fis =new FileInputStream("fos.dat"); FileOutputStream fos = new FileOutputStream("fos1.dat",true); byte [] b= new byte[10*1024]; int d=-1; while((d=fis.read(b))!=-1){ fos.write(d); } fis.close(); fos.close(); } }
三、緩沖流
1. BIS基本工作原理
在讀取數據時若以字節為單位讀取數據,會導致讀取次數過於頻繁從而大大的降低讀取效率。為此我們可以通過提高一次讀取的字節數量減少讀寫次數來提高讀取的效率。
BufferedInputStream是緩沖字節輸入流。其內部維護着一個緩沖區(字節數組),使用該流在讀取一個字節時,該流會盡可能多的一次性讀取若干字節並存入緩沖區,然后逐一的將字節返回,直到緩沖區中的數據被全部讀取完畢,會再次讀取若干字節從而反復。這樣就減少了讀取的次數,從而提高了讀取效率。
BIS是一個處理流,該流為我們提供了緩沖功能。
2. BOS基本工作原理
與緩沖輸入流相似,在向硬件設備做寫出操作時,增大寫出次數無疑也會降低寫出效率,為此我們可以使用緩沖輸出流來一次性批量寫出若干數據減少寫出次數來提高寫 出效率。
BufferedOutputStream緩沖輸出流內部也維護着一個緩沖區,每當我們向該流寫數據時,都會先將數據存入緩沖區,當緩沖區已滿時,緩沖流會將數據一次性全部寫出。
3. 使用BIS與BOS實現緩沖輸入輸出
使用緩沖流來實現文件復制:
FileInputStream fis = new FileInputStream("java.zip"); BufferedInputStream bis = new BufferedInputStream(fis); FileOutputStream fos = new FileOutputStream("copy_java.zip"); BufferedOutputStream bos = new BufferedOutputStream(fos); int d = -1; while((d = bis.read())!=-1){ bos.write(d); } bis.close();//讀寫完畢后要關閉流,只需要關閉最外層的流即可 bos.close();
4. flush方法
使用緩沖輸出流可以提高寫出效率,但是這也存在着一個問題,就是寫出數據缺乏即時性。有時我們需要需要在執行完某些寫出操作后,就希望將這些數據確實寫出,而非在緩沖區中保存直到緩沖區滿后才寫出。這時我們可以使用緩沖流的一個方法flush。
void flush()
清空緩沖區,將緩沖區中的數據強制寫出。
BufferedOutputStream bos = new BufferedOutputStream( new FileOutputStream("demo.dat") ); bos.write('a');//並沒有向磁盤寫出,而是寫入到了BOS的緩存中 bos.flush();//強制將緩存中的數據一次性寫出,這時‘a’才會被寫入磁盤 bos.close();//實際上,close()方法在變比緩沖流前也調用了flush()
class TestFlow2{ public static void main(String[] args) throws IOException { FileOutputStream fos =new FileOutputStream("1.txt"); BufferedOutputStream bos = new BufferedOutputStream(fos); String str = "hello"; bos.write(str.getBytes()); bos.flush(); //清楚緩存區 bos.close(); } }
四、對象流
對象是存在於內存中的。有時候我們需要將對象保存到硬盤上,又有時我們需要將對象傳輸到另一台計算機上等等這樣的操作。這時我們需要將對象轉換為一個字節序列,而這個過程就稱為對象序列化。相反,我們有這樣一個字節序列需要將其轉換為對應的對象,這個過程就稱為對象的反序列化。
1. 使用OOS實現對象序列化
ObjectOutputStream是用來對對象進行序列化的輸出流。
其實現對象序列化的方法為:
void writeObject(Object o)
該方法可以將給定的對象轉換為一個字節序列后寫出。
例如:
Emp emp = new Emp("張三",12,"男");
FileOutputStream fos = new FileOutputStream("Emp.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(emp);//將emp對象序列化后寫入文件
oos.close();
2. 使用OIS實現對象反序列化
ObjectInputStream是用來對對象進行反序列化的輸入流。
其實現對象反序列化的方法為:
Object readObject()
該方法可以從流中讀取字節並轉換為對應的對象。
例如:
FileInputStream fis = new FileInputStream("Emp.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
Emp emp = (Emp)ois.readObject();//將Emp對象從文件中讀取並反序列
....
ois.close();
3. Serializable接口
ObjectOutputStream在對對象進行序列化時有一個要求,就是需要序列化的對象所屬的類必須實現Serializable接口。
實現該接口不需要重寫任何方法。其只是作為可序列化的標志。
通常實現該接口的類需要提供一個常量serialVersionUID,表明該類的版本。若不顯示的聲明,在對象序列化時也會根據當前類的各個方面計算該類的默認serialVersionUID,但不同平台編譯器實現有所不同,所以若向跨平台,都應顯示的聲明版本號。
如果聲明的類序列化存到硬盤上面,之后隨着需求的變化更改了類別的屬性(增加或減少或改名),那么當反序列化時,就會出現InvalidClassException,這樣就會造成不兼容性的問題。
但當serialVersionUID相同時,它就會將不一樣的field以type的預設值反序列化,可避開不兼容性問題。
例如:
/** * 每一個實例用於描述一個人的信息 * @author Administrator * */ // Serializable 實現這個接口就是打標簽 無需在重寫任何東西 class person implements Serializable{ private static final long serialVersionUID = 1L; //版本號 private String name; private int age; private int phoneNumber; private int sex; private List<String> otherInfo; private Date birDay; public person(String name, int age, int phoneNumber, int sex, List<String> otherInfo, Date birDay) { super(); this.name = name; this.age = age; this.phoneNumber = phoneNumber; this.sex = sex; this.otherInfo = otherInfo; this.birDay = birDay; } @Override public String toString() { return "person [age=" + age + ", birDay=" + birDay + ", name=" + name + ", otherInfo=" + otherInfo + ", phoneNumber=" + phoneNumber + ", sex=" + sex + "]"; } }
/** * 使用ObjectOutputStream 將對象寫入文件 * @author Administrator * */ class TestOOSDemo{ public static void main(String[] args) throws IOException { List<String> otherInfo =new ArrayList<String>(); otherInfo.add("其他信息1"); otherInfo.add("其他信息2"); otherInfo.add("其他信息3"); person ps =new person("張三",25,1364951,1,otherInfo, null); System.out.println(ps); /* * 將Person序列化后寫入文件中 * 1、向文件中寫數據的流:FileOutputStream * 2、將對象序列化的流:ObjectOutputStream */ FileOutputStream fos =new FileOutputStream("ps.obj"); ObjectOutputStream oos =new ObjectOutputStream(fos); /* * 將對象轉為字節 將數據寫入磁盤的過程稱之為:持久化 */ oos.writeObject(ps); } }
/** * 實現數據的反序列化 * @author Administrator * */ class TestBisDemo{ public static void main(String[] args) throws IOException, ClassNotFoundException { /* * 從文件中讀取字節數據后再轉換為對象 * 1.FIS 讀取文件 * 2.OIS 反序列化 */ FileInputStream fis = new FileInputStream("ps.obj"); ObjectInputStream ois =new ObjectInputStream(fis); /* * 反序列的方法 * Object readObject(); */ person ps = (person)ois.readObject(); System.out.println(ps); ois.close(); }
4. transient關鍵字
對象在序列化后得到的字節序列往往比較大,有時我們在對一個對象進行序列化時可以忽略某些不必要的屬性,從而對序列化后得到的字節序列”瘦身”。
關鍵字 transient
被該關鍵字修飾的屬性在序列化時其值將被忽略
public class Emp implements Serializable{ private static final long serialVersionUID = 1L; private String name; private transient int age;//該屬性在序列化時會被忽略 private String gender; //getter and setter and other ... }