Java輸入輸出流(I/O)
目標:
------------------------------------------------------------
第十四章 Stream I/O and Files, 共 44 個slide(411-455);
知識點:一. 流的概念
程式的主要任務是操縱資料。在Java中,把一組有序的資料序列稱為流。根據操作的方向,可以把流分為輸入流和
輸出流兩種。程式從輸入流讀取資料,向輸出流寫出資料。
檔案 輸入流 輸出流 檔案
記憶體 -------------> Java程式 ------------------> 記憶體
鍵盤 控制檯
資料來源 資料目的地
Java I/O系統負責處理程式的輸入和輸出,I/O類庫位於java.io包中,它對各種常見的輸入流和輸出流進行了抽象。
如果資料流中最小的資料單元是位元組,那麼稱這種流為位元組流;如果資料流中最小的資料單元是字元,那麼稱這種流
為字元流。在I/O類庫中,java.io.InputStream和java.io.OutputStream分別表示位元組輸入流和位元組輸出流,
java.io.Reader和java.io.Writer分別表示字元輸入流和字元輸出流。
二. 位元組輸入流和輸出流概述
在java.io包中,java.io.InputStream表示位元組輸入流,java.io.OutputStream表示位元組輸出流,它們都是抽象類,不
能被例項化。
InputStream類提供了一系列和讀取資料有關的方法:
1. read(): 從輸入流讀取資料:有三種過載形式:
a. int read(): 從輸入流讀取一個8位的位元組,把它轉換為0-255之間的整數,並返回這一整數。例如,如果讀到的
位元組為9,則返回9,如果讀到的位元組為-9,則返回247。如果遇到輸入流的結尾,則返回-1;
b. int read(byte[] b): 從輸入流讀取若干個位元組,把它們儲存到引數b指定的位元組陣列中。返回的整數表示讀取的
位元組數。如果遇到輸入流的結尾,則返回-1;
c. int read(byte[] b, int off, int len): 從輸入流讀取若干個位元組,把它們儲存到引數b指定的位元組陣列中。
返回的整數表示讀取的位元組數。引數off指定在位元組陣列中開始儲存資料的起始下標,引數len指定
讀取的位元組數目。返回的整數表示實現讀取的位元組數。如果遇到輸入流的結尾,則返回-1;
以上第一個read方法從輸入流讀取一個位元組,而其餘兩個read方法從輸入流批量讀取若干位元組。在從檔案或鍵盤讀資料
時,採用後面兩個read方法可以減少進行物理讀檔案或鍵盤的次數,因此能提高I/O操作的效率。
2. void close(): 關閉輸入流,InputStream類本身的close()方法不執行任何操作。它的一些子類覆蓋了close()方法,
在close()方法中釋放和流有關的系統資源。
3. int available(): 返回可以從輸入流中讀取的位元組數目;
4. skip(long): 從輸入流中跳過引數n指定數目的位元組。
5. boolean markSupported(),void mark(int),void reset(): 如果要從流中重複讀入資料,先用markSupported()方法
來判斷這個流是否支援重複讀入資料,如果返回true,則表明可以在流上設定標記。接下來呼叫mark(int readLimit)
方法從流的當前位置開始設定標記。最後呼叫reset()方法,該方法使輸入流重新定位到剛才做了標記的起始位置。這
樣就可以重複讀取做過標記的資料了。
OuputStream類提供了一系列和寫資料有關的方法:
1. write(): 向輸出流寫入資料:有三種過載形式:
a. void write(int b): 向輸出流寫入一個位元組;
b. void write(byte[] b): 把引數b指定的位元組陣列中的所有位元組寫到輸出流;
c. void write(byte[] b, int off, int len): 把引數b指定的位元組陣列中的所有位元組寫到輸出流,引數off指定位元組
陣列的起始下標,從這個位置開始輸出由引數len指定數目的位元組;
以上第一個write方法從輸出流寫入一個位元組,而其餘兩個write方法從輸出流批量寫出若干位元組。在向檔案或控制檯寫資料
時,採用後面兩個write方法可以減少進行物理讀檔案或鍵盤的次數,因此能提高I/O操作的效率。
2. void close(): 關閉輸出流,OutputStream類本身的close()方法不執行任何操作。它的一些子類覆蓋了close()方法,
在close()方法中釋放和流有關的系統資源。
3. void flush(): OutputStream類本身的flush()方法不執行任何操作,它的一些帶有緩衝區的子類(比如BufferedOutputStream
和PrintStream類)覆蓋了flush()方法。通過帶緩衝區的輸出流寫資料時,資料先儲存在緩衝區中,積累到
一定程度才會真正寫到輸出流中。緩衝區通常用位元組陣列實現,實際上是指一塊記憶體空間。flush()方法強
制把緩衝區內的資料寫到輸出流中。
三. 輸入流和輸出流層級結構
1. ByteArrayInputStream: 把位元組陣列轉換為輸入流;
2. FileInputStream: 從檔案中讀取資料;
3. PipedInputStream: 連線一個PipedOutputStream;
4. SequenceInputStream: 把幾個輸入流轉換為一個輸入流;
5. ObjectInputStream: 物件輸入流;
6. FilterInputStream: 過濾器,擴充套件其它輸入流功能;
講解:ch14.ByteArrayTester.java
位元組型別 8位二進位制形式 轉換為int型別二進位制形式 int值
-1 1111 1111 0000 ... 1111 1111 255
-9 1111 0111 0000 ... 1111 0111 247
講解:ch14.FileInputStreamTester.java
字元 編碼值
a -> 97
b -> 98
c -> 99
1 -> 49
空格 -> 32
發 -> 186 195 //一個漢字佔2個位元組.
講解:ch14.UseBuffer.java
四. BufferedInputStream類
BufferedInputStream類覆蓋了被過濾的輸入流的讀資料行為,利用緩衝區來提高讀資料的效率。BufferedInputStream類先
把一批資料讀入到緩衝區,接下來 read()方法只需要從緩衝區內獲取資料,就能減少物理性讀取資料的次數。
. BufferedInputStream(InputStream in)——引數in指定需要被過濾的輸入流。
. BufferedInputStream(InputStream in, int size)——引數in指定需要被過濾的輸入流。引數size指定緩衝區的大小,
以位元組為單位。
五. DataInputStream 類
DataInputStream 實現了DataInput介面,用於讀取基本型別資料,如int, float, long, double和boolean等。
. readByte()——從輸入流中讀取1個位元組,指它轉換為byte型別的資料;
. readLong()——從輸入流中讀取8個位元組,指它轉換為long型別的資料;
. readFloat()——從輸入流中讀取4個位元組,指它轉換為float型別的資料;
. readUTF()—— 從輸入流中讀取1到3個位元組,指它轉換為UTF-8字元編碼的字串;
講解:ch14.FormatDataIO.java
六. 管道輸入類:PipedInputStream 類
管道輸入流從一個管理輸出流中讀取資料。通常由一個執行緒向管理輸出流寫資料,由另一個執行緒從管理輸入流中讀取資料,
兩個執行緒可以用管理來通訊。
講解:ch14.PipedTest.java
七. Reader and Writer概述
InputStream和OutputStream類處理的是位元組流,也就是說,資料流中的最小單元為一個位元組,它包括8個二進位制位。在許
多應用場合,Java應用程式需要讀寫文字檔案。在文字檔案中存放了採用特定字元編碼的字元。為了便於讀於各種字元
編碼的字元,java.io包中提供了Reader/Writer類,它們分別表示字元輸入流和字元輸出流。
在處理字元流時,最主要的問題是進行字元編碼的轉換。Java語言採用Unicode字元編碼。對於每一個字元,Java虛擬機器會
為其分配兩個位元組的記憶體。而在文字檔案中,字元有可能採用其他型別的編碼,比如GBK和UTF-8字元編碼等。
Reader類能夠將輸入流中採用其他編碼型別的字元轉換為Unicode字元,然後在記憶體中為這些Unicode字元分配記憶體。Writer
類能夠把記憶體中的Unicode字元轉換為其他編碼型別的字元,再寫到輸出流中。
在預設情況下,Reader和Writer會在本地平臺的字元編碼和Unicode字元編碼之間進行編碼轉換。
Writer的write()方法
使用Unicode字元編碼的字 ----------------------------------------> 使用本地平臺的字元編碼的字串
符串(記憶體中) <--------------------------------------- (資料來源/資料目的地)
Reader的read()方法
如果要輸入或輸出採用特定型別編碼的字串,可以使用InputStreamReader類和OutputStreamWriter類。在它們的構造方法
中可以指定輸入流或輸出流的字元編碼。
OutputStreamWriter的write()方法
使用Unicode字元編碼的字 ----------------------------------------> 使用本地平臺的字元編碼的字串
符串(記憶體中) <--------------------------------------- (資料來源/資料目的地)
InputStreamReader的read()方法
由於Reader和Writer採用了字元編碼轉換技術,Java I/O系統能夠正確地訪問採用各種字元編碼的文字檔案,另一方面,在為
字元分配記憶體時,虛擬機器對字元統一採用Unicode字元編碼,因此Java程式處理字元具有平臺獨立性。
CharArrayReader : 把字元陣列轉換為Reader,從字元陣列中讀取字元;
BufferedReader : 過濾器,為其他Reader提供讀緩衝區,此外,它的readLine()方法能夠讀入一行字串;
StringReader : 把字串轉換為Reader,從字串中讀取字元;
PipedReader : 連線一個PipedWriter;
PushBackReader : 能把讀到的字元壓回到緩衝區中,通常用做編譯器的掃描器,在程式中一般很少使用它。
InputStreamReader : 過濾器,把InputStream轉換為Reader,可以指定字元編碼;
FileReader : 從檔案中讀取字元;
八. InputStreamReader類
InputStreamReader類把InputStream型別轉換為Reader型別,構造方法:
. InputStreamReader(InputStream in): 按照本地平臺的字元編碼讀取輸入流中的字元;
. InputStreamReader(InputStream in, String charsetName): 按照指定的字元編碼讀取輸入流中的字元;
九. FileReader類
InputStreamReader的一個子類,用於從檔案中讀取字元資料。該類只能按照本地平臺的字元編碼來讀取資料,使用者不能
指定其他字元編碼型別。
. FileReader(File file): 引數file指定需要讀取的檔案;
. FileReader(String name): 引數name指定需要讀取的檔案的路徑;
十. BufferedReader類
Reader類的read()方法每次都從資料來源讀入一個字元,BufferedReader帶有緩衝區,它可以先把一批資料讀到緩衝區內,
接下來的操作都從緩衝區內獲取資料,避免每次都從資料來源讀取資料並進行字元編碼轉換,從而提高讀操作的效率。
BufferedReader的readLine()方法可以一次讀入一行字元,以字元形式返回。
. BufferedReader(Reader in): 指定被修飾的Reader類;
. BufferedReader(Reader in, int sz): 引數in指定被裝飾的Reader類,引數sz指定緩衝區的大小,以字元為單位。
十一. File類
File類提供管理檔案或目錄的方法。File例項表示真實檔案系統中的一個檔案或者目錄。
1. 構造方法
. File(String pathname): 引數pathname表示檔案路徑或者目錄路徑;
. File(String parent, String child): 引數parent表示根路徑,引數child表示子路徑;
. File(File parent, String child): 引數parent表示根路徑,引數child表示子路徑;
只處理一個檔案,使用第一個構造方法;如處理一個公共目錄的若干子目錄或檔案,那麼使用第二個或者第三個更方便。
2. 普通方法
. boolean createNewFile():建立一個新的檔案,如果檔案已經存在,則建立失敗(返回false),否則建立
成功(返回true)。
. boolean delete():刪除檔案或者空目錄
. boolean mkdir()/mkdirs():建立一個或者多個目錄(連續建立)->如果該目錄的父目錄不存在話,那麼還會建立所
有的父目錄;
. boolean renameTo(File destination):檔案的改名
. boolean canRead()/canWrite():判斷指定的檔案是否能讀取或者寫入資料
. boolean exists():判斷指定的檔案或者目錄是否存在
. String[] list():返回指定目錄下所有檔名或者子目錄名所組成的字串陣列
. long lastModified():返回指定檔案最後一次被修改的時間(從1970年1月1日凌晨12點到這個檔案的修改時間
之間所經歷的毫秒數)
. String getPath()/getAbsolutePath():返回指定檔案或者目錄的路徑和絕對路徑
. String getCanonicalPath(): 獲取該File物件所代表的檔案或者目錄的正規路徑
. String getParent()/getName():返回指定檔案或者目錄的父目錄(沒有返回null)和名字
File f = new File(".\\test.txt"));
System.out.println(f.getCanonicalPath()); //c:\mypath\test.txt
System.out.println(f.getAbsolutePath()); //c:\mypath\ .\test.txt
System.out.println(f.getPath()); //.\test.txt
if(!f.exists()) f.createNewFile());
講解:ch04.FileEx.java
十一. FileInputStream and FileOutputStream
1. 當建立一個FileInputStream物件的時候,檔案必須存在以及是可讀的FileInputStream(File file)
FileInputStream(String name)
2. 當建立一個FileOutputStream物件的時候,可以建立一個新的檔案,也可以覆蓋一個已經存在的同名檔案。
FileOutputStream(File file)
FileOutputStream(File file, boolean append)
如果要建立的檔案已經存在,可以選擇向舊檔案新增新的內容(append為true)或者新的內容覆蓋舊檔案的
內容(append為false)。
FileOutputStream(String name)
FileOutputStream(String name, boolean append)
如果要建立的檔案已經存在,可以選擇向舊檔案新增新的內容(append為true)或者新的內容覆蓋舊檔案的
內容(append為false)。
注意:在這裡寫個com.briup.ch14.CopyTime.java
十二. FileReader and FileWriter
1. 讀寫字元檔案方便
2. 構造器
FileReader(File file)
FileReader(String name)
FileWriter(File file)
FileWriter(String filename)
FileReader:new FileReader(“d:/back/string.txt”) =
new InputStreamReader(new FileInputStream(“d:/back/string.txt”));
FileWriter:new FileWriter(“d:/back/string.txt”) =
new InputStreamWriter(new FileOutputStream(“d:/back/string.txt”));
注意:這裡可以將com.briup.ch14.FileStringTest.java用FileReader修改一下,並列印系統預設的字元編碼
十三. PrintWriter
可以輸出基本資料型別、物件、字元(字元陣列)和字串,但是不能輸出位元組流。
PrintWriter類可以代替橋和BufferedWriter
PrintStream類既可以輸出位元組流也可以輸出字元流或者字串
十四. Reading and Writing with RandomAccessFile
1. 實現資料的輸入和輸出
2. 用它能夠提供讀寫檔案的功能
3. 提供通過一個檔案指標從檔案的某一個斷點開始讀寫資料的功能
4. 構造器
RandomAccessFile(File file, String mode)
RandomAccessFile(String filename, String mode)
mode可以選擇讀、寫和讀寫的方式
5. 方法
read()/write() seek(long pointer) 定位到檔案的斷點
十五. 物件的序列化和反序列化
物件的序列化: 把物件寫到一個輸出流;
物件的反序列化:從一個輸入流中讀取一個物件;
1. 物件的持久化
2. 僅僅是一個物件的資料被序列化(將物件的資料序列化成位元組流)
3. 標識為transit的資料不能被序列化 例如:transit 類名 表示該類不能被序列化 或者transit 欄位
4. 要序列化的物件必須實現java.io.Serializable介面
物件的序列化主要用於:
1. 網路中傳輸的是位元組流的資料,網路中物件的傳輸,是將物件的資料經過序列化後轉換成位元組流。
2. 將物件資料序列化到檔案中,將物件資料轉換成位元組流儲存到檔案中。
從檔案中讀取位元組流資料並轉換成物件叫做物件的反序列化。
ObjectInputStream 和ObjectOutputStream(物件輸入和輸出流,可以讀寫基本資料型別和物件)
1. ObjectInputStream 和ObjectOutputStream為應用程式提供物件的持久化儲存
2. 一個ObjectInputStream 可以反序列化通過ObjectOutputStream寫入的基本資料型別和物件
3. 構造器
ObjectInputStream(InputStream in)
ObjectOutputStream(OutputStream out)
4. 方法
readObject()/writeObject() 將物件寫入到輸出流中或者從輸入流中讀取物件
注意:這裡寫com.briup.ch14.ObjectSerial.java 將一個Person類的物件序列化到檔案中,再將物件從檔案中反序
列化出來。
(Person和Address先不序列化測試一下,再序列化測試一下)
com.briup.ch14.CollectionSerialTest.java 將一個集合序列化到檔案中,再將集合從檔案中反序列化出來。
com.briup.ch14.BufferSerialTest.java 將一個Person類的物件序列化到快取中,再將物件從快取中反序列化出來。
Review Questions :
1.(Level 1)What are the main classes for input stream?
答:InputStream, DataInputStream, FileInputStream, ByteInputStream, BufferedInputStream
2.(Level 1)What are reader/writer classes?
答:Reader類能夠將輸入流中採用其他編碼型別的字元轉換為Unicode字元,然後在記憶體中為這些Unicode字元分配記憶體。 Writer類能夠把記憶體中的Unicode字元轉換為其他編碼型別的字元,再寫到輸出流中。
3.(Level 1)What are the bridges between InputStream and Reader?
答:InputStreamReader
4.(Level 1)What is object serialization?
答:把物件寫到一個輸出流;
5.(Level 2)The File class contains a method that changes the current working directory
A.True B.False
答:B
6.(Level 2)It is possible to use the File class to list the contents of the current working directory
A.True B.False
答:A
7.(Level 2)Readers have methods that can read and return floats and doubles
A.True B.False
答:B
8.(Level 3)How many bytes does the following code write to file dest ?
1.try{
2.FileOutputStream fos = new FileOutputStream(“dest”);
3.DataOutputStream dos = new DataOutputStream(fos);
4.dos.writelnt(3);
5.dos.writeDouble(0.001);
6.dos.close();
7.fos.close();
8.}catch(IOException e){}
A.2
B.8
C.12
D.16
E.It depends on the underlving system
答:C