IO是計算機中Input和Output簡稱,即輸入和輸出。 無論是系統(tǒng)、還是語言的設(shè)計中IO的設(shè)計都是異常復(fù)雜的。JAVA語言在IO設(shè)計方面是比較成功的,不僅是面向?qū)ο螅依醚b飾器設(shè)計模式(后面會寫針對設(shè)計模式的文章)減少了大量的類,提供了較好的擴展性。
那Java IO怎么寫入/讀取數(shù)據(jù)?
Java IO類庫可以分為輸入流和輸出流,輸入流來讀數(shù)據(jù),輸出流來寫數(shù)據(jù)。
輸出流
實例代碼一:
//輸出流
public static void outputStream() throws IOException{ //創(chuàng)建一個File實例 File file = new File("/home/wenhaibo/IOTest.txt");
//FileOutputStream為文件輸出流 FileOutputStream out = new FileOutputStream(file); //將內(nèi)容轉(zhuǎn)換為字節(jié)碼輸出 out.write("This is IOTest".getBytes());
//強制輸出內(nèi)存中所有內(nèi)容 out.flush();
//關(guān)閉輸出流 out.close();
}
輸入流
實例代碼二:
//輸入流
public static void inputStream() throws IOException{ //創(chuàng)建一個File實例 File file = new File("/home/wenhaibo/IOTest.txt");
//FileInputStream為文件輸入流 FileInputStream in = new FileInputStream(file);
byte[] b = new byte[1024];
//將 byte.length 個字節(jié)的數(shù)據(jù)讀入一個 byte 數(shù)組中
int len =in.read(b);
//將字節(jié)碼轉(zhuǎn)為字符串打印輸出 System.out.println(new String(b, 0, len));
//關(guān)閉輸入流 in.close();
}
字節(jié)流和字符流
上面的實例中用的字節(jié)流方式寫入/讀取數(shù)據(jù),Java IO不僅提供字節(jié)流方式還通過了字符流處理方式,處理字符流用Reader、Writer兩個專門操作字符流的類。
Writer
實例代碼三:
//字符流
public static void outputStreamWriter() throws IOException{ //創(chuàng)建一個File實例 File file = new File("/home/wenhaibo/IOTest.txt");
//FileWriter為文件輸出流 Writer out = new FileWriter(file); //直接輸出字符 out.write("This is IOTest");
//強制輸出內(nèi)存中所有內(nèi)容 out.flush();
//關(guān)閉輸出流 out.close();
}
Reader
實例代碼四:
public static void inputStreamReader() throws IOException{
//創(chuàng)建一個File實例 File file = new File("/home/wenhaibo/IOTest.txt");
//Reader為文件輸入流 Reader in=new FileReader(file);
char[] c=new char[1024];
//將 byte.length 個字節(jié)的數(shù)據(jù)讀入一個 byte 數(shù)組中
int len =in.read(c);
//將字節(jié)碼轉(zhuǎn)為字符串打印輸出 System.out.println(new String(c, 0, len));
//關(guān)閉輸入流 in.close();
}
緩沖流
Java緩沖流是在輸入流和輸出流之上進(jìn)行了一次包裝(裝飾器設(shè)計模式),目的是解決頻繁寫入/讀取數(shù)據(jù)時效率差的問題。緩沖流先將數(shù)據(jù)緩存起來,然后一起寫入或讀取出來。
字節(jié)緩沖流類:BufferedInputStream 和 BufferedOutputStream
字符緩沖流類:BufferedReader 和 BufferedWriter
flush()方法
當(dāng)向文件寫入數(shù)據(jù)時是先將輸出流寫入到緩沖區(qū),當(dāng)緩沖區(qū)寫滿后才將緩沖區(qū)的內(nèi)容輸出到文件中。但是當(dāng)主機完成輸出流的輸出后,有可能緩沖區(qū)這個時候還沒有被填滿,這樣的話,就會一直等待主機發(fā)送內(nèi)容,這時候,就可以使用flush()方法將緩沖區(qū)的內(nèi)容強制輸出到文件中,清空緩沖區(qū)。
所以,一般在關(guān)閉輸出流之前,要先調(diào)用flush()方法強制緩沖區(qū)中的內(nèi)容輸出,并清空緩沖區(qū)。
Java NIO的出現(xiàn)
因為傳統(tǒng)的IO是阻塞而且低效的,JDK 1.4 提供了NIO(New IO)API。
那傳統(tǒng)的IO和NIO有哪些不同?
面向流與面向緩沖
傳統(tǒng)IO處理數(shù)據(jù)就像“小雞啄米”,而NIO則是“狼吞虎咽”。
NIO中引入了緩沖區(qū)的概念,緩沖區(qū)作為傳輸數(shù)據(jù)的基本單位塊,所有對數(shù)據(jù)的操作都是基于將數(shù)據(jù)移進(jìn)/移出緩沖區(qū)而來;讀數(shù)據(jù)的時候從緩沖區(qū)中取,寫的時候?qū)?shù)據(jù)填入緩沖區(qū)。盡管傳統(tǒng)JavaIO中也有相應(yīng)的緩沖區(qū)過濾器流(BufferedInputStream等),但是移進(jìn)/移出的操作是由程序員來包裝的,它本質(zhì)是對數(shù)據(jù)結(jié)構(gòu)化和積累達(dá)到處理時的方便,并不是一種提高I/O效率的措施。NIO的緩沖區(qū)則不然,對緩沖區(qū)的移進(jìn)/移出操作是由底層操作系統(tǒng)來實現(xiàn)的。
除了效率上的差別外,緩沖區(qū)在數(shù)據(jù)分析和處理上也帶來的很大的便利和靈活性。
阻塞與非阻塞IO
傳統(tǒng)JavaIO是基于阻塞I/O模型。這意味著,當(dāng)一個線程調(diào)用read() 或 write()時,該線程被阻塞,直到有一些數(shù)據(jù)被讀取,或數(shù)據(jù)完全寫入。該線程在此期間不能再干任何事情了。 Java NIO的非阻塞模式,使一個線程從某通道發(fā)送請求讀取數(shù)據(jù),但是它僅能得到目前可用的數(shù)據(jù),如果目前沒有數(shù)據(jù)可用時,就什么都不會獲取。而不是保持線程阻塞,所以直至數(shù)據(jù)變的可以讀取之前,該線程可以繼續(xù)做其他的事情。 非阻塞寫也是如此。一個線程請求寫入一些數(shù)據(jù)到某通道,但不需要等待它完全寫入,這個線程同時可以去做別的事情。 線程通常將非阻塞IO的空閑時間用于在其它通道上執(zhí)行IO操作,所以一個單獨的線程現(xiàn)在可以管理多個輸入和輸出通道(channel)。
Java NIO 核心API
Java NIO 中Channel,Buffer 和 Selector 構(gòu)成了核心的API。
Channel
Channel(通道)和IO中的流是差不多一個等級的。只不過流是單向的,譬如:InputStream, OutputStream.而Channel是雙向的,既可以用來進(jìn)行讀操作,又可以用來進(jìn)行寫操作。
NIO中的Channel的主要實現(xiàn)有:
- FileChannel
- DatagramChannel
- SocketChannel
- ServerSocketChannel
分別可以對應(yīng)文件IO、UDP和TCP(Server和Client)。
Buffer
NIO中的關(guān)鍵Buffer實現(xiàn)有:ByteBuffer, CharBuffer, DoubleBuffer, FloatBuffer, IntBuffer, LongBuffer, ShortBuffer,分別對應(yīng)基本數(shù)據(jù)類型: byte, char, double, float, int, long, short。
選擇器(Selectors)
Java NIO的選擇器允許一個單獨的線程來監(jiān)視多個輸入通道,你可以注冊多個通道使用一個選擇器,然后使用一個單獨的線程來“選擇”通道:這些通道里已經(jīng)有可以處理的輸入,或者選擇已準(zhǔn)備寫入的通道。這種選擇機制,使得一個單獨的線程很容易來管理多個通道。
實例代碼五:
//使用NOI輸出
public static void inputNIOChannel() throws IOException { //創(chuàng)建一個File實例 File file = new File("/home/wenhaibo/IOTest.txt");
//FileInputStream為文件輸入流 FileInputStream in = new FileInputStream(file);
//緩沖器向通道輸入數(shù)據(jù) FileChannel fileChannel = in.getChannel();
//創(chuàng)建一個容量為1024字節(jié)的ByteBuffer
ByteBuffer buf = ByteBuffer.allocate(1024);
//寫入數(shù)據(jù)到Buffer int bytesRead = fileChannel.read(buf);
while(bytesRead != -1)
{ //回繞緩沖區(qū)(輸出通道會從數(shù)據(jù)的開頭而不是末尾開始) buf.flip(); while(buf.hasRemaining())
{ System.out.print((char)buf.get());
} /** * 壓縮此緩沖區(qū),compact方法會執(zhí)行兩個動作 * 1.清除之前寫好的字符
* 2.通過標(biāo)記位置為0
* 這就為什么要結(jié)合filp()使用 */ buf.compact(); //寫入數(shù)據(jù)到Buffer bytesRead = fileChannel.read(buf);
} }
實際應(yīng)用中在不考慮并發(fā)和讀取/寫入數(shù)據(jù)使用頻率比較高的情況下Java IO已經(jīng)可以勝任,但在使用到網(wǎng)絡(luò)IO中Java IO已經(jīng)不能滿足實際需求,Java NIO 無疑是更好的選擇。