I/O的方式通常分為幾種,同步阻塞的BIO、同步非阻塞的NIO犁罩、異步非阻塞的AIO偿凭。
基于不同的IO抽象模型和交互方式产弹,可以對IO進(jìn)行簡單區(qū)分:
* 傳統(tǒng)的IO基于流模型實(shí)現(xiàn),交互方式是同步弯囊、阻塞的方式痰哨,也就是說胶果,在讀取輸入流或者寫入輸出流時(shí),在讀斤斧、寫動(dòng)作完成之前早抠,線程會一直阻塞在那里,它們之間的調(diào)用是可靠的線性順序折欠。也稱為BIO贝或。
* 在java1.4中引入了NIO框架,提供了Channel锐秦、Selector咪奖、Buffer等新的抽象,可以構(gòu)建多路復(fù)用的酱床、同步非阻塞IO羊赵。
* 在Java7中,NIO有了進(jìn)一步的改進(jìn)扇谣,也就是NIO2昧捷,引入了異步非阻塞IO方式,也叫AIO(Asynchronous IO)罐寨。異步IO操作基于事件和回調(diào)機(jī)制靡挥,可以簡單理解為,應(yīng)用操作直接返回鸯绿,而不會阻塞在那里跋破,當(dāng)后臺處理完成,操作系統(tǒng)會通知相應(yīng)線程進(jìn)行后續(xù)工作瓶蝴。
BIO
在JDK1.4出來之前毒返,我們建立網(wǎng)絡(luò)連接的時(shí)候采用BIO模式,需要先在服務(wù)端啟動(dòng)一個(gè)ServerSocket舷手,然后在客戶端啟動(dòng)Socket來對服務(wù)端進(jìn)行通信拧簸,默認(rèn)情況下服務(wù)端需要對每個(gè)請求建立一堆線程等待請求,而客戶端發(fā)送請求后男窟,先咨詢服務(wù)端是否有線程相應(yīng)盆赤,如果沒有則會一直等待或者遭到拒絕請求,如果有的話蝎宇,客戶端會線程會等待請求結(jié)束后才繼續(xù)執(zhí)行弟劲。
IO流的分類
根據(jù)操作對象可分為:
磁盤操作:File
字節(jié)操作:InputStream 和OutputStream
字符操作:Reader 和Writer
對象操作:Serializable
網(wǎng)絡(luò)操作:Socket
根據(jù)處理數(shù)據(jù)類型的不同分為:字符流和字節(jié)流,字節(jié)流一次讀入或讀出是8位二進(jìn)制姥芥,字符流一次讀入或讀出是16位二進(jìn)制兔乞。
根據(jù)數(shù)據(jù)流向不同分為:輸入流和輸出流,輸入流只能進(jìn)行讀操作,輸出流只能進(jìn)行寫操作庸追。
結(jié)論:只要是處理純文本數(shù)據(jù)霍骄,就優(yōu)先考慮使用字符流。 除此之外都使用字節(jié)流淡溯。
按照流是否直接與特定的地方(如磁盤读整、內(nèi)存、設(shè)備等)相連咱娶,分為節(jié)點(diǎn)流和處理流兩類:
1)節(jié)點(diǎn)流:直接與數(shù)據(jù)源相連米间,讀入或讀出。
常用節(jié)點(diǎn)流:
父 類:InputStream 膘侮、OutputStream屈糊、 Reader、Writer
文 件:FileInputStream 琼了、FileOutputStrean 逻锐、FileReader 、FileWriter 文件進(jìn)行處理的節(jié)點(diǎn)流
數(shù) 組:ByteArrayInputStream雕薪、ByteArrayOutputStream昧诱、 CharArrayReader 、CharArrayWriter對數(shù)組進(jìn)行處理的節(jié)點(diǎn)流(對應(yīng)的不再是文件所袁,而是內(nèi)存中的一個(gè)數(shù)組)
字符串:StringReader盏档、 StringWriter 對字符串進(jìn)行處理的節(jié)點(diǎn)流
管 道:PipedInputStream 、PipedOutputStream燥爷、PipedReader 妆丘、PipedWriter對管道進(jìn)行處理的節(jié)點(diǎn)流
2)處理流:直接使用節(jié)點(diǎn)流,讀寫不方便局劲,為了更快的讀寫文件,才有了處理流奶赠。處理流和節(jié)點(diǎn)流一塊使用鱼填,在節(jié)點(diǎn)流的基礎(chǔ)上,再套接一層毅戈,套接在節(jié)點(diǎn)流上的就是處理流苹丸。
常用處理流:
緩沖流:BufferedInputStrean 、BufferedOutputStream苇经、 BufferedReader赘理、BufferedWriter增加緩沖功能,避免頻繁讀寫硬盤扇单∩棠#可修飾所有從節(jié)點(diǎn)流父類繼承的對應(yīng)節(jié)點(diǎn)流。
轉(zhuǎn)換流:InputStreamReader 、OutputStreamReader實(shí)現(xiàn)字節(jié)流和字符流之間的轉(zhuǎn)換施流。
數(shù)據(jù)流:DataInputStream 响疚、DataOutputStream等-提供將基礎(chǔ)數(shù)據(jù)類型寫入到文件中,或者讀取出來瞪醋。
什么時(shí)候使用轉(zhuǎn)換流忿晕?
1)源或者目的對應(yīng)的設(shè)備是字節(jié)流,但是操作的卻是文本數(shù)據(jù)银受,可以使用轉(zhuǎn)換作為橋梁践盼。提高對文本操作的便捷。
2)一旦操作文本涉及到具體的指定編碼表時(shí)宾巍,必須使用轉(zhuǎn)換流咕幻。
IO流的交互方式是同步、阻塞的方式蜀漆,也就是說谅河,在讀取輸入流或者寫入輸出流時(shí),在讀确丢、寫動(dòng)作完成之前绷耍,線程會一直阻塞在那里,他們之間的調(diào)用是可靠的線性順序鲜侥。
BIO常見操作示例:
1)磁盤操作
File 類可以用于表示文件和目錄的信息褂始,但是它不表示文件的內(nèi)容。
遞歸地列出一個(gè)目錄下所有文件:
public static void listAllFiles(File dir) {
??? if (dir == null ||!dir.exists()) {
??????? return;
??? }
??? if (dir.isFile()) {
???????System.out.println(dir.getName());
??????? return;
??? }
??? for (File file :dir.listFiles()) {
??????? listAllFiles(file);
??? }
}
從 Java7 開始描函,可以使用 Paths 和 Files 代替 File崎苗。
2)字節(jié)操作
實(shí)現(xiàn)文件復(fù)制
public static void copyFile(String src, String dist) throwsIOException {
??? FileInputStream in = newFileInputStream(src);
??? FileOutputStream out =new FileOutputStream(dist);
? ? byte[] buffer = newbyte[20 * 1024];
??? int cnt;
??? // read()最多讀取 buffer.length 個(gè)字節(jié)
??? //返回的是實(shí)際讀取的個(gè)數(shù)
??? //返回 -1 的時(shí)候表示讀到 eof,即文件尾
??? while ((cnt =in.read(buffer, 0, buffer.length)) != -1) {
??????? out.write(buffer, 0,cnt);
??? }
??? in.close();
??? out.close();
}
裝飾者模式
Java I/O 使用了裝飾者模式來實(shí)現(xiàn)舀寓。以 InputStream 為例胆数,
InputStream 是抽象組件;
FileInputStream 是 InputStream 的子類互墓,屬于具體組件必尼,提供了字節(jié)流的輸入操作;
FilterInputStream 屬于抽象裝飾者篡撵,裝飾者用于裝飾組件判莉,為組件提供額外的功能。例如 BufferedInputStream 為 FileInputStream 提供緩存的功能育谬。
實(shí)例化一個(gè)具有緩存功能的字節(jié)流對象時(shí)券盅,只需要在 FileInputStream 對象上再套一層 BufferedInputStream 對象即可。
FileInputStream fileInputStream = new FileInputStream(filePath);
BufferedInputStream bufferedInputStream = newBufferedInputStream(fileInputStream);
DataInputStream 裝飾者提供了對更多數(shù)據(jù)類型進(jìn)行輸入的操作膛檀,比如 int锰镀、double 等基本類型娘侍。
3)字符操作
編碼與解碼
編碼就是把字符轉(zhuǎn)換為字節(jié),而解碼是把字節(jié)重新組合成字符互站。
如果編碼和解碼過程使用不同的編碼方式那么就出現(xiàn)了亂碼私蕾。
GBK 編碼中,中文字符占 2 個(gè)字節(jié)胡桃,英文字符占 1 個(gè)字節(jié)踩叭;
UTF-8 編碼中,中文字符占 3 個(gè)字節(jié)翠胰,英文字符占 1 個(gè)字節(jié)容贝;
UTF-16be 編碼中,中文字符和英文字符都占 2 個(gè)字節(jié)之景。
UTF-16be 中的 be 指的是 Big Endian斤富,也就是大端。相應(yīng)地也有 UTF-16le锻狗,le 指的是 Little Endian满力,也就是小端。
Java 的內(nèi)存編碼使用雙字節(jié)編碼 UTF-16be轻纪,這不是指 Java 只支持這一種編碼方式油额,而是說 char 這種類型使用 UTF-16be 進(jìn)行編碼。char 類型占 16 位刻帚,也就是兩個(gè)字節(jié)潦嘶,Java 使用這種雙字節(jié)編碼是為了讓一個(gè)中文或者一個(gè)英文都能使用一個(gè) char 來存儲。
String 的編碼方式
String 可以看成一個(gè)字符序列崇众,可以指定一個(gè)編碼方式將它編碼為字節(jié)序列掂僵,也可以指定一個(gè)編碼方式將一個(gè)字節(jié)序列解碼為 String。
String str1 = "中文";
byte[] bytes = str1.getBytes("UTF-8");
String str2 = new String(bytes, "UTF-8");
System.out.println(str2);
在調(diào)用無參數(shù) getBytes() 方法時(shí)顷歌,默認(rèn)的編碼方式不是 UTF-16be锰蓬。雙字節(jié)編碼的好處是可以使用一個(gè) char 存儲中文和英文,而將 String 轉(zhuǎn)為 bytes[] 字節(jié)數(shù)組就不再需要這個(gè)好處眯漩,因此也就不再需要雙字節(jié)編碼互妓。getBytes() 的默認(rèn)編碼方式與平臺有關(guān),一般為 UTF-8坤塞。
byte[] bytes = str1.getBytes();
Reader 與Writer
不管是磁盤還是網(wǎng)絡(luò)傳輸,最小的存儲單元都是字節(jié)澈蚌,而不是字符摹芙。但是在程序中操作的通常是字符形式的數(shù)據(jù),因此需要提供對字符進(jìn)行操作的方法宛瞄。
InputStreamReader實(shí)現(xiàn)從字節(jié)流解碼成字符流浮禾;
OutputStreamWriter實(shí)現(xiàn)字符流編碼成為字節(jié)流交胚。
實(shí)現(xiàn)逐行輸出文本文件的內(nèi)容
public static void readFileContent(String filePath) throwsIOException {
??? FileReader fileReader =new FileReader(filePath);
??? BufferedReaderbufferedReader = new BufferedReader(fileReader);
??? String line;
??? while ((line =bufferedReader.readLine()) != null) {
???????System.out.println(line);
??? }
??? //裝飾者模式使得 BufferedReader 組合了一個(gè) Reader 對象
??? //在調(diào)用 BufferedReader 的 close() 方法時(shí)會去調(diào)用 Reader 的 close() 方法
??? //因此只要一個(gè) close() 調(diào)用即可
??? bufferedReader.close();
}
4)對象操作
序列化
序列化就是將一個(gè)對象轉(zhuǎn)換成字節(jié)序列,方便存儲和傳輸盈电。
序列化:ObjectOutputStream.writeObject()
反序列化:ObjectInputStream.readObject()
不會對靜態(tài)變量進(jìn)行序列化蝴簇,因?yàn)樾蛄谢皇潜4鎸ο蟮臓顟B(tài),靜態(tài)變量屬于類的狀態(tài)匆帚。
Serializable
序列化的類需要實(shí)現(xiàn) Serializable 接口熬词,它只是一個(gè)標(biāo)準(zhǔn),沒有任何方法需要實(shí)現(xiàn)吸重,但是如果不去實(shí)現(xiàn)它的話而進(jìn)行序列化互拾,會拋出異常。
public static void main(String[] args) throws IOException,ClassNotFoundException {
??? A a1 = new A(123,"abc");
? ??String objectFile = "file/a1";
??? ObjectOutputStreamobjectOutputStream = new ObjectOutputStream(new FileOutputStream(objectFile));
???objectOutputStream.writeObject(a1);
???objectOutputStream.close();
??? ObjectInputStreamobjectInputStream = new ObjectInputStream(new FileInputStream(objectFile));
??? A a2 = (A)objectInputStream.readObject();
???objectInputStream.close();
??? System.out.println(a2);
}
private static class A implements Serializable {
??? private int x;
??? private String y;
??? A(int x, String y) {
??????? this.x = x;
??????? this.y = y;
??? }
??? @Override
??? public String toString(){
??????? return "x =" + x + "? " + "y =" + y;
??? }
}
transient
transient 關(guān)鍵字可以使一些屬性不會被序列化嚎幸。
ArrayList 中存儲數(shù)據(jù)的數(shù)組 elementData 是用 transient 修飾的颜矿,因?yàn)檫@個(gè)數(shù)組是動(dòng)態(tài)擴(kuò)展的,并不是所有的空間都被使用嫉晶,因此就不需要所有的內(nèi)容都被序列化骑疆。通過重寫序列化和反序列化方法,使得可以只序列化數(shù)組中有內(nèi)容的那部分?jǐn)?shù)據(jù)替废。
private transient Object[] elementData;
5)網(wǎng)絡(luò)操作
Java中的網(wǎng)絡(luò)支持:
InetAddress:用于表示網(wǎng)絡(luò)上的硬件資源箍铭,即IP地址;
URL:統(tǒng)一資源定位符舶担;
Sockets:使用TCP協(xié)議實(shí)現(xiàn)網(wǎng)絡(luò)通信坡疼;
Datagram:使用UDP協(xié)議實(shí)現(xiàn)網(wǎng)絡(luò)通信。
InetAddress
沒有公有的構(gòu)造函數(shù)衣陶,只能通過靜態(tài)方法來創(chuàng)建實(shí)例柄瑰。
InetAddress.getByName(String host);
InetAddress.getByAddress(byte[] address);
URL
可以直接從 URL 中讀取字節(jié)流數(shù)據(jù)。
public static void main(String[] args) throws IOException {
??? URL url = newURL("http://www.baidu.com");
? ? /*字節(jié)流*/
??? InputStream is =url.openStream();
? ? /*字符流*/
??? InputStreamReader isr =new InputStreamReader(is, "utf-8");
? ? /*提供緩存功能*/
??? BufferedReader br = newBufferedReader(isr);
??? String line;
??? while ((line =br.readLine()) != null) {
??????? System.out.println(line);
??? }
? ? br.close();
}
Sockets
ServerSocket:服務(wù)器端類
Socket:客戶端類
服務(wù)器和客戶端通過 InputStream 和 OutputStream 進(jìn)行輸入輸出剪况。
Datagram
DatagramSocket:通信類
DatagramPacket:數(shù)據(jù)包類