java按照io流向基类_Java IO详解
1 Java IO流的概念,分類
1.1 Java IO流的概念
java的IO是實現輸入和輸出的基礎,可以方便的實現數據的輸入和輸出操作。在java中把不同的輸入/輸出源(鍵盤,文件,網絡連接等)抽象表述為“流”(stream)。流的本質是數據傳輸,根據數據傳輸特性將流抽象為各種類,方便更直觀的進行數據操作。
1.2.1按照流的流向分,可以分為輸入流和輸出流。
輸入流:只能從中讀入數據,而不能向其寫出數據。
輸出流:只能向其寫出數據,而不能向其讀入數據。
注:為什么叫寫出和讀入,這里涉及到一個方向問題,因為劃分輸入/輸出流時是從程序運行所在的內存的角度來考慮的;將內存中的數據寫出到硬盤中的文件,或者是將硬盤中文件的信息讀入到內存。
注:java的輸入流主要是InputStream和Reader作為基類,而輸出流則是主要由OutputStream和Writer作為基類。它們都是一些抽象基類,無法直接創建實例。
1.2.2 按照操作單元劃分,可以劃分為字節流和字符流。
字符流的由來: 因為數據編碼的不同,而有了對字符進行高效操作的流對象。本質其實就是基于字節流讀取時,去查了指定的碼表。
字節流和字符流的區別:
讀寫單位不同:字節流以字節(8bit)為單位,字符流以字符為單位,根據碼表映射字符,一次可能讀多個字節。
處理對象不同:字節流能處理所有類型的數據(如圖片、avi等),而字符流只能處理字符類型的數據。
字節流:一次讀入或讀出是8位二進制。
字符流:一次讀入或讀出是16位二進制。
1.2.3 按照流的角色劃分為節點流和處理流。
節點流(低級流):直接與數據源相連,讀入或寫出。直接使用節點流,讀寫不方便,為了更快的讀寫文件,才有了處理流。
處理流(高級流)和節點流一塊使用,在節點流的基礎上,再套接一層,套接在節點流上的就是處理流。如BufferedReader處理流的構造方法總是要帶一個其他的流對象做參數。一個流對象經過其他流的多次包裝,稱為流的鏈接。
1.3.1 流的原理淺析:
java IO流共涉及40多個類,這些類看上去很雜亂,但實際上很有規則,而且彼此之間存在非常緊密的聯系, Java IO流的40多個類都是從如下4個抽象類基類中派生出來的。
InputStream/Reader: 所有的輸入流的基類,前者是字節輸入流,后者是字符輸入流。
OutputStream/Writer: 所有輸出流的基類,前者是字節輸出流,后者是字符輸出流。
對于InputStream和Reader而言,它們把輸入設備抽象成為一個”水管“,這個水管的每個“水滴”依次排列。
字節流和字符流的處理方式其實很相似,只是它們處理的輸入/輸出單位不同而已。輸入流使用隱式的記錄指針來表示當前正準備從哪個“水滴”開始讀取,每當程序從InputStream或者Reader里面取出一個或者多個“水滴”后,記錄指針自定向后移動;除此之外,InputStream和Reader里面都提供了一些方法來控制記錄指針的移動。
對于OutputStream和Writer而言,它們同樣把輸出設備抽象成一個”水管“,只是這個水管里面沒有任何水滴。
當執行輸出時,程序相當于依次把“水滴”放入到輸出流的水管中,輸出流同樣采用隱示指針來標識當前水滴即將放入的位置,每當程序向 OutputStream 或者 Writer 里面輸出一個或者多個水滴后,記錄指針自動向后移動。
注:Java的處理流模型則體現了Java輸入和輸出流設計的靈活性。處理流的功能主要體現在以下兩個方面。
性能的提高:主要以增加緩沖的方式來提供輸入和輸出的效率。
操作的便捷:處理流可能提供了一系列便捷的方法來一次輸入和輸出大批量的內容,而不是輸入/輸出一個或者多個“水滴”。
1.3.2 java輸入/輸出流體系中常用的流的分類表
分類
字節輸入流
字節輸出流
字符輸入流
字符輸出流
抽象基類
InputStream
OutputStream
Reader
Writer
訪問文件
FileInputStream
FileOutputStream
FileReader
FileWriter
訪問數組
ByteArrayInputStream
ByteArrayOutputStream
CharArrayReader
CharArrayWriter
訪問管道
PipedInputStream
PipedOutputStream
PipedReader
PipedWriter
訪問字符串
StringReader
StringWriter
緩沖流
BufferedInputStream
BufferedOutputStream
BufferedReader
BufferedWriter
轉換流
InputStreamReader
OutputStreamWriter
對象流
ObjectInputStream
ObjectInputStream
抽象基類
FilterInputStream
FilterOutputStream
FilterReader
FilterWriter
打印流
PrintStream
PrintWriter
推回輸入流
PushbackInputStream
PushbackReader
特殊流
DataInputStream
DataOutputStreamn
注:表中粗體字所標出的類代表節點流,必須直接與指定的物理節點關聯:斜體字標出的類代表抽象基類,無法直接創建實例。
2.1 IO體系的基類(InputStream/Reader,OutputStream/Writer)
字節流和字符流的操作方式基本一致,只是操作的數據單元不同——字節流的操作單元是字節,字符流的操作單元是字符。
InputStream和Reader是所有輸入流的抽象基類,本身并不能創建實例來執行輸入,但它們將成為所有輸入流的模板,所以它們的方法是所有輸入流都可使用的方法。
在InputStream里面包含如下3個方法。
int read(); 從輸入流中讀取單個字節(相當于從水管中取出一滴水),返回所讀取的字節數據(字節數據可直接轉換為int類型)。
int read(byte[] b)從輸入流中最多讀取b.length個字節的數據,并將其存儲在字節數組b中,返回實際讀取的字節數。
int read(byte[] b,int off,int len); 從輸入流中最多讀取len個字節的數據,并將其存儲在數組b中,放入數組b中時,并不是從數組起點開始,而是從off位置開始,返回實際讀取的字節數。
在Reader中包含如下3個方法。
int read(); 從輸入流中讀取單個字符(相當于從水管中取出一滴水),返回所讀取的字符數據(字節數據可直接轉換為int類型)。
int read(char[] b)從輸入流中最多讀取b.length個字符的數據,并將其存儲在字節數組b中,返回實際讀取的字符數。
int read(char[] b,int off,int len); 從輸入流中最多讀取len個字符的數據,并將其存儲在數組b中,放入數組b中時,并不是從數組起點開始,而是從off位置開始,返回實際讀取的字符數。
InputStream和Reader提供的一些移動指針的方法:
void mark(int readAheadLimit); 在記錄指針當前位置記錄一個標記(mark)。
boolean markSupported(); 判斷此輸入流是否支持mark()操作,即是否支持記錄標記。
void reset(); 將此流的記錄指針重新定位到上一次記錄標記(mark)的位置。
long skip(long n); 記錄指針向前移動n個字節/字符。
OutputStream和Writer的用法也非常相似,兩個流都提供了如下三個方法:
void write(int c); 將指定的字節/字符輸出到輸出流中,其中c即可以代表字節,也可以代表字符。
void write(byte[]/char[] buf); 將字節數組/字符數組中的數據輸出到指定輸出流中。
void write(byte[]/char[] buf, int off,int len ); 將字節數組/字符數組中從off位置開始,長度為len的字節/字符輸出到輸出流中。
因為字符流直接以字符作為操作單位,所以Writer可以用字符串來代替字符數組,即以String對象作為參數。Writer里面還包含如下兩個方法。
void write(String str); 將str字符串里包含的字符輸出到指定輸出流中。
void write (String str, int off, int len); 將str字符串里面從off位置開始,長度為len的字符輸出到指定輸出流中。
2.2 IO體系的基類文件流的使用(FileInputStream/FileReader ,FileOutputStream/FileWriter)
使用FileInputStream讀取文件:
@Test
public void fileInputStreamTest() throws IOException {
FileInputStream fis = null;
try {
// 創建字節輸入流
fis = new FileInputStream(
"D:/test/test.txt");
// 創建一個長度為1024的竹筒
byte[] b = new byte[1024];
// 用于保存的實際字節數
int hasRead = 0;
// 使用循環來重復取水的過程
while ((hasRead = fis.read(b)) > 0) {
// 取出竹筒中的水滴(字節),將字節數組轉換成字符串進行輸出, 后面的gbk是為了處理中文亂碼問題
System.out.print(new String(b, 0, hasRead, "gbk"));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
fis.close();
}
}
注:上面程序最后使用了fis.close()來關閉該文件的輸入流,與JDBC編程一樣,程序里面打開的文件IO資源不屬于內存的資源,垃圾回收機制無法回收該資源,所以應該顯示的關閉打開的IO資源。Java 7改寫了所有的IO資源類,它們都實現了AntoCloseable接口,因此都可以通過自動關閉資源的try語句來關閉這些IO流。
使用FileReader讀取文件:
@Test
public void fileReaderTest() throws IOException{
FileReader fis = null;
try {
// 創建字節輸入流
fis = new FileReader("D:/test/test.txt");
// 創建一個長度為1024的竹筒
char[] b = new char[1024];
// 用于保存的實際字節數
int hasRead = 0;
// 使用循環來重復取水的過程
while ((hasRead = fis.read(b)) > 0) {
// 取出竹筒中的水滴(字節),將字節數組轉換成字符串進行輸出
System.out.print(new String(b, 0, hasRead));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
fis.close();
}
}
可以看出使用FileInputStream和FileReader進行文件的讀寫并沒有什么區別,只是操作單元不同而且。
FileOutputStream的用法:
@Test
public void fileOutputStreamTest() throws IOException {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
// 創建字節輸入流
fis = new FileInputStream("D:/test/test.txt");
// 創建字節輸出流
fos = new FileOutputStream("D:/test/test2.txt");
byte[] b = new byte[1024];
int hasRead = 0;
// 循環從輸入流中取出數據
while ((hasRead = fis.read(b)) > 0) {
// 每讀取一次,即寫入文件輸入流,讀了多少,就寫多少。
fos.write(b, 0, hasRead);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
fis.close();
fos.close();
}
}
注: 使用java的IO流執行輸出時,不要忘記關閉輸出流,關閉輸出流除了可以保證流的物理資源被回收之外,可能還可以將輸出流緩沖區中的數據flush到物理節點中里(因為在執行close()方法之前,自動執行輸出流的flush()方法)。java很多輸出流默認都提供了緩存功能,其實我們沒有必要刻意去記憶哪些流有緩存功能,哪些流沒有,只有正常關閉所有的輸出流即可保證程序正常。
4.緩沖流的使用(BufferedInputStream/BufferedReader, BufferedOutputStream/BufferedWriter):
字節緩沖流
@Test
public void bufferedTest() throws IOException {
FileInputStream fis = null;
FileOutputStream fos = null;
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
// 創建字節輸入流
fis = new FileInputStream("D:/test/test.txt");
// 創建字節輸出流
fos = new FileOutputStream("D:/test/test3.txt");
// 創建字節緩存輸入流
bis = new BufferedInputStream(fis);
// 創建字節緩存輸出流
bos = new BufferedOutputStream(fos);
byte[] b = new byte[1024];
int hasRead = 0;
// 循環從緩存流中讀取數據
while ((hasRead = bis.read(b)) > 0) {
// 向緩存流中寫入數據,讀取多少寫入多少
bos.write(b, 0, hasRead);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
bis.close();
bos.close();
}
}
可以看到使用字節緩存流讀取和寫入數據的方式和文件流(FileInputStream,FileOutputStream)并沒有什么不同,只是把處理流套接到文件流上進行讀寫。
上面代碼中我們使用了緩存流和文件流,但是我們只關閉了緩存流。這個需要注意一下,當我們使用處理流套接到節點流上的使用的時候,只需要關閉最上層的處理就可以了。java會自動幫我們關閉下層的節點流。
2.3 轉換流的使用(InputStreamReader/OutputStreamWriter):
下面以獲取鍵盤輸入為例來介紹轉換流的用法。java使用System.in代表輸入。即鍵盤輸入,但這個標準輸入流是InputStream類的實例,使用不太方便,而且鍵盤輸入內容都是文本內容,所以可以使用InputStreamReader將其包裝成BufferedReader,利用BufferedReader的readLine()方法可以一次讀取一行內容,如下代碼所示:
轉換流的使用
@Test
public void changeStreamTest(){
try {
// 將System.in對象轉化為Reader對象
InputStreamReader reader=new InputStreamReader(System.in);
//將普通的Reader包裝成BufferedReader
BufferedReader bufferedReader=new BufferedReader(reader);
String buffer=null;
while ((buffer=bufferedReader.readLine())!=null){
// 如果讀取到的字符串為“exit”,則程序退出
if(buffer.equals("exit")){
System.exit(1);
}
//打印讀取的內容
System.out.print("輸入內容:"+buffer);
}
}catch (IOException e){
e.printStackTrace();
}finally {
}
}
上面程序將System.in包裝成BufferedReader,BufferedReader流具有緩存功能,它可以一次讀取一行文本——以換行符為標志,如果它沒有讀到換行符,則程序堵塞。等到讀到換行符為止。運行上面程序可以發現這個特征,當我們在控制臺執行輸入時,只有按下回車鍵,程序才會打印出剛剛輸入的內容。
2.4 對象流的使用(ObjectInputStream/ObjectOutputStream)的使用:
對象類:
package com.design.prompt;
import java.io.Serializable;
public class UserTest implements Serializable {
/**
*
*/
private static final long serialVersionUID = 6522988290199824802L;
private String username;
transient String password;
public UserTest(){
}
public UserTest(String username, String password) {
super();
this.username = username;
this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "UserTest [username=" + username + ", password=" + password
+ "]";
}
}
寫出對象:
@Test
public void objectWriterTest() {
OutputStream outputStream = null;
BufferedOutputStream buf = null;
ObjectOutputStream obj = null;
try {
// 序列化文件輸出流
outputStream = new FileOutputStream("D:/test/test.txt");
// 構建緩沖流
buf = new BufferedOutputStream(outputStream);
// 構建字符輸出的對象流
obj = new ObjectOutputStream(buf);
// 序列化數據寫入
obj.writeObject(new UserTest("A", "123"));// Person對象
// 關閉流
obj.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
讀入對象
@Test
public void objectReaderTest() {
try {
InputStream inputStream = new FileInputStream("D:/test/test.txt");
// 構建緩沖流
BufferedInputStream buf = new BufferedInputStream(inputStream);
// 構建字符輸入的對象流
ObjectInputStream obj = new ObjectInputStream(buf);
UserTest tempPerson = (UserTest) obj.readObject();
System.out.println("UserTest對象為:" + tempPerson);
// 關閉流
obj.close();
buf.close();
inputStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
注:使用對象流的一些注意事項
1.讀取順序和寫入順序一定要一致,不然會讀取出錯。
2.在對象屬性前面加transient關鍵字,則該對象的屬性不會被序列化。
總結
以上是生活随笔為你收集整理的java按照io流向基类_Java IO详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python rpa_(RPA学习)Py
- 下一篇: python自动输入账号密码_Pytho