<meta charset="utf-8">
Java的IO和NIO
一莲绰、Java的IO
Java的IO功能在java.io
包下,包括輸入观堂、輸出兩種IO流让网,每種輸入、輸出流又可分為字節(jié)流和字符流兩大類(lèi)型将。字節(jié)流以字節(jié)(8位)為單位處理輸入輸出,字符流以字符(16位)為單位來(lái)處理輸入輸出荐虐。
1.1 流的分類(lèi)
- 輸入輸出流
Java的輸入流主要有InputStream
和Reader
作為基類(lèi)七兜,而輸出流主要由OutputStream
和Writer
作為基類(lèi)。他們是一些抽象類(lèi)福扬,無(wú)法直接創(chuàng)建實(shí)例腕铸。
- 字節(jié)流和字符流
處理字節(jié)流:InputStream、OutputStream铛碑;
處理字符流:Reader狠裹、Writer。
- 節(jié)點(diǎn)流和處理流
節(jié)點(diǎn)流:可以直接從IO設(shè)備(如磁盤(pán)汽烦、網(wǎng)絡(luò))讀寫(xiě)數(shù)據(jù)的流稱(chēng)為節(jié)點(diǎn)流涛菠,也被稱(chēng)為低級(jí)流,比如撇吞,處理文件的輸入流:FileInputStream和FileReader俗冻;
處理流:對(duì)一個(gè)已存在的流進(jìn)行連接和封裝,通過(guò)封裝后的流來(lái)實(shí)現(xiàn)數(shù)據(jù)讀寫(xiě)功能牍颈,也被稱(chēng)為高級(jí)流迄薄,比如PrintStream。
當(dāng)使用節(jié)點(diǎn)流時(shí)煮岁,程序可以直接連接到實(shí)際的數(shù)據(jù)源讥蔽。當(dāng)使用處理流時(shí)涣易,程序并不會(huì)直接連接到實(shí)際的數(shù)據(jù)源。
使用處理流的好處就是冶伞,只要使用相同的處理流新症,程序就可以采用完全相同的輸入輸出代碼來(lái)訪問(wèn)不同的數(shù)據(jù)源,隨著處理流所包裝節(jié)點(diǎn)流的變化碰缔,程序?qū)嶋H訪問(wèn)的數(shù)據(jù)源也會(huì)相應(yīng)的發(fā)生改變账劲。實(shí)際上這是一種裝飾者模式,處理流也被稱(chēng)為包裝流金抡。通過(guò)使用處理流瀑焦,Java程序無(wú)須理會(huì)輸入輸出節(jié)點(diǎn)是磁盤(pán)還是網(wǎng)絡(luò),還是其他設(shè)備梗肝,程序只要將這些節(jié)點(diǎn)流包裝成處理流榛瓮,就可以使用相同的輸入輸出代碼來(lái)讀寫(xiě)不同設(shè)備上的數(shù)據(jù)。
識(shí)別處理流很簡(jiǎn)單巫击,主要流的構(gòu)造參數(shù)不是一個(gè)物理節(jié)點(diǎn)禀晓,而是已經(jīng)存在的流丰刊;而節(jié)點(diǎn)流都是直接以物理IO節(jié)點(diǎn)作為構(gòu)造器參數(shù)的茉帅。比如FileInputStream的構(gòu)造器是FileInputStream(String name)
和
FileInputStream(File file),那么它就是一個(gè)節(jié)點(diǎn)流勺像,再比如PrintStream的構(gòu)造器是PrintStream(OutputStream out)
顷级,那么它就是一個(gè)處理流凫乖。
使用處理流的優(yōu)點(diǎn)
- 對(duì)開(kāi)發(fā)人員來(lái)講,使用處理流進(jìn)行輸入輸出操作更簡(jiǎ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");
//寫(xiě)入文件
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輸入字符和對(duì)象
* PrintStream輸出功能非常強(qiáng)大导街,我們平時(shí)常用的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個(gè)類(lèi)。
下面將常用的一些類(lèi)做一下分類(lèi)匯總纤子。其中粗體的為處理流搬瑰。
分類(lèi) | 字節(jié)輸入流 | 字節(jié)輸出流 | 字符輸入流 | 字符輸出流 |
---|---|---|---|---|
基類(lèi) | 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 | ||
對(duì)象流 | ObjectInputStream | ObjectOutputStream | ||
打印流 | PrintStream | PrintWriter | ||
推回輸入流 | PushbackInputStream | PushbackReader |
通常來(lái)說(shuō),我們認(rèn)為字節(jié)流的功能比字符流的功能強(qiáng)大控硼,因?yàn)橛?jì)算機(jī)里所有的數(shù)據(jù)都是二進(jìn)制的跌捆,而字節(jié)流可以處理所有的二進(jìn)制文件,但問(wèn)題就是如果使用字節(jié)流處理文本文件象颖,則需要使用合適的方式將這些字節(jié)轉(zhuǎn)換成字符佩厚,這就增加了編程的復(fù)雜度。
所以通常有這么一個(gè)規(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)容恰好能被正常解析成字符時(shí),則該二進(jìn)制文件就變成了文本文件煞额。
注意2:上面的表格中列舉了4種訪問(wèn)管道的流思恐,它們都是用于實(shí)現(xiàn)進(jìn)程之間通信功能的。
1.3 轉(zhuǎn)換流
上面的表格中列舉了java提供的兩個(gè)轉(zhuǎn)換流膊毁。InputStreamReader和OutputStreamWriter用于將字節(jié)流轉(zhuǎn)換成字符流胀莹。為什么要這么做呢?前面提到了字節(jié)流的功能比較強(qiáng)大婚温,字符流使用起來(lái)方便描焰,如果有一個(gè)字節(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是鍵盤(pán)輸入荆秦,是InputStream(字節(jié)流)的實(shí)例,因?yàn)殒I盤(pán)輸入都是字符力图,所以轉(zhuǎn)換成字符流操作更方便
* 普通的Reader讀取內(nèi)容時(shí)依然不方便步绸,可以包裝成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具有一個(gè)readLine方法吃媒,可以非常方便的一次讀取一行內(nèi)容瓤介,所以經(jīng)常把讀取文本的輸出流包裝成BufferedReader,用來(lái)方便的讀取輸入流中的文本內(nèi)容晓折。
1.4 推回輸入流
在java的IO體系中提供了兩個(gè)特殊的流惑朦,PushbackInputStream和PushbackReader兽泄。它們提供了unread方法用于將字節(jié)/字符推回到推回緩沖區(qū)里面漓概,從而允許讀取剛剛讀取的內(nèi)容病梢。
它們帶有一個(gè)推回緩沖區(qū)觅彰,程序調(diào)用read方法時(shí)總是先從推回緩沖區(qū)里面讀取填抬,只有完全讀取了推回緩沖區(qū)的內(nèi)容飒责,并且還沒(méi)有裝滿read所需的數(shù)組時(shí)才會(huì)從原始輸入流中讀取宏蛉。
代碼實(shí)例
package com.wangjun.othersOfJava;
import java.io.FileReader;
import java.io.IOException;
import java.io.PushbackReader;
/*
* 推回緩沖流測(cè)試
* 如果文件中包含LeetCode就把LeetCode周邊的字符打印出來(lái)
*/
public class PushbackReaderTest {
public static void main(String[] args) throws IOException {
//推回緩沖區(qū)的長(zhǎng)度為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);
//這一次會(huì)先讀取緩沖區(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)輸入輸出分別通過(guò)System.in和System.out來(lái)代表揍堰,默認(rèn)情況下他們代表鍵盤(pán)和顯示器,在System類(lèi)里面提供了3個(gè)重定向的方法: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)容就會(huì)打印到ps中尾菇,而不是Console控制臺(tái)
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ī)讀寫(xiě)其他進(jìn)程的數(shù)據(jù)
Runtime對(duì)象的exec()方法可以運(yùn)行平臺(tái)上的其他程序,該方法產(chǎn)生一個(gè)Process對(duì)象默赂,Process對(duì)象代表由該Java程序啟動(dòng)的子進(jìn)程缆八。Process提供了如下3個(gè)方法用于程序和其子進(jìn)程進(jìn)行通信:
- InputStream getErrorStream():獲取子進(jìn)程的錯(cuò)誤流;
- 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)程的錯(cuò)誤流創(chuàng)建BufferedReader對(duì)象
// 這個(gè)錯(cuò)誤流對(duì)本程序是輸入流蜡峰,對(duì)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)行沒(méi)有成功湿颅,待分析...
Java的NIO
BufferedReader有一個(gè)特征,就是讀取輸入流中的數(shù)據(jù)時(shí)油航,如果沒(méi)有讀到有效數(shù)據(jù)崭庸,程序?qū)⒃诖颂幾枞摼€程的執(zhí)行(使用InputStream的read方法從流中讀取數(shù)據(jù)時(shí)谊囚,也有這樣的特性)怕享,java.io下面的輸入輸出流都是阻塞式的。不僅如此镰踏,傳統(tǒng)的輸入輸出流都是通過(guò)字節(jié)的移動(dòng)來(lái)處理的,及時(shí)我們不直接去處理字節(jié)流奠伪,單底層的實(shí)現(xiàn)還是依賴(lài)于字節(jié)處理绊率,也就是說(shuō)脸狸,面向流的輸入輸出系統(tǒng)一次只能處理一個(gè)字節(jié)顽聂,因此面向流的輸入輸出系統(tǒng)通常效率都不高。
為了解決上面的問(wèn)題全景,NIO就出現(xiàn)了耀石。NIO采用內(nèi)存映射文件來(lái)處理輸入輸出,NIO將文件或文件的一段區(qū)域映射到內(nèi)存中,這樣就可以像訪問(wèn)內(nèi)存一樣來(lái)訪問(wèn)文件了滞伟。(這種方式模擬了操作系統(tǒng)上虛擬內(nèi)存的概念)揭鳞,通過(guò)這種方式進(jìn)行輸入輸出要快得多。
Channel(通道)和Buffer(緩沖)是NIO中兩個(gè)核心對(duì)象梆奈。Channel是對(duì)傳統(tǒng)的輸入輸出系統(tǒng)的模擬野崇,在NIO系統(tǒng)中所有的數(shù)據(jù)都需要通過(guò)通道傳輸,Channel與傳統(tǒng)的InputStream亩钟、OutputStream最大的區(qū)別就是提供了一個(gè)map()方法乓梨,通過(guò)它可以直接將“一塊數(shù)據(jù)”映射到內(nèi)存中。如果說(shuō)傳統(tǒng)IO是面向流的處理清酥,那么NIO是面向塊的處理扶镀。
Buffer可以被理解為一個(gè)容器,它的本質(zhì)是一個(gè)數(shù)組焰轻,發(fā)送到Channel中所有的對(duì)象都必須首先放到Buffer中臭觉,從而Channel中讀取的數(shù)據(jù)也必須先放到Buffer中。Buffer既可以一次次從Channel取數(shù)據(jù)辱志,也可以使用Channel直接將文件的某塊數(shù)據(jù)映射成Buffer蝠筑。
除了Channel和Buffer之外,NIO還提供了用于將Unicode字符串映射成字節(jié)序列以及逆映射操作的Charset類(lèi)揩懒,也提供了用于支持非阻塞式輸入輸出的Selector類(lèi)菱肖。其中Selector是非阻塞IO的核心。?
Buffer介紹
在Buffer中有三個(gè)重要的概念:
- 容量(capacity):表示Buffer的大小旭从,創(chuàng)建后不能呢過(guò)改變稳强;
- 界限(limit):第一個(gè)不能被讀寫(xiě)的緩沖區(qū)的位置,也就是后面的數(shù)據(jù)不能被讀寫(xiě)和悦;
- 位置(position):用于指明下一個(gè)可以被讀寫(xiě)的緩沖區(qū)位置索引退疫。當(dāng)從Channel讀取數(shù)據(jù)的時(shí)候,position的值就等于讀到了多少數(shù)據(jù)鸽素。
Buffer的主要作用就是裝入數(shù)據(jù)褒繁,然后輸出數(shù)據(jù)。當(dāng)裝入數(shù)據(jù)結(jié)束時(shí)馍忽,調(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ù)的放入和讀取碗啄,分為相對(duì)和絕對(duì)兩種:
- 相對(duì):從Buffer當(dāng)前position位置開(kāi)始讀取或者寫(xiě)入數(shù)據(jù),然后將position的值按處理元素的個(gè)數(shù)增加稳摄;
- 絕對(duì):直接根據(jù)索引向Buffer中讀取和寫(xiě)入數(shù)據(jù)稚字,使用絕對(duì)方式訪問(wèn)Buffer里的數(shù)據(jù)時(shí),不會(huì)影響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("加入三個(gè)元素后尉共,position:" + cb.position());
cb.flip();
System.out.println("執(zhí)行flip()后,limit:" + cb.limit());
System.out.println("執(zhí)行flip()后弃锐,position:" + cb.position());
System.out.println("取出第一個(gè)元素: " + cb.get());
System.out.println("取出第一個(gè)元素后袄友,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ù)沒(méi)有清空,第三個(gè)值是:" + cb.get(2));
System.out.println("執(zhí)行絕對(duì)讀取后旋廷,position:" + cb.position());
}
}
通過(guò)allocate方法創(chuàng)建的是普通Buffer鸠按,還可以通過(guò)allocateDirect方法來(lái)創(chuàng)建直接Buffer,雖然創(chuàng)建成本比較高饶碘,但是讀寫(xiě)快目尖。因此適用于長(zhǎng)期生存的Buffer,使用方法和普通Buffer類(lèi)似扎运。注意瑟曲,只有ByteBuffer提供了此方法,其他類(lèi)型的想用豪治,可以將該Buffer轉(zhuǎn)成其他類(lèi)型的Buffer洞拨。
Channel(通道)介紹
Channel類(lèi)似傳統(tǒng)的流對(duì)象,主要區(qū)別如下:
- Channel可以直接將指定文件的部分或全部直接映射成Buffer负拟。
- 程序不能直接訪問(wèn)Channel中的數(shù)據(jù)烦衣,只能通過(guò)Buffer交互。
所有的Channel都不應(yīng)該通過(guò)構(gòu)造器來(lái)創(chuàng)建掩浙,而是通過(guò)傳統(tǒng)的InputStream花吟、OutputStream的getChannel()方法來(lái)返回對(duì)應(yīng)的Channel,不同的節(jié)點(diǎn)流獲取的Channel不一樣厨姚,比如FileInputStream返回的是FileChannel衅澈。
Channel常用的方法有三類(lèi):map()、read()遣蚀、write()矾麻。map方法將Channel對(duì)應(yīng)的部分或全部數(shù)據(jù)映射成ByteBuffer;read和write方法都有一系列的重載形式芭梯,這些方法用于從Buffer中讀取/寫(xiě)入數(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括號(hào)內(nèi)的資源會(huì)在try語(yǔ)句結(jié)束后自動(dòng)釋放,前提是這些可關(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方法的三個(gè)參數(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字符集來(lái)創(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對(duì)應(yīng)的文件過(guò)大贬派,使用map方法一次性將所有文件內(nèi)容映射到內(nèi)存中可能會(huì)因此性能下降
//這時(shí)候我們可以適應(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();
){
//定義一個(gè)ByteBuffer對(duì)象,用于重復(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選擇器類(lèi)管理著一個(gè)被注冊(cè)的通道集合的信息和它們的就緒狀態(tài)澎媒。通道是和選擇器一起被注冊(cè)的搞乏,并且使用選擇器來(lái)更新通道的就緒狀態(tài)。利用 Selector可使一個(gè)單獨(dú)的線程管理多個(gè) Channel戒努,selector 是非阻塞 IO 的核心请敦。
應(yīng)用
當(dāng)通道使用register(Selector sel, int ops)方法將通道注冊(cè)選擇器時(shí),選擇器對(duì)通道事件進(jìn)行監(jiān)聽(tīng)储玫,通過(guò)第二個(gè)參數(shù)指定監(jiān)聽(tīng)的事件類(lèi)型侍筛。
其中可監(jiān)聽(tīng)的事件類(lèi)型包括以下:
讀 : SelectionKey.OP_READ
寫(xiě) : SelectionKey.OP_WRITE
連接 : SelectionKey.OP_CONNECT
接收 : SelectionKey.OP_ACCEPT
如果需要監(jiān)聽(tīng)多個(gè)事件可以使用位或
操作符:
int key = SelectionKey.OP_READ | SelectionKey.OP_WRITE ; //表示同時(shí)監(jiān)聽(tīng)讀寫(xiě)操作
參考:
https://blog.csdn.net/dd864140130/article/details/50299687
文件鎖
在NIO中,Java提供了文件鎖的支持撒穷,使用FileLock來(lái)支持文件鎖定功能匣椰,在FileChannel中提供lock()/tryLock()方法來(lái)獲取文件鎖FileLock對(duì)象,從而鎖定文件端礼。lock和tryLock的區(qū)別是前者無(wú)法得到文件鎖的時(shí)候會(huì)阻塞禽笑,后者不會(huì)阻塞。也支持鎖定部分內(nèi)容蛤奥,使用lock(long position, long size, boolean shared)即可蒲每,其中shared為true時(shí),表明該鎖是一個(gè)共享鎖喻括,可以允許多個(gè)縣城讀取文件邀杏,但阻止其他進(jìn)程獲得該文件的排他鎖。當(dāng)shared為false時(shí)唬血,表明是一個(gè)排他鎖望蜡,它將鎖住對(duì)該文件的讀寫(xiě)。
默認(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();
){
//使用非阻塞方式對(duì)指定文件加鎖
FileLock lock = fc.tryLock();
Thread.sleep(3000);
lock.release();//釋放鎖
}
}
}
Java的NIO2
Java7對(duì)原來(lái)的NIO進(jìn)行了重大改進(jìn):
- 提供了全面的文件IO和文件系統(tǒng)訪問(wèn)支持脖律;
- 基于異步Channel的IO。
這里先簡(jiǎn)單介紹一下對(duì)文件系統(tǒng)的支持腕侄,后續(xù)繼續(xù)學(xué)習(xí)小泉。
NIO2提供了一下接口和工具類(lèi):
- Path接口:通過(guò)和Paths工具類(lèi)結(jié)合使用產(chǎn)生Path對(duì)象芦疏,可以獲取文件根路徑、絕對(duì)路徑微姊、路徑數(shù)量等酸茴;
- Files工具類(lèi):提供了很多靜態(tài)方法,比如復(fù)制文件兢交、一次性讀取文件所有行薪捍、判斷是否為隱藏文件、判斷文件大小配喳、遍歷文件和子目錄酪穿、訪問(wèn)文件屬性等;
- FileVisitor接口:代表一個(gè)文件訪問(wèn)器晴裹,F(xiàn)iles工具類(lèi)使用walkFileTree方法遍歷文件和子目錄時(shí)被济,都會(huì)觸發(fā)FileVisitor中相應(yīng)的方法,比如訪問(wèn)目錄之前涧团、之后溉潭,訪問(wèn)文件時(shí),訪問(wèn)文件失敗時(shí)少欺;
- 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)行操作的,沒(méi)有被緩存在任何地方赞别,而NIO是基于通道(Channel)和緩沖區(qū)(Buffer)進(jìn)行操作畏陕,數(shù)據(jù)總是從通道讀取到緩沖區(qū)中,或者從緩沖區(qū)寫(xiě)入到通道中仿滔,因此可以前后移動(dòng)流中的數(shù)據(jù)惠毁;
- Asynchronous IO(異步IO):Java NIO可以讓你異步的使用IO,例如:當(dāng)線程從通道讀取數(shù)據(jù)到緩沖區(qū)時(shí)崎页,線程還是可以進(jìn)行其他事情鞠绰。當(dāng)數(shù)據(jù)被寫(xiě)入到緩沖區(qū)時(shí),線程可以繼續(xù)處理它飒焦。從緩沖區(qū)寫(xiě)入通道也類(lèi)似蜈膨。因?yàn)镹IO將阻塞交給了后臺(tái)線程執(zhí)行,非阻塞IO的空余時(shí)間可以用于其他通道上進(jìn)行IO牺荠,因此可以一個(gè)線程可以管理多個(gè)通道翁巍,而IO是阻塞的,阻塞式的IO每個(gè)連接必須開(kāi)一個(gè)線程來(lái)處理休雌,每個(gè)線程都需要珍貴的CPU資源灶壶,等待IO的時(shí)候,CPU資源沒(méi)有被釋放就被浪費(fèi)掉了杈曲。
- Selectors(選擇器):選擇器允許一個(gè)單獨(dú)的線程可以監(jiān)聽(tīng)多個(gè)數(shù)據(jù)通道((網(wǎng)絡(luò)連接或文件)驰凛,你可以注冊(cè)多個(gè)通道使用一個(gè)選擇器胸懈,然后使用一個(gè)單獨(dú)的線程來(lái)“選擇”通道:這些通道里已經(jīng)有可以處理的輸入,或者選擇已準(zhǔn)備寫(xiě)入的通道恰响。這種選擇機(jī)制趣钱,使得一個(gè)單獨(dú)的線程很容易來(lái)管理多個(gè)通道。但付出的代價(jià)是解析數(shù)據(jù)可能會(huì)比從一個(gè)阻塞流中讀取數(shù)據(jù)更復(fù)雜渔隶。
使用場(chǎng)景
NIO
- 優(yōu)勢(shì)在于一個(gè)線程管理多個(gè)通道羔挡;但是數(shù)據(jù)的處理將會(huì)變得復(fù)雜洁奈;
- 如果需要管理同時(shí)打開(kāi)的成千上萬(wàn)個(gè)連接间唉,這些連接每次只是發(fā)送少量的數(shù)據(jù),采用這種利术;
傳統(tǒng)的IO
- 適用于一個(gè)線程管理一個(gè)通道的情況呈野;因?yàn)槠渲械牧鲾?shù)據(jù)的讀取是阻塞的;
- 如果需要管理同時(shí)打開(kāi)不太多的連接印叁,這些連接會(huì)發(fā)送大量的數(shù)據(jù)被冒;
參考:BIO、NIO轮蜕、AIO
Netty簡(jiǎn)介
Netty是一個(gè)java開(kāi)源框架昨悼。Netty提供異步的、事件驅(qū)動(dòng)的網(wǎng)絡(luò)應(yīng)用程序框架和工具跃洛,用以快速開(kāi)發(fā)高性能率触、高可靠性的網(wǎng)絡(luò)服務(wù)器和客戶(hù)端程序。
Netty是一個(gè)NIO客戶(hù)端汇竭、服務(wù)端框架葱蝗。允許快速簡(jiǎn)單的開(kāi)發(fā)網(wǎng)絡(luò)應(yīng)用程序。例如:服務(wù)端和客戶(hù)端之間的協(xié)議细燎。它最牛逼的地方在于簡(jiǎn)化了網(wǎng)絡(luò)編程規(guī)范两曼。例如:TCP和UDP的Socket服務(wù)。
作者:王小冬
鏈接:http://www.reibang.com/p/4c8d7b9edec9
來(lái)源:簡(jiǎn)書(shū)
著作權(quán)歸作者所有玻驻。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán)悼凑,非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。