一、概述
I/O的本質是通信。
有多種源端和接收端:文件(硬盤)、鍵盤/控制檯、網絡鏈接等
有多種不同的通信方式:順序、隨機存取、緩衝、二進制、按字符、按行、按字等。
java設計了大量的類來解決 這個通信問題。
在電腦上的數據有三種存儲方式,一種是外存,一種是內存,一種是緩存。緩存用於提升計算機工作效率。
將數據衝外存中讀取到內存中的稱爲輸入流,將數據從內存寫入外存中的稱爲輸出流。
I/O類圖:
二:File類
1、概述:
既能代表一個文件的名稱,也能代表一個目錄。
File f1 = new File(“c:\java\demo.txt”); File f2 = new File(“c:\java”);
2、各種方法:
1:創建。
- boolean createNewFile():在指定目錄下創建文件,如果該文件已存在,則不創建。而對操作文件的輸出流而言,輸出流對象已建立,就會創建文件,如果文件已存在,會覆蓋。除非續寫。
boolean mkdir():創建此抽象路徑名指定的目錄。 boolean mkdirs() :創建多級目錄。
2 :刪除。
- boolean delete():刪除此抽象路徑名錶示的文件或目錄。
void deleteOnExit():在虛擬機退出時刪除。
注意:在刪除文件夾時,必須保證這個文件夾中沒有任何內容,纔可以將該文件夾用 delete刪除。
window的刪除動作,是從裏往外刪。 注意:java刪除文件不走回收站。要慎用。
3:獲取.
- long length():獲取文件大小。
String getName():返回由此抽象路徑名錶示的文件或目錄的名稱。
String getPath():將此抽象路徑名轉換爲一個路徑名字符串。
String getAbsolutePath():返回此抽象路徑名的絕對路徑名字符串。
String getParent():返回此抽象路徑名父目錄的抽象路徑名,如果此路徑名沒有指定父目錄,則返回 null。
long lastModified():返回此抽象路徑名錶示的文件最後一次被修改的時間。
File.pathSeparator:返回當前系統默認的路徑分隔符, windows默認爲 “;”。
File.Separator:返回當前系統默認的目錄分隔符, windows默認爲 “\”。
4 :判斷:
- boolean exists():判斷文件或者文件夾是否存在。
boolean isDirectory():測試此抽象路徑名錶示的文件是否是一個目錄。
boolean isFile():測試此抽象路徑名錶示的文件是否是一個標準文件。
boolean isHidden():測試此抽象路徑名指定的文件是否是一個隱藏文件。
boolean isAbsolute():測試此抽象路徑名是否爲絕對路徑名。
5 :重命名。
- boolean renameTo(File dest) :可以實現移動的效果: 剪切 +重命名。
6.目錄的操作:
- 當file對象代表目錄時,實質上是代表該目錄下一組文件的名稱,是一個文件集合,可以用listFiles方法獲得一個File類型的數組:
File[ ] f2list = f2.listFiles( );
- 當要獲取所有文件目錄包括子目錄時,可以採用遞歸的方式:
public static void showDir(File dir,int level)
{
System.out.println(getLevel(level)+dir.getName());
level++;
File[] files = dir.listFiles();
for(int x=0; x<files.length; x++)
{
if(files[x].isDirectory())
showDir(files[x],level);
else
System.out.println(getLevel(level)+files[x]);
}
}
public static String getLevel(int level)
{
StringBuilder sb = new StringBuilder();
sb.append("|--");
for(int x=0; x<level; x++)
{
//sb.append("|--");
sb.insert(0,"| ");
}
return sb.toString();
}
三、RandomAcessFile類:
非流類,主要提供隨機訪問文件的功能,內部封裝了格式化輸入輸出流(DataInputStream和DataOutputStream),必須文件的結構是已知的,才能使用本類來操作。
使用方法:通過getFilePointer獲取指針位置,利用seek()方法在文件中移動指針
可以指定操作模式:只讀或讀寫。讀寫時,如果文件存在,不會像File,PrintWriter那樣覆蓋。
提供瞭如下方法:
(1)RandomAccessFile raf = new RandomAccessFile(“file”,”r或者rw”);
(2)raf.seek(long pos); 把文件指針設定在pos處
(3)long raf.getFilePointer():返回文件指針
(4)long raf.length();返回長度
(5)raf.skipBytes(long);
四、字節流
InputStream接口提供的方法:
- (1)int read(); 讀取1個字節數據,然後返回該字節轉換成的0-255範圍的int值,讀取完爲-1
注意:這裏read方法中的byte轉換成int和一般類型轉換的byte轉int是不同的,這裏的int範圍一定要在0-255之間。
(2)int read(byte[]b); 讀取流中數據寫入字節數組,返回本次讀入的長度int值
(3)int read(byte[]b,int off,int len) 讀取len長度的字節數,寫入b的off開始的字節數組中
注意:如果考慮效率方面,則可以使用(2)(3)的方法,因爲能夠批量讀取。
(4)void close(); 關閉流
OutputStream提供瞭如下方法:
- (1)write(int b); 將b先轉型成byte即截取低八位字節,並寫入輸出流,例如如果b=257,則實際寫入的只是1
(2)write(byte[]b); 將byte數組寫入到輸出流中。
(3)write(byte[]b,int off,int len);
(4)void flush();
(5)void close();關閉前會自行刷新一次
五、字符流
與字節流的對比:
- 字符流處理的單元爲2個字節的Unicode字符,分別操作字符、字符數組或字符串;而字節流處理單元爲1個字節, 操作字節和字節數組。
所有文件的儲存是都是字節(byte)的儲存,在磁盤上保留的並不是文件的字符而是先把字符編碼成字節,再儲存這些字節到磁盤。在讀取文件時,也是一個字節一個字節地讀取以形成字節序列.
字節流可用於任何類型的對象,包括二進制對象,而字符流只能處理字符或者字符串; 2. 字節流提供了處理任何類型的IO操作的功能,但它不能直接處理Unicode字符,而字符流就可以。
Reader:用於讀取字符流的抽象類。子類必須實現的方法只有 read(char[], int, int) 和 close()。
- read()返回的值爲int型,是讀入該char[]數組的字符數,不同於字節流的單個字節的int值。
Writer:寫入字符流的抽象類。子類必須實現的方法僅有 write(char[], int, int)、flush() 和 close()。
編碼問題:
在Java中,字符串用統一的Unicode編碼,每個字符佔用兩個字節,與編碼有關的兩個主要函數爲:
- 1)將字符串用指定的編碼集合解析成字節數組,完成Unicode-〉charsetName轉換
public byte[] getBytes(String charsetName)
2)將字節數組以指定的編碼集合構造成字符串,完成charsetName-〉Unicode轉換
public new String(byte[] bytes, String charsetName)
流編碼過程:
- 從文件讀取:源bytes–>根據指定的或默認的編碼表編碼後的字符–>Unicode字符(java中最終的char)
寫入到文件:與String.getBytes([encode])同理:Unicode字符–>encode字符–>bytes。
Unicode統一採用2個字節編碼,UTF-8是Unicode的改進,原本ASCII碼的字符還是一個字節。
Unicode與各編碼之間的直接轉換
- Unicode和GBK
每個漢字轉換爲兩個字節,且是可逆的,即通過字節可以轉換回字符串
Unicode和UTF-8
測試結果如下,每個漢字轉換爲三個字節,且是可逆的,即通過字節可以轉換回字符串
Unicode和ISO-8859-1
測試結果如下,當存在漢字時轉換失敗,非可逆,即通過字節不能再轉換回字符串
Unicode與各編碼之間的交叉轉換
在上面直接轉換中,由字符串(Unicode)生成的字節數組,在構造回字符串時,使用的是正確的編碼集合,如果使用的不是正確的編碼集合會怎樣呢?會正確構造嗎?如果不能正確構造能有辦法恢復嗎?會信息丟失嗎?
能夠正確顯示的中間不正確轉換
- String-GBK〉ByteArray-ISO-8859-1〉String-ISO-8859-1〉ByteArray-GBK〉String
String-UTF-8〉ByteArray-ISO-8859-1〉String-ISO-8859-1〉ByteArray-UTF-8〉String
String-UTF-8〉ByteArray-GBK〉String-GBK〉ByteArray-UTF-8〉String
六、裝飾器之轉換流
InputStreamReader和InputStreamWriterInputStreamReader:
以字節流爲數據源,經過編碼後變成字符。編碼方式如未指定,使用的是系統默認的編碼表。
InputStreamReader in2 = new InputStreamReader(new FileInputStream("Reader.txt"),"UTF-8");
七、裝飾器之緩衝功能:
BufferedInputStream和BufferedOutputStream:
緩衝流作爲過濾流的中間層,構建緩衝區,提高效率
BufferedInputStream in = new BufferedInputStream(new FileInputStream("1.txt"));
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream("1.txt"));
BufferedReader 和BufferedWriter :
特點:提供了readLine()方法,可以一行一行地讀取文本。
FileReader fr = new FileReader("bufdemo.txt");
BufferedReader bufr = new BufferedReader(fr);
String line = null;
while((line=bufr.readLine())!=null)
{
System.out.println(line); //readLine方法返回的時候是不帶換行符的。
}
bufr.close();
FileWriter fw = new FileWriter("bufdemo.txt");
BufferedWriter bufw = new BufferedWriter(fw);//讓緩衝區和指定流相關聯。
for(int x=0; x<4; x++)
{
bufw.write(x+"abc");
bufw.newLine(); //寫入一個換行符,這個換行符可以依據平臺的不同寫入不同的換行符。
bufw.flush();//對緩衝區進行刷新,可以讓數據到目的地中。
}
bufw.close();//關閉緩衝區,其實就是在關閉具體的流。
八、文件的讀寫:文件流:
根據文件的類型選擇相應的流對象:
- 文本文件選用字符流,其他選擇字節流。且爲提高效率,多用緩衝功能。輸出流多采用打印流簡化書寫。
字節流:FileInputStream和FileOutputStream:
BufferedInputStream in = new BufferedInputStream(new FileInputStream("c:\\1.bmp"));
PrintStream out = new PrintStream("d:\\1.bmp",true);
字符流:FileReader和FileWriter
寫文本文件時,若需指定編碼表,則使用FileWriter+轉換流,若無需指定,使用PrintWriter簡化書寫。
BufferedReader bufr = new BufferedReader(new FileReader("c:\\1.txt"));
PrintWriter pw = new PrintWriter(new FileWriter("d:\\1.txt"));//使用默認編碼表編碼,若文件存在,則覆蓋。
九、標準輸入輸出:
System.in:鍵盤錄入封裝成一個InputStream,常用轉換流轉換成字符,一行行讀取
System.out: 控制檯輸出,封裝成一個OutputStream,提供了print和println方法。
十、數據格式化輸入輸出流:
DataInputStream和DataOutputStream
實現了DataInput和DataOutput接口的類。
DataInput提供瞭如下方法:
(1)Xxx readXxx();讀取基本數據類型
(2)int read(byte[]b);讀取至字節數組中,返回實際讀取的長度
(3)readChar()讀取一個字符即兩個字節
(4)String readUTF();
DataOutput提供瞭如下方法:
(1)void wrtieXxx(Xxx )寫入基本數據類型
(2)void writeBytes(String)能夠以字節方式寫入String,隨後可以用read讀取。
(3)void writeChars(String)以字符方式寫入,一個字符是兩個字節
(4)void writeUTF(String );
十一、打印流
PrintStream和PrintWriter: 簡化了書寫,且指定輸出流時,可定義自動刷新
PrintStream out = new PrintStream(OutputStream o,boolean autoflush);
提供了方法:
out.print(Xxx);
out.println(Xxx);
十二、序列流
序列流,作用就是將多個讀取流合併成一個讀取流。實現數據合併。
表示其他輸入流的邏輯串聯。它從輸入流的有序集合開始,並從第一個輸入流開始讀取,直到到達文件末尾,接着從第二個輸入流讀取,依次類推,直到到達包含的最後一個輸入流的文件末尾爲止。
這樣做,可以更方便的操作多個讀取流,其實這個序列流內部會有一個有序的集合容器,用於存儲多個讀取流對象。
SequenceInputStream(InputStream s1, InputStream s2)
SequenceInputStream(Enumeration<? extends InputStream> e)
對象的構造函數參數是枚舉,想要獲取枚舉,需要有Vector集合,但不高效。需用ArrayList,但ArrayList中沒有枚舉,只有自己去創建枚舉對象。
但是方法怎麼實現呢?因爲枚舉操作的是具體集合中的元素,所以無法具體實現,但是枚舉和迭代器是功能一樣的,所以,可以用迭代替代枚舉。
合併原理:多個讀取流對應一個輸出流。
切割原理:一個讀取流對應多個輸出流。
十二、操作對象的流與對象序列化
目的:將一個具體的對象進行持久化,寫入到硬盤上。
注意:靜態數據不能被序列化,因爲靜態數據不在堆內存中,是存儲在靜態方法區中。
如何將非靜態的數據不進行序列化?用transient 關鍵字修飾此變量即可。
Serializable:用於啓動對象的序列化功能,可以強制讓指定類具備序列化功能,該接口中沒有成員,這是一個標記接口。這個標記接口用於給序列化類提供UID。這個uid是依據類中的成員的數字簽名進行運行獲取的。如果不需要自動獲取一個uid,可以在類中,手動指定一個名稱爲serialVersionUID id號。依據編譯器的不同,或者對信息的高度敏感性。最好每一個序列化的類都進行手動顯示的UID的指定。
相關流:ObjectInputStream和ObjectOutputStream:
對象流實現了DataInput和DataOutput接口
對於對象:
(1)writeObject(Object);
(2)Object readObject(); 讀取後需要強制類型轉換
對於基本類型:使用readXxx和writeXxx方法
十三、管道流
管道讀取流和管道寫入流可以像管道一樣對接上,管道讀取流就可以讀取管道寫入流寫入的數據。
注意:需要加入多線程技術,因爲單線程,先執行read,會發生死鎖,因爲read方法是阻塞式的,沒有數據的read方法會讓線程等待。
public static void main(String[] args) throws IOException
{
PipedInputStream pipin = new PipedInputStream();
PipedOutputStream pipout = new PipedOutputStream();
pipin.connect(pipout);
new Thread(new Input(pipin)).start();
new Thread(new Output(pipout)).start();
}