Java的IO和NIO
一泞辐、Java的IO
Java的IO功能在java.io
包下毅贮,包括輸入、輸出兩種IO流驾凶,每種輸入牙甫、輸出流又可分為字節(jié)流和字符流兩大類。字節(jié)流以字節(jié)(8位)為單位處理輸入輸出调违,字符流以字符(16位)為單位來處理輸入輸出窟哺。
1.1 流的分類
- 輸入輸出流
Java的輸入流主要有InputStream
和Reader
作為基類,而輸出流主要由OutputStream
和Writer
作為基類技肩。他們是一些抽象類且轨,無法直接創(chuàng)建實(shí)例。
- 字節(jié)流和字符流
處理字節(jié)流:InputStream虚婿、OutputStream旋奢;
處理字符流:Reader、Writer然痊。
- 節(jié)點(diǎn)流和處理流
節(jié)點(diǎn)流:可以直接從IO設(shè)備(如磁盤至朗、網(wǎng)絡(luò))讀寫數(shù)據(jù)的流稱為節(jié)點(diǎn)流,也被稱為低級流剧浸,比如锹引,處理文件的輸入流:FileInputStream和FileReader;
處理流:對一個已存在的流進(jìn)行連接和封裝唆香,通過封裝后的流來實(shí)現(xiàn)數(shù)據(jù)讀寫功能嫌变,也被稱為高級流,比如PrintStream躬它。
當(dāng)使用節(jié)點(diǎn)流時腾啥,程序可以直接連接到實(shí)際的數(shù)據(jù)源。當(dāng)使用處理流時冯吓,程序并不會直接連接到實(shí)際的數(shù)據(jù)源倘待。
使用處理流的好處就是,只要使用相同的處理流桑谍,程序就可以采用完全相同的輸入輸出代碼來訪問不同的數(shù)據(jù)源延柠,隨著處理流所包裝節(jié)點(diǎn)流的變化,程序?qū)嶋H訪問的數(shù)據(jù)源也會相應(yīng)的發(fā)生改變锣披。實(shí)際上這是一種裝飾者模式贞间,處理流也被稱為包裝流贿条。通過使用處理流,Java程序無須理會輸入輸出節(jié)點(diǎn)是磁盤還是網(wǎng)絡(luò)增热,還是其他設(shè)備整以,程序只要將這些節(jié)點(diǎn)流包裝成處理流,就可以使用相同的輸入輸出代碼來讀寫不同設(shè)備上的數(shù)據(jù)峻仇。
識別處理流很簡單公黑,主要流的構(gòu)造參數(shù)不是一個物理節(jié)點(diǎn),而是已經(jīng)存在的流摄咆;而節(jié)點(diǎn)流都是直接以物理IO節(jié)點(diǎn)作為構(gòu)造器參數(shù)的凡蚜。比如FileInputStream的構(gòu)造器是FileInputStream(String name)
和
FileInputStream(File file),那么它就是一個節(jié)點(diǎn)流吭从,再比如PrintStream的構(gòu)造器是PrintStream(OutputStream out)
朝蜘,那么它就是一個處理流。
使用處理流的優(yōu)點(diǎn)
- 對開發(fā)人員來講涩金,使用處理流進(jìn)行輸入輸出操作更簡單谱醇;
- 使用處理流的執(zhí)行效率更高。
使用節(jié)點(diǎn)流的例子:
package com.wangjun.othersOfJava;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
/*
* 節(jié)點(diǎn)流演示
*/
public class FileInputStreamTest {
public static void main(String[] args) throws IOException {
//讀取文件
FileInputStream fis = new FileInputStream("README.md");
//寫入文件
FileOutputStream fos = new FileOutputStream(new File("test.md"));
//定義每次讀取的字節(jié)數(shù)步做,保存在b中
byte[] b = new byte[12];
//read方法返回實(shí)際讀取的字節(jié)數(shù)
int hasRead = 0;
while((hasRead = fis.read(b)) > 0) {
System.out.println("hasRead:" + hasRead);
System.out.println(new String(b, 0, hasRead));
fos.write(b, 0, hasRead);
}
fis.close();
}
}
使用處理流的例子:
package com.wangjun.othersOfJava;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
/*
* 處理流演示
* 程序使用了PrintStream包裝了FileOutputStream節(jié)點(diǎn)流輸出流副渴,使用println輸入字符和對象
* PrintStream輸出功能非常強(qiáng)大,我們平時常用的System.out.println()就是使用的PrintStream
*/
public class PrintStreamTest {
public static void main(String[] args) throws FileNotFoundException {
FileOutputStream fos = new FileOutputStream("print.md");
PrintStream ps = new PrintStream(fos);
ps.println("test1");
ps.println("test2");
ps.println(new PrintStreamTest());
}
}
1.2 Java的IO體系
Java的IO體系提供了將近40個類全度。
下面將常用的一些類做一下分類匯總煮剧。其中粗體的為處理流。
分類 | 字節(jié)輸入流 | 字節(jié)輸出流 | 字符輸入流 | 字符輸出流 |
---|---|---|---|---|
基類 | InputStream | OutputStream | Reader | Writer |
文件 | FileInputStream | FileOutputStream | FileReader | FileWriter |
數(shù)組 | ByteArrayInputStream | ByteArrayOutputStream | CharArrayReader | CharArrayWriter |
字符串 | StringReader | StringWriter | ||
管道 | PipedInputStream | PipedOutputStream | PipedReader | PipedWriter |
緩沖流 | BufferedInputStream | BufferedOutputStream | BufferedReader | BufferedWriter |
轉(zhuǎn)換流 | InputStreamReader | OutputStreamWriter | ||
對象流 | ObjectInputStream | ObjectOutputStream | ||
打印流 | PrintStream | PrintWriter | ||
推回輸入流 | PushbackInputStream | PushbackReader |
通常來說将鸵,我們認(rèn)為字節(jié)流的功能比字符流的功能強(qiáng)大轿秧,因?yàn)橛?jì)算機(jī)里所有的數(shù)據(jù)都是二進(jìn)制的,而字節(jié)流可以處理所有的二進(jìn)制文件咨堤,但問題就是如果使用字節(jié)流處理文本文件,則需要使用合適的方式將這些字節(jié)轉(zhuǎn)換成字符漩符,這就增加了編程的復(fù)雜度一喘。
所以通常有這么一個規(guī)則:如果進(jìn)行輸入輸出的內(nèi)容是文本內(nèi)容,則應(yīng)考慮使用字符流嗜暴;如果進(jìn)行輸入輸出的內(nèi)容是二進(jìn)制內(nèi)容凸克,則應(yīng)該考慮用字節(jié)流。
注意1:我們一般把計(jì)算機(jī)的文件分為文本文件和二進(jìn)制文件闷沥,其實(shí)計(jì)算機(jī)所有的文件都是二進(jìn)制文件萎战,當(dāng)二進(jìn)制文件內(nèi)容恰好能被正常解析成字符時,則該二進(jìn)制文件就變成了文本文件舆逃。
注意2:上面的表格中列舉了4種訪問管道的流蚂维,它們都是用于實(shí)現(xiàn)進(jìn)程之間通信功能的戳粒。
1.3 轉(zhuǎn)換流
上面的表格中列舉了java提供的兩個轉(zhuǎn)換流。InputStreamReader和OutputStreamWriter用于將字節(jié)流轉(zhuǎn)換成字符流虫啥。為什么要這么做呢蔚约?前面提到了字節(jié)流的功能比較強(qiáng)大,字符流使用起來方便涂籽,如果有一個字節(jié)流內(nèi)容都是文本內(nèi)容苹祟,那么將它轉(zhuǎn)換成字符流更方便操作。
代碼實(shí)例
package com.wangjun.othersOfJava;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
/*
* 字節(jié)流轉(zhuǎn)字符流示例
* System.in是鍵盤輸入评雌,是InputStream(字節(jié)流)的實(shí)例树枫,因?yàn)殒I盤輸入都是字符,所以轉(zhuǎn)換成字符流操作更方便
* 普通的Reader讀取內(nèi)容時依然不方便景东,可以包裝成BufferedReader砂轻,利用BufferedReader的readLine()方法
* 可以一次讀取一行內(nèi)容
*/
public class InputStreamReaderTest {
public static void main(String[] args) throws IOException {
InputStreamReader isr = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(isr);
String buffer = null;
while((buffer = br.readLine()) != null) {
if(buffer.equals("exit")) {
System.out.println("bye bye!");
System.exit(1);
}
System.out.println("輸入內(nèi)容為:" + buffer);
}
}
}
BufferedReader具有一個readLine方法,可以非常方便的一次讀取一行內(nèi)容耐薯,所以經(jīng)常把讀取文本的輸出流包裝成BufferedReader舔清,用來方便的讀取輸入流中的文本內(nèi)容。
1.4 推回輸入流
在java的IO體系中提供了兩個特殊的流曲初,PushbackInputStream和PushbackReader体谒。它們提供了unread方法用于將字節(jié)/字符推回到推回緩沖區(qū)里面,從而允許讀取剛剛讀取的內(nèi)容臼婆。
它們帶有一個推回緩沖區(qū)抒痒,程序調(diào)用read方法時總是先從推回緩沖區(qū)里面讀取,只有完全讀取了推回緩沖區(qū)的內(nèi)容颁褂,并且還沒有裝滿read所需的數(shù)組時才會從原始輸入流中讀取故响。
代碼實(shí)例
package com.wangjun.othersOfJava;
import java.io.FileReader;
import java.io.IOException;
import java.io.PushbackReader;
/*
* 推回緩沖流測試
* 如果文件中包含LeetCode就把LeetCode周邊的字符打印出來
*/
public class PushbackReaderTest {
public static void main(String[] args) throws IOException {
//推回緩沖區(qū)的長度為64
PushbackReader pr = new PushbackReader(new FileReader("README.md"), 64);
char[] cbuf = new char[16];
int hasRead = 0;
String lastRead = "";
while((hasRead = pr.read(cbuf)) > 0) {
String str = new String(cbuf, 0, hasRead);
if((lastRead + str).contains("LeetCode")) {
//推回到緩沖區(qū)
pr.unread(cbuf, 0, hasRead);
//這一次會先讀取緩沖區(qū)的內(nèi)容
hasRead = pr.read(cbuf);
//打印字符
System.out.println(new String(cbuf, 0 ,hasRead));
System.out.println("lastRead:" + lastRead);
pr.close();
return;
}else {
lastRead = str;
}
}
pr.close();
}
}
1.5 重定向標(biāo)準(zhǔn)輸入輸出
Java的標(biāo)準(zhǔn)輸入輸出分別通過System.in和System.out來代表,默認(rèn)情況下他們代表鍵盤和顯示器颁独,在System類里面提供了3個重定向的方法:setError(PrintStream err)
彩届、setIn(IputStream in)
、setOut(PrintStream out)
誓酒。
代碼實(shí)例
package com.wangjun.othersOfJava;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.util.Scanner;
/*
* 將System.out重定向到文件樟蠕,而不是屏幕
*/
public class SystemInRedirect {
public static void main(String[] args) throws FileNotFoundException {
// 重定向標(biāo)準(zhǔn)輸出
PrintStream ps = new PrintStream(new FileOutputStream("out.md"));
// 將標(biāo)準(zhǔn)輸出重定向到ps流
System.setOut(ps);
// 后面打印的內(nèi)容就會打印到ps中,而不是Console控制臺
System.out.println("test1");
System.out.println(new SystemInRedirect());
// 重定向標(biāo)準(zhǔn)輸入
FileInputStream fis = new FileInputStream("test.md");
System.setIn(fis);
Scanner s = new Scanner(System.in);
s.useDelimiter("\n");
while (s.hasNext()) {
System.out.println("獲取到的輸入內(nèi)容是:" + s.next());
}
s.close();
}
}
1.6 Java虛擬機(jī)讀寫其他進(jìn)程的數(shù)據(jù)
Runtime對象的exec()方法可以運(yùn)行平臺上的其他程序靠柑,該方法產(chǎn)生一個Process對象寨辩,Process對象代表由該Java程序啟動的子進(jìn)程。Process提供了如下3個方法用于程序和其子進(jìn)程進(jìn)行通信:
- InputStream getErrorStream():獲取子進(jìn)程的錯誤流歼冰;
- InputStream getInputStream():獲取子進(jìn)程的輸入流靡狞;
- OutputStream getOutputStream():獲取子進(jìn)程的輸出流。
代碼實(shí)例
package com.wangjun.othersOfJava;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.util.Scanner;
/*
* 讀取其他進(jìn)程的輸出信息
*/
public class ReadFromProcess {
public static void main(String[] args) throws IOException {
// 1. 讀取其他進(jìn)程的數(shù)據(jù)
// 運(yùn)行javac命令隔嫡,返回該命令的子進(jìn)程
Process p = Runtime.getRuntime().exec("javac");
// 以p進(jìn)程的錯誤流創(chuàng)建BufferedReader對象
// 這個錯誤流對本程序是輸入流甸怕,對p進(jìn)程則是輸出流
BufferedReader br = new BufferedReader(new InputStreamReader(p.getErrorStream()));
String buff = null;
while ((buff = br.readLine()) != null) {
System.out.println(buff);
}
// 2. 將數(shù)據(jù)輸出到其他程序
Process p2 = Runtime.getRuntime().exec("java ReadStandard");
PrintStream ps = new PrintStream(p2.getOutputStream());
ps.println("普通字符串");
ps.println(new ReadFromProcess());
System.out.println(222);
}
}
class ReadStandard {
public static void main(String[] args) throws FileNotFoundException {
try (Scanner s = new Scanner(System.in);
PrintStream ps = new PrintStream(new FileOutputStream("out.md"))) {
s.useDelimiter("\n");
while (s.hasNext()) {
ps.println("輸入內(nèi)容是:" + s.next());
}
}
}
}
輸出到其他程序甘穿,代碼運(yùn)行沒有成功,待分析...
Java的NIO
BufferedReader有一個特征蕾各,就是讀取輸入流中的數(shù)據(jù)時扒磁,如果沒有讀到有效數(shù)據(jù),程序?qū)⒃诖颂幾枞摼€程的執(zhí)行(使用InputStream的read方法從流中讀取數(shù)據(jù)時式曲,也有這樣的特性)妨托,java.io下面的輸入輸出流都是阻塞式的。不僅如此吝羞,傳統(tǒng)的輸入輸出流都是通過字節(jié)的移動來處理的兰伤,及時我們不直接去處理字節(jié)流,單底層的實(shí)現(xiàn)還是依賴于字節(jié)處理钧排,也就是說摹迷,面向流的輸入輸出系統(tǒng)一次只能處理一個字節(jié)怠苔,因此面向流的輸入輸出系統(tǒng)通常效率都不高蛔琅。
為了解決上面的問題男杈,NIO就出現(xiàn)了。NIO采用內(nèi)存映射文件來處理輸入輸出糟袁,NIO將文件或文件的一段區(qū)域映射到內(nèi)存中判族,這樣就可以像訪問內(nèi)存一樣來訪問文件了。(這種方式模擬了操作系統(tǒng)上虛擬內(nèi)存的概念)项戴,通過這種方式進(jìn)行輸入輸出要快得多形帮。
Channel(通道)和Buffer(緩沖)是NIO中兩個核心對象。Channel是對傳統(tǒng)的輸入輸出系統(tǒng)的模擬周叮,在NIO系統(tǒng)中所有的數(shù)據(jù)都需要通過通道傳輸辩撑,Channel與傳統(tǒng)的InputStream、OutputStream最大的區(qū)別就是提供了一個map()方法仿耽,通過它可以直接將“一塊數(shù)據(jù)”映射到內(nèi)存中合冀。如果說傳統(tǒng)IO是面向流的處理,那么NIO是面向塊的處理项贺。
Buffer可以被理解為一個容器水慨,它的本質(zhì)是一個數(shù)組,發(fā)送到Channel中所有的對象都必須首先放到Buffer中敬扛,從而Channel中讀取的數(shù)據(jù)也必須先放到Buffer中。Buffer既可以一次次從Channel取數(shù)據(jù)朝抖,也可以使用Channel直接將文件的某塊數(shù)據(jù)映射成Buffer啥箭。
除了Channel和Buffer之外,NIO還提供了用于將Unicode字符串映射成字節(jié)序列以及逆映射操作的Charset類治宣,也提供了用于支持非阻塞式輸入輸出的Selector類急侥。其中Selector是非阻塞IO的核心砌滞。?
Buffer介紹
在Buffer中有三個重要的概念:
- 容量(capacity):表示Buffer的大小,創(chuàng)建后不能呢過改變坏怪;
- 界限(limit):第一個不能被讀寫的緩沖區(qū)的位置贝润,也就是后面的數(shù)據(jù)不能被讀寫;
- 位置(position):用于指明下一個可以被讀寫的緩沖區(qū)位置索引铝宵。當(dāng)從Channel讀取數(shù)據(jù)的時候打掘,position的值就等于讀到了多少數(shù)據(jù)。
Buffer的主要作用就是裝入數(shù)據(jù)鹏秋,然后輸出數(shù)據(jù)尊蚁。當(dāng)裝入數(shù)據(jù)結(jié)束時,調(diào)用flip()方法侣夷,該方法將limit設(shè)為position所在位置横朋,將position的值設(shè)為0,為輸出數(shù)據(jù)做好準(zhǔn)備百拓。當(dāng)輸出數(shù)據(jù)結(jié)束后琴锭,調(diào)用clear()方法,將position的值設(shè)為0衙传,limit設(shè)為capacity决帖,為下一次的裝入數(shù)據(jù)做準(zhǔn)備。
常用的Buffer是CharBuffer和ByteBuffer粪牲。
使用put()和get()方法進(jìn)行數(shù)據(jù)的放入和讀取古瓤,分為相對和絕對兩種:
- 相對:從Buffer當(dāng)前position位置開始讀取或者寫入數(shù)據(jù),然后將position的值按處理元素的個數(shù)增加腺阳;
- 絕對:直接根據(jù)索引向Buffer中讀取和寫入數(shù)據(jù)落君,使用絕對方式訪問Buffer里的數(shù)據(jù)時,不會影響position的值亭引。
代碼示例
package com.wangjun.othersOfJava;
import java.nio.CharBuffer;
public class NIOBufferTest {
public static void main(String[] args) {
//創(chuàng)建CharBuffer
CharBuffer cb = CharBuffer.allocate(8);
System.out.println("capacity:" + cb.capacity());
System.out.println("limit:" + cb.limit());
System.out.println("position:" + cb.position());
//放入元素
cb.put('a');
cb.put('b');
cb.put('c');
System.out.println("加入三個元素后绎速,position:" + cb.position());
cb.flip();
System.out.println("執(zhí)行flip()后,limit:" + cb.limit());
System.out.println("執(zhí)行flip()后焙蚓,position:" + cb.position());
System.out.println("取出第一個元素: " + cb.get());
System.out.println("取出第一個元素后纹冤,position:" + cb.position());
//調(diào)用clear方法
cb.clear();
System.out.println("執(zhí)行clear()后,limit:" + cb.limit());
System.out.println("執(zhí)行clear()后购公,position:" + cb.position());
System.out.println("執(zhí)行clear()后萌京,數(shù)據(jù)沒有清空,第三個值是:" + cb.get(2));
System.out.println("執(zhí)行絕對讀取后宏浩,position:" + cb.position());
}
}
通過allocate方法創(chuàng)建的是普通Buffer知残,還可以通過allocateDirect方法來創(chuàng)建直接Buffer,雖然創(chuàng)建成本比較高比庄,但是讀寫快求妹。因此適用于長期生存的Buffer乏盐,使用方法和普通Buffer類似。注意制恍,只有ByteBuffer提供了此方法父能,其他類型的想用,可以將該Buffer轉(zhuǎn)成其他類型的Buffer净神。
Channel(通道)介紹
Channel類似傳統(tǒng)的流對象何吝,主要區(qū)別如下:
- Channel可以直接將指定文件的部分或全部直接映射成Buffer。
- 程序不能直接訪問Channel中的數(shù)據(jù)强挫,只能通過Buffer交互岔霸。
所有的Channel都不應(yīng)該通過構(gòu)造器來創(chuàng)建,而是通過傳統(tǒng)的InputStream俯渤、OutputStream的getChannel()方法來返回對應(yīng)的Channel呆细,不同的節(jié)點(diǎn)流獲取的Channel不一樣,比如FileInputStream返回的是FileChannel八匠。
Channel常用的方法有三類:map()絮爷、read()、write()梨树。map方法將Channel對應(yīng)的部分或全部數(shù)據(jù)映射成ByteBuffer坑夯;read和write方法都有一系列的重載形式,這些方法用于從Buffer中讀取/寫入數(shù)據(jù)抡四。
代碼示例
package com.wangjun.othersOfJava;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.CharBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
/*
* 將FileChannel的全部數(shù)據(jù)映射成ByteBuffer
*/
public class NIOChannelTest {
public static void main(String[] args) throws Exception {
File f = new File("NIOChannelTest.md");
//java7新特性try括號內(nèi)的資源會在try語句結(jié)束后自動釋放柜蜈,前提是這些可關(guān)閉的資源必須實(shí)現(xiàn) java.lang.AutoCloseable 接口。
try(
FileChannel inChannel = new FileInputStream(f).getChannel();
FileChannel outChannel = new FileOutputStream("a.md").getChannel();
){
//將FileChannel的全部數(shù)據(jù)映射成ByteBuffer
//map方法的三個參數(shù):1映射模式指巡;2,3控制將哪些數(shù)據(jù)映射成ByteBuffer
MappedByteBuffer buffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, f.length());
//直接將buffer的數(shù)據(jù)全部輸出淑履,完成文件的復(fù)制: NIOChannelTest.md -> a.md
outChannel.write(buffer);
//以下為了輸出文件字符串內(nèi)容
//使用GBK字符集來創(chuàng)建解碼器
Charset charset = Charset.forName("GBK");
//復(fù)原limit和position的位置
buffer.clear();
//創(chuàng)建解碼器
CharsetDecoder decoder = charset.newDecoder();
//使用解碼器將ByteBuffer轉(zhuǎn)成CharBuffer
CharBuffer charBuffer = decoder.decode(buffer);
System.out.println(charBuffer);
}
}
}
除了上面的一次性取數(shù)據(jù),也可以分多次取數(shù)據(jù)
//如果Channel對應(yīng)的文件過大藻雪,使用map方法一次性將所有文件內(nèi)容映射到內(nèi)存中可能會因此性能下降
//這時候我們可以適應(yīng)Channel和Buffer進(jìn)行多次重復(fù)取值
public static void getDataByArray() throws Exception {
try(
//創(chuàng)建文件輸入流
FileInputStream fileInputStream = new FileInputStream("NIOChannelTest.md");
FileChannel fileChannel = fileInputStream.getChannel();
){
//定義一個ByteBuffer對象秘噪,用于重復(fù)取水
ByteBuffer bbuff = ByteBuffer.allocate(64);
while(fileChannel.read(bbuff) != -1) {
bbuff.flip();
Charset charset = Charset.forName("GBK");
//創(chuàng)建解碼器
CharsetDecoder decoder = charset.newDecoder();
//使用解碼器將ByteBuffer轉(zhuǎn)成CharBuffer
CharBuffer charBuffer = decoder.decode(bbuff);
System.out.println(charBuffer);
//為下一次讀取數(shù)據(jù)做準(zhǔn)備
bbuff.clear();
}
}
}
Selector(選擇器)介紹
Selector選擇器類管理著一個被注冊的通道集合的信息和它們的就緒狀態(tài)。通道是和選擇器一起被注冊的勉耀,并且使用選擇器來更新通道的就緒狀態(tài)指煎。利用 Selector可使一個單獨(dú)的線程管理多個 Channel,selector 是非阻塞 IO 的核心便斥。
應(yīng)用
當(dāng)通道使用register(Selector sel, int ops)方法將通道注冊選擇器時至壤,選擇器對通道事件進(jìn)行監(jiān)聽,通過第二個參數(shù)指定監(jiān)聽的事件類型枢纠。
其中可監(jiān)聽的事件類型包括以下:
讀 : SelectionKey.OP_READ
寫 : SelectionKey.OP_WRITE
連接 : SelectionKey.OP_CONNECT
接收 : SelectionKey.OP_ACCEPT
如果需要監(jiān)聽多個事件可以使用位或
操作符:
int key = SelectionKey.OP_READ | SelectionKey.OP_WRITE ; //表示同時監(jiān)聽讀寫操作
參考:
https://blog.csdn.net/dd864140130/article/details/50299687
文件鎖
在NIO中像街,Java提供了文件鎖的支持,使用FileLock來支持文件鎖定功能,在FileChannel中提供lock()/tryLock()方法來獲取文件鎖FileLock對象宅广,從而鎖定文件。lock和tryLock的區(qū)別是前者無法得到文件鎖的時候會阻塞些举,后者不會阻塞跟狱。也支持鎖定部分內(nèi)容,使用lock(long position, long size, boolean shared)即可户魏,其中shared為true時驶臊,表明該鎖是一個共享鎖,可以允許多個縣城讀取文件叼丑,但阻止其他進(jìn)程獲得該文件的排他鎖关翎。當(dāng)shared為false時,表明是一個排他鎖鸠信,它將鎖住對該文件的讀寫纵寝。
默認(rèn)獲取的是排他鎖。
代碼示例
package com.wangjun.othersOfJava;
import java.io.FileOutputStream;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
public class FileLockTest {
public static void main(String[] args) throws Exception {
try(
FileOutputStream fos = new FileOutputStream("a.md");
FileChannel fc = fos.getChannel();
){
//使用非阻塞方式對指定文件加鎖
FileLock lock = fc.tryLock();
Thread.sleep(3000);
lock.release();//釋放鎖
}
}
}
Java的NIO2
Java7對原來的NIO進(jìn)行了重大改進(jìn):
- 提供了全面的文件IO和文件系統(tǒng)訪問支持星立;
- 基于異步Channel的IO爽茴。
這里先簡單介紹一下對文件系統(tǒng)的支持,后續(xù)繼續(xù)學(xué)習(xí)绰垂。
NIO2提供了一下接口和工具類:
- Path接口:通過和Paths工具類結(jié)合使用產(chǎn)生Path對象室奏,可以獲取文件根路徑、絕對路徑劲装、路徑數(shù)量等胧沫;
- Files工具類:提供了很多靜態(tài)方法,比如復(fù)制文件占业、一次性讀取文件所有行绒怨、判斷是否為隱藏文件、判斷文件大小纺酸、遍歷文件和子目錄窖逗、訪問文件屬性等;
- FileVisitor接口:代表一個文件訪問器餐蔬,F(xiàn)iles工具類使用walkFileTree方法遍歷文件和子目錄時碎紊,都會觸發(fā)FileVisitor中相應(yīng)的方法,比如訪問目錄之前樊诺、之后仗考,訪問文件時,訪問文件失敗時词爬;
- WatchService:監(jiān)控文件的變化秃嗜;
NIO和IO的區(qū)別
Java的NIO提供了與標(biāo)準(zhǔn)IO不同的工作方式:
- Channels and Buffers(通道和緩沖區(qū)):標(biāo)準(zhǔn)的IO基于字節(jié)流和字符流進(jìn)行操作的,沒有被緩存在任何地方,而NIO是基于通道(Channel)和緩沖區(qū)(Buffer)進(jìn)行操作锅锨,數(shù)據(jù)總是從通道讀取到緩沖區(qū)中叽赊,或者從緩沖區(qū)寫入到通道中,因此可以前后移動流中的數(shù)據(jù)必搞;
- Asynchronous IO(異步IO):Java NIO可以讓你異步的使用IO必指,例如:當(dāng)線程從通道讀取數(shù)據(jù)到緩沖區(qū)時,線程還是可以進(jìn)行其他事情恕洲。當(dāng)數(shù)據(jù)被寫入到緩沖區(qū)時塔橡,線程可以繼續(xù)處理它。從緩沖區(qū)寫入通道也類似霜第。因?yàn)镹IO將阻塞交給了后臺線程執(zhí)行葛家,非阻塞IO的空余時間可以用于其他通道上進(jìn)行IO,因此可以一個線程可以管理多個通道泌类,而IO是阻塞的癞谒,阻塞式的IO每個連接必須開一個線程來處理,每個線程都需要珍貴的CPU資源末誓,等待IO的時候扯俱,CPU資源沒有被釋放就被浪費(fèi)掉了。
- Selectors(選擇器):選擇器允許一個單獨(dú)的線程可以監(jiān)聽多個數(shù)據(jù)通道((網(wǎng)絡(luò)連接或文件)喇澡,你可以注冊多個通道使用一個選擇器迅栅,然后使用一個單獨(dú)的線程來“選擇”通道:這些通道里已經(jīng)有可以處理的輸入,或者選擇已準(zhǔn)備寫入的通道晴玖。這種選擇機(jī)制读存,使得一個單獨(dú)的線程很容易來管理多個通道。但付出的代價(jià)是解析數(shù)據(jù)可能會比從一個阻塞流中讀取數(shù)據(jù)更復(fù)雜呕屎。
使用場景
NIO
- 優(yōu)勢在于一個線程管理多個通道让簿;但是數(shù)據(jù)的處理將會變得復(fù)雜;
- 如果需要管理同時打開的成千上萬個連接秀睛,這些連接每次只是發(fā)送少量的數(shù)據(jù)尔当,采用這種;
傳統(tǒng)的IO
- 適用于一個線程管理一個通道的情況蹂安;因?yàn)槠渲械牧鲾?shù)據(jù)的讀取是阻塞的椭迎;
- 如果需要管理同時打開不太多的連接,這些連接會發(fā)送大量的數(shù)據(jù)田盈;
參考:BIO畜号、NIO、AIO
Netty簡介
Netty是一個java開源框架允瞧。Netty提供異步的简软、事件驅(qū)動的網(wǎng)絡(luò)應(yīng)用程序框架和工具蛮拔,用以快速開發(fā)高性能、高可靠性的網(wǎng)絡(luò)服務(wù)器和客戶端程序痹升。
Netty是一個NIO客戶端建炫、服務(wù)端框架。允許快速簡單的開發(fā)網(wǎng)絡(luò)應(yīng)用程序疼蛾。例如:服務(wù)端和客戶端之間的協(xié)議踱卵。它最牛逼的地方在于簡化了網(wǎng)絡(luò)編程規(guī)范。例如:TCP和UDP的Socket服務(wù)据过。