?一惊搏、IO 流與系統(tǒng)
IO 技術(shù)在 JDK 中算是極其復(fù)雜的模塊梭姓,其復(fù)雜的一個(gè)關(guān)鍵原因就是 IO 操作和系統(tǒng)內(nèi)核的關(guān)聯(lián)性,另外網(wǎng)絡(luò)編程屠升,文件管理都依賴(lài) IO 技術(shù)潮改,而且都是編程的難點(diǎn),想要整體理解 IO 流汇在,先從 Linux 操作系統(tǒng)開(kāi)始殖告。
Linux 空間隔離
Linux 使用是區(qū)分用戶(hù)的宝与,這個(gè)是基礎(chǔ)常識(shí)诽里,其底層也區(qū)分用戶(hù)和內(nèi)核兩個(gè)模塊:
User space:用戶(hù)空間
Kernel space:內(nèi)核空間
常識(shí)用戶(hù)空間的權(quán)限相對(duì)內(nèi)核空間操作權(quán)限弱很多墓懂,這就涉及到用戶(hù)與內(nèi)核兩個(gè)模塊間的交互榜跌,此時(shí)部署在服務(wù)上的應(yīng)用如果需要請(qǐng)求系統(tǒng)資源础浮,則在交互上更為復(fù)雜:
用戶(hù)空間本身無(wú)法直接向系統(tǒng)發(fā)布調(diào)度指令,java學(xué)習(xí)交流:737251827必須通過(guò)內(nèi)核靴姿,對(duì)于內(nèi)核中數(shù)據(jù)的操作维雇,也是需要先拷貝到用戶(hù)空間铝侵,這種隔離機(jī)制可以有效的保護(hù)系統(tǒng)的安全性和穩(wěn)定性猾警。
參數(shù)查看
可以通過(guò) Top 命令動(dòng)態(tài)查看各項(xiàng)數(shù)據(jù)分析温自,進(jìn)程占用資源的狀況:
us:用戶(hù)空間占用 CPU 的百分比;
sy:內(nèi)核空間占用 CPU 的百分比鸠踪;
id:空閑進(jìn)程占用 CPU 的百分比械媒;
wa:IO 等待占用 CPU 的百分比;
對(duì)wa指標(biāo)评汰,在大規(guī)模文件任務(wù)流程里是監(jiān)控的核心項(xiàng)之一纷捞。
IO 協(xié)作流程
此時(shí)再看上面圖【1】的流程,當(dāng)應(yīng)用端發(fā)起 IO 操作的請(qǐng)求時(shí)被去,請(qǐng)求沿著鏈路上的各個(gè)節(jié)點(diǎn)流轉(zhuǎn)兰绣,有兩個(gè)核心概念:
節(jié)點(diǎn)交互模式:同步與異步;
IO 數(shù)據(jù)操作:阻塞與非阻塞编振;
這里就是文件流中常說(shuō)的:【同步/異步】IO,【阻塞/非阻塞】IO,下面看細(xì)節(jié)。
二踪央、IO 模型分析
1臀玄、同步阻塞
用戶(hù)線(xiàn)程與內(nèi)核的交互方式,應(yīng)用端請(qǐng)求對(duì)應(yīng)一個(gè)線(xiàn)程處理畅蹂,整個(gè)過(guò)程中 accept(接收)和 read(讀取)方法都會(huì)阻塞直至整個(gè)動(dòng)作完成:
在常規(guī) CS 架構(gòu)模式中健无,這是一次 IO 操作的基本過(guò)程,該方式如果在高并發(fā)的場(chǎng)景下液斜,客戶(hù)端的請(qǐng)求響應(yīng)會(huì)存在嚴(yán)重的性能問(wèn)題累贤,并且占用過(guò)多資源。
2少漆、同步非阻塞
在同步阻塞 IO 的基礎(chǔ)上進(jìn)行優(yōu)化臼膏,當(dāng)前線(xiàn)程不會(huì)一直等待數(shù)據(jù)就緒直到完成復(fù)制:
在線(xiàn)程請(qǐng)求后會(huì)立即返回,并不斷輪詢(xún)直至拿到數(shù)據(jù)示损,才會(huì)停止輪詢(xún)渗磅,這種模式的缺陷也是顯而易見(jiàn)的,如果數(shù)據(jù)準(zhǔn)備好检访,在通知線(xiàn)程完成后續(xù)動(dòng)作始鱼,這樣就可以省掉很多中間交互。
3脆贵、異步通知模式
在異步模式下医清,徹底摒棄阻塞機(jī)制,過(guò)程分段進(jìn)行交互卖氨,這與常規(guī)的第三方對(duì)接模式很相似会烙,本地服務(wù)在請(qǐng)求第三方服務(wù)時(shí),如果請(qǐng)求過(guò)程耗時(shí)很大双泪,會(huì)異步執(zhí)行持搜,第三方第一次回調(diào),確認(rèn)請(qǐng)求可以被執(zhí)行焙矛;第二次回調(diào)則是推送處理結(jié)果葫盼,這種思想在處理復(fù)雜問(wèn)題時(shí),可以很大程度的提高性能村斟,節(jié)省資源:
異步模式對(duì)于性能的提升是巨大的贫导,當(dāng)然其相應(yīng)的處理機(jī)制也更復(fù)雜,程序的迭代和優(yōu)化是無(wú)止境的蟆盹,在 NIO 模式中再次對(duì) IO 流模式進(jìn)行優(yōu)化孩灯。
三、File 文件類(lèi)
1逾滥、基礎(chǔ)描述
File 類(lèi)作為文件和目錄路徑名的抽象表示峰档,用來(lái)獲取磁盤(pán)文件的相關(guān)元數(shù)據(jù)信息,例如:文件名稱(chēng)、大小讥巡、修改時(shí)間掀亩、權(quán)限判斷等。
注意:File 并不操作文件承載的數(shù)據(jù)內(nèi)容欢顷,文件內(nèi)容稱(chēng)為數(shù)據(jù)槽棍,文件自身信息稱(chēng)為元數(shù)據(jù)。
public class File01 {
? ? public static void main(String[] args) throws Exception {
? ? ? ? // 1抬驴、讀取指定文件
? ? ? ? File speFile = new File(IoParam.BASE_PATH+"fileio-03.text") ;
? ? ? ? if (!speFile.exists()){
? ? ? ? ? ? boolean creFlag = speFile.createNewFile() ;
? ? ? ? ? ? System.out.println("創(chuàng)建:"+speFile.getName()+"; 結(jié)果:"+creFlag);
? ? ? ? }
? ? ? ? // 2炼七、讀取指定位置
? ? ? ? File dirFile = new File(IoParam.BASE_PATH) ;
? ? ? ? // 判斷是否目錄
? ? ? ? boolean dirFlag = dirFile.isDirectory() ;
? ? ? ? if (dirFlag){
? ? ? ? ? ? File[] dirFiles = dirFile.listFiles() ;
? ? ? ? ? ? printFileArr(dirFiles);
? ? ? ? }
? ? ? ? // 3、刪除指定文件
? ? ? ? if (speFile.exists()){
? ? ? ? ? ? boolean delFlag = speFile.delete() ;
? ? ? ? ? ? System.out.println("刪除:"+speFile.getName()+"; 結(jié)果:"+delFlag);
? ? ? ? }
? ? }
? ? private static void printFileArr (File[] fileArr){
? ? ? ? if (fileArr != null && fileArr.length>0){
? ? ? ? ? ? for (File file : fileArr) {
? ? ? ? ? ? ? ? printFileInfo(file) ;
? ? ? ? ? ? }
? ? ? ? }
? ? }
? ? private static void printFileInfo (File file) {
? ? ? ? System.out.println("名稱(chēng):"+file.getName());
? ? ? ? System.out.println("長(zhǎng)度:"+file.length());
? ? ? ? System.out.println("路徑:"+file.getPath());
? ? ? ? System.out.println("文件判斷:"+file.isFile());
? ? ? ? System.out.println("目錄判斷:"+file.isDirectory());
? ? ? ? System.out.println("最后修改:"+new Date(file.lastModified()));
? ? ? ? System.out.println();
? ? }
}
上述案例使用了 File 類(lèi)中的基本構(gòu)造和常用方法(讀取布持、判斷豌拙、創(chuàng)建、刪除)等鳖链,JDK 源碼在不斷的更新迭代姆蘸,通過(guò)類(lèi)的構(gòu)造器、方法芙委、注釋等去判斷類(lèi)具有的基本功能逞敷,是作為開(kāi)發(fā)人員的必備能力。
在 File 文件類(lèi)中缺乏兩個(gè)關(guān)鍵信息描述:類(lèi)型和編碼灌侣,如果經(jīng)常開(kāi)發(fā)文件模塊的需求推捐,就知道這是兩個(gè)極其復(fù)雜的點(diǎn),很容易出現(xiàn)問(wèn)題侧啼,下面站在實(shí)際開(kāi)發(fā)的角度看看如何處理牛柒。
2、文件業(yè)務(wù)場(chǎng)景
如圖所示痊乾,在常規(guī)的文件流任務(wù)中皮壁,會(huì)涉及【文件、流哪审、數(shù)據(jù)】三種基本形式的轉(zhuǎn)換:
基本過(guò)程描述:
源文件生成蛾魄,推送文件中心;
通知業(yè)務(wù)使用節(jié)點(diǎn)獲取文件湿滓;
業(yè)務(wù)節(jié)點(diǎn)進(jìn)行邏輯處理滴须;
很顯然的一個(gè)問(wèn)題,任何節(jié)點(diǎn)都無(wú)法適配所有文件處理策略叽奥,比如類(lèi)型與編碼扔水,面對(duì)復(fù)雜場(chǎng)景下的問(wèn)題,規(guī)則約束是常用的解決策略朝氓,即在約定規(guī)則之內(nèi)的事情才處理魔市。
上面流程中主届,源文件節(jié)點(diǎn)通知業(yè)務(wù)節(jié)點(diǎn)時(shí)的數(shù)據(jù)主體描述:
public class BizFile {
? ? /**
? ? * 文件任務(wù)批次號(hào)
? ? */
? ? private String taskId ;
? ? /**
? ? * 是否壓縮
? ? */
? ? private Boolean zipFlag ;
? ? /**
? ? * 文件地址
? ? */
? ? private String fileUrl ;
? ? /**
? ? * 文件類(lèi)型
? ? */
? ? private String fileType ;
? ? /**
? ? * 文件編碼
? ? */
? ? private String fileCode ;
? ? /**
? ? * 業(yè)務(wù)關(guān)聯(lián):數(shù)據(jù)庫(kù)
? ? */
? ? private String bizDataBase ;
? ? /**
? ? * 業(yè)務(wù)關(guān)聯(lián):數(shù)據(jù)表
? ? */
? ? private String bizTableName ;
}
把整個(gè)過(guò)程當(dāng)做一個(gè)任務(wù)進(jìn)行封裝,即:任務(wù)批次待德、文件信息岂膳、業(yè)務(wù)庫(kù)表路由等,當(dāng)然這些信息也可以直接標(biāo)記在文件命名的策略上磅网,處理的手段類(lèi)似:
/**
* 基于約定策略讀取信息
*/
public class File02 {
? ? public static void main(String[] args) {
? ? ? ? BizFile bizFile = new BizFile("IN001",Boolean.FALSE, IoParam.BASE_PATH,
? ? ? ? ? ? ? ? "csv","utf8","model","score");
? ? ? ? bizFileInfo(bizFile) ;
? ? ? ? /*
? ? ? ? * 業(yè)務(wù)性校驗(yàn)
? ? ? ? */
? ? ? ? File file = new File(bizFile.getFileUrl());
? ? ? ? if (!file.getName().endsWith(bizFile.getFileType())){
? ? ? ? ? ? System.out.println(file.getName()+":描述錯(cuò)誤...");
? ? ? ? }
? ? }
? ? private static void bizFileInfo (BizFile bizFile){
? ? ? ? logInfo("任務(wù)ID",bizFile.getTaskId());
? ? ? ? logInfo("是否解壓",bizFile.getZipFlag());
? ? ? ? logInfo("文件地址",bizFile.getFileUrl());
? ? ? ? logInfo("文件類(lèi)型",bizFile.getFileType());
? ? ? ? logInfo("文件編碼",bizFile.getFileCode());
? ? ? ? logInfo("業(yè)務(wù)庫(kù)",bizFile.getBizDataBase());
? ? ? ? logInfo("業(yè)務(wù)表",bizFile.getBizTableName());
? ? }
}
基于主體描述的信息,也可以轉(zhuǎn)化到命名規(guī)則上:命名策略:編號(hào)_壓縮_Excel_編碼_庫(kù)_表筷屡,這樣一來(lái)在業(yè)務(wù)處理時(shí)涧偷,不符合約定的文件直接排除掉,降低文件異常導(dǎo)致的數(shù)據(jù)問(wèn)題毙死。
四燎潮、基礎(chǔ)流模式
1、整體概述
IO 流向
基本編碼邏輯:源文件->輸入流->邏輯處理->輸出流->目標(biāo)文件扼倘;
基于不同的角度看确封,流可以被劃分很多模式:
流動(dòng)方向:輸入流、輸出流再菊;
流數(shù)據(jù)類(lèi)型:字節(jié)流爪喘、字符流;
IO 流的模式有很多種纠拔,相應(yīng)的 API 設(shè)計(jì)也很復(fù)雜秉剑,通常復(fù)雜的 API 要把握住核心接口與常用的實(shí)現(xiàn)類(lèi)和原理。
基礎(chǔ) API
字節(jié)流:InputStream 輸入稠诲、OutputStream 輸出侦鹏;數(shù)據(jù)傳輸?shù)幕締挝皇亲止?jié);
read():輸入流中讀取數(shù)據(jù)的下一個(gè)字節(jié)臀叙;
read(byte b[]):讀數(shù)據(jù)緩存到字節(jié)數(shù)組略水;
write(int b):指定字節(jié)寫(xiě)入輸出流;
write(byte b[]):數(shù)組字節(jié)寫(xiě)入輸出流劝萤;
字符流:Reader 讀取渊涝、Writer 寫(xiě)出;數(shù)據(jù)傳輸?shù)幕締挝皇亲址?/p>
read():讀取一個(gè)單字符稳其;
read(char cbuf[]):讀取到字符數(shù)組驶赏;
write(int c):寫(xiě)一個(gè)指定字符;
write(char cbuf[]):寫(xiě)一個(gè)字符數(shù)組既鞠;
緩沖模式
IO 流常規(guī)讀寫(xiě)模式煤傍,即讀取到數(shù)據(jù)然后寫(xiě)出,還有一種緩沖模式嘱蛋,即數(shù)據(jù)先加載到緩沖數(shù)組蚯姆,在讀取的時(shí)候判斷是否要再次填充緩沖區(qū):
緩沖模式的優(yōu)點(diǎn)十分明顯五续,保證讀寫(xiě)過(guò)程的高效率,并且與數(shù)據(jù)填充過(guò)程隔離執(zhí)行龄恋,在 BufferedInputStream疙驾、BufferedReader 類(lèi)中是對(duì)緩沖邏輯的具體實(shí)現(xiàn)。
2郭毕、字節(jié)流
API 關(guān)系圖:
字節(jié)流基礎(chǔ) API:
public class IoByte01 {
? ? public static void main(String[] args) throws Exception {
? ? ? ? // 源文件 目標(biāo)文件
? ? ? ? File source = new File(IoParam.BASE_PATH+"fileio-01.png") ;
? ? ? ? File target = new File(IoParam.BASE_PATH+"copy-"+source.getName()) ;
? ? ? ? // 輸入流 輸出流
? ? ? ? InputStream inStream = new FileInputStream(source) ;
? ? ? ? OutputStream outStream = new FileOutputStream(target) ;
? ? ? ? // 讀入 寫(xiě)出
? ? ? ? byte[] byteArr = new byte[1024];
? ? ? ? int readSign ;
? ? ? ? while ((readSign=inStream.read(byteArr)) != -1){
? ? ? ? ? ? outStream.write(byteArr);
? ? ? ? }
? ? ? ? // 關(guān)閉輸入它碎、輸出流
? ? ? ? outStream.close();
? ? ? ? inStream.close();
? ? }
}
字節(jié)流緩沖 API:
public class IoByte02 {
? ? public static void main(String[] args) throws Exception {
? ? ? ? // 源文件 目標(biāo)文件
? ? ? ? File source = new File(IoParam.BASE_PATH+"fileio-02.png") ;
? ? ? ? File target = new File(IoParam.BASE_PATH+"backup-"+source.getName()) ;
? ? ? ? // 緩沖:輸入流 輸出流
? ? ? ? InputStream bufInStream = new BufferedInputStream(new FileInputStream(source));
? ? ? ? OutputStream bufOutStream = new BufferedOutputStream(new FileOutputStream(target));
? ? ? ? // 讀入 寫(xiě)出
? ? ? ? int readSign ;
? ? ? ? while ((readSign=bufInStream.read()) != -1){
? ? ? ? ? ? bufOutStream.write(readSign);
? ? ? ? }
? ? ? ? // 關(guān)閉輸入、輸出流
? ? ? ? bufOutStream.close();
? ? ? ? bufInStream.close();
? ? }
}
字節(jié)流應(yīng)用場(chǎng)景:數(shù)據(jù)是文件本身显押,例如圖片扳肛,視頻,音頻等乘碑。
3挖息、字符流
API 關(guān)系圖:
字符流基礎(chǔ) API:
public class IoChar01 {
? ? public static void main(String[] args) throws Exception {
? ? ? ? // 讀文本 寫(xiě)文本
? ? ? ? File readerFile = new File(IoParam.BASE_PATH+"io-text.txt") ;
? ? ? ? File writerFile = new File(IoParam.BASE_PATH+"copy-"+readerFile.getName()) ;
? ? ? ? // 字符輸入輸出流
? ? ? ? Reader reader = new FileReader(readerFile) ;
? ? ? ? Writer writer = new FileWriter(writerFile) ;
? ? ? ? // 字符讀入和寫(xiě)出
? ? ? ? int readSign ;
? ? ? ? while ((readSign = reader.read()) != -1){
? ? ? ? ? ? writer.write(readSign);
? ? ? ? }
? ? ? ? writer.flush();
? ? ? ? // 關(guān)閉流
? ? ? ? writer.close();
? ? ? ? reader.close();
? ? }
}
字符流緩沖 API:
public class IoChar02 {
? ? public static void main(String[] args) throws Exception {
? ? ? ? // 讀文本 寫(xiě)文本
? ? ? ? File readerFile = new File(IoParam.BASE_PATH+"io-text.txt") ;
? ? ? ? File writerFile = new File(IoParam.BASE_PATH+"line-"+readerFile.getName()) ;
? ? ? ? // 緩沖字符輸入輸出流
? ? ? ? BufferedReader bufReader = new BufferedReader(new FileReader(readerFile)) ;
? ? ? ? BufferedWriter bufWriter = new BufferedWriter(new FileWriter(writerFile)) ;
? ? ? ? // 字符讀入和寫(xiě)出
? ? ? ? String line;
? ? ? ? while ((line = bufReader.readLine()) != null){
? ? ? ? ? ? bufWriter.write(line);
? ? ? ? ? ? bufWriter.newLine();
? ? ? ? }
? ? ? ? bufWriter.flush();
? ? ? ? // 關(guān)閉流
? ? ? ? bufWriter.close();
? ? ? ? bufReader.close();
? ? }
}
字符流應(yīng)用場(chǎng)景:文件作為數(shù)據(jù)的載體,例如 Excel兽肤、CSV套腹、TXT 等。
4资铡、編碼解碼
編碼:字符轉(zhuǎn)換為字節(jié)电禀;
解碼:字節(jié)轉(zhuǎn)換為字符;
public class EnDeCode {
? ? public static void main(String[] args) throws Exception {
? ? ? ? String var = "IO流" ;
? ? ? ? // 編碼
? ? ? ? byte[] enVar = var.getBytes(StandardCharsets.UTF_8) ;
? ? ? ? for (byte encode:enVar){
? ? ? ? ? ? System.out.println(encode);
? ? ? ? }
? ? ? ? // 解碼
? ? ? ? String deVar = new String(enVar,StandardCharsets.UTF_8) ;
? ? ? ? System.out.println(deVar);
? ? ? ? // 亂碼
? ? ? ? String messyVar = new String(enVar,StandardCharsets.ISO_8859_1) ;
? ? ? ? System.out.println(messyVar);
? ? }
}
亂碼出現(xiàn)的根本原因害驹,就是在編碼與解碼的兩個(gè)階段使用的編碼類(lèi)型不同鞭呕。
5、序列化
序列化:對(duì)象轉(zhuǎn)換為流的過(guò)程宛官;
反序列化:流轉(zhuǎn)換為對(duì)象的過(guò)程葫松;
public class SerEntity implements Serializable {
? ? private Integer id ;
? ? private String name ;
}
public class Seriali01 {
? ? public static void main(String[] args) throws Exception {
? ? ? ? // 序列化對(duì)象
? ? ? ? OutputStream outStream = new FileOutputStream("SerEntity.txt") ;
? ? ? ? ObjectOutputStream objOutStream = new ObjectOutputStream(outStream);
? ? ? ? objOutStream.writeObject(new SerEntity(1,"Cicada"));
? ? ? ? objOutStream.close();
? ? ? ? // 反序列化對(duì)象
? ? ? ? InputStream inStream = new FileInputStream("SerEntity.txt");
? ? ? ? ObjectInputStream objInStream = new ObjectInputStream(inStream) ;
? ? ? ? SerEntity serEntity = (SerEntity) objInStream.readObject();
? ? ? ? System.out.println(serEntity);
? ? ? ? inStream.close();
? ? }
}
注意:引用類(lèi)型的成員對(duì)象也必須是可被序列化的,否則會(huì)拋出NotSerializableException異常底洗。
五腋么、NIO 模式
1、基礎(chǔ)概念
NIO 即(NonBlockingIO)亥揖,面向數(shù)據(jù)塊的處理機(jī)制珊擂,同步非阻塞模型,服務(wù)端的單個(gè)線(xiàn)程可以處理多個(gè)客戶(hù)端請(qǐng)求费变,對(duì) IO 流的處理速度有極高的提升摧扇,三大核心組件:
Buffer(緩沖區(qū)):底層維護(hù)數(shù)組存儲(chǔ)數(shù)據(jù);
Channel(通道):支持讀寫(xiě)雙向操作挚歧;
Selector(選擇器):提供 Channel 多注冊(cè)和輪詢(xún)能力扛稽;
API 使用案例
public class IoNew01 {
? ? public static void main(String[] args) throws Exception {
? ? ? ? // 源文件 目標(biāo)文件
? ? ? ? File source = new File(IoParam.BASE_PATH+"fileio-02.png") ;
? ? ? ? File target = new File(IoParam.BASE_PATH+"channel-"+source.getName()) ;
? ? ? ? // 輸入字節(jié)流通道
? ? ? ? FileInputStream inStream = new FileInputStream(source);
? ? ? ? FileChannel inChannel = inStream.getChannel();
? ? ? ? // 輸出字節(jié)流通道
? ? ? ? FileOutputStream outStream = new FileOutputStream(target);
? ? ? ? FileChannel outChannel = outStream.getChannel();
? ? ? ? // 直接通道復(fù)制
? ? ? ? // outChannel.transferFrom(inChannel, 0, inChannel.size());
? ? ? ? // 緩沖區(qū)讀寫(xiě)機(jī)制
? ? ? ? ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
? ? ? ? while (true) {
? ? ? ? ? ? // 讀取通道中數(shù)據(jù)到緩沖區(qū)
? ? ? ? ? ? int in = inChannel.read(buffer);
? ? ? ? ? ? if (in == -1) {
? ? ? ? ? ? ? ? break;
? ? ? ? ? ? }
? ? ? ? ? ? // 讀寫(xiě)切換
? ? ? ? ? ? buffer.flip();
? ? ? ? ? ? // 寫(xiě)出緩沖區(qū)數(shù)據(jù)
? ? ? ? ? ? outChannel.write(buffer);
? ? ? ? ? ? // 清空緩沖區(qū)
? ? ? ? ? ? buffer.clear();
? ? ? ? }
? ? ? ? outChannel.close();
? ? ? ? inChannel.close();
? ? }
}
上述案例只是 NIO 最基礎(chǔ)的文件復(fù)制能力,在網(wǎng)絡(luò)通信中滑负,NIO 模式的發(fā)揮空間十分寬廣在张。
2用含、網(wǎng)絡(luò)通信
服務(wù)端的單線(xiàn)程可以處理多個(gè)客戶(hù)端請(qǐng)求,通過(guò)輪詢(xún)多路復(fù)用器查看是否有 IO 請(qǐng)求帮匾,這樣一來(lái)啄骇,服務(wù)端的并發(fā)能力得到極大的提升,并且顯著降低了資源的消耗瘟斜。
API 案例:服務(wù)端模擬
public class SecServer {
? ? public static void main(String[] args) {
? ? ? ? try {
? ? ? ? ? ? //啟動(dòng)服務(wù)開(kāi)啟監(jiān)聽(tīng)
? ? ? ? ? ? ServerSocketChannel socketChannel = ServerSocketChannel.open();
? ? ? ? ? ? socketChannel.socket().bind(new InetSocketAddress("127.0.0.1", 8089));
? ? ? ? ? ? // 設(shè)置非阻塞缸夹,接受客戶(hù)端
? ? ? ? ? ? socketChannel.configureBlocking(false);
? ? ? ? ? ? // 打開(kāi)多路復(fù)用器
? ? ? ? ? ? Selector selector = Selector.open();
? ? ? ? ? ? // 服務(wù)端Socket注冊(cè)到多路復(fù)用器,指定興趣事件
? ? ? ? ? ? socketChannel.register(selector, SelectionKey.OP_ACCEPT);
? ? ? ? ? ? // 多路復(fù)用器輪詢(xún)
? ? ? ? ? ? ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
? ? ? ? ? ? while (selector.select() > 0){
? ? ? ? ? ? ? ? Set<SelectionKey> selectionKeys = selector.selectedKeys();
? ? ? ? ? ? ? ? Iterator<SelectionKey> selectionKeyIter = selectionKeys.iterator();
? ? ? ? ? ? ? ? while (selectionKeyIter.hasNext()){
? ? ? ? ? ? ? ? ? ? SelectionKey selectionKey = selectionKeyIter.next() ;
? ? ? ? ? ? ? ? ? ? selectionKeyIter.remove();
? ? ? ? ? ? ? ? ? ? if(selectionKey.isAcceptable()) {
? ? ? ? ? ? ? ? ? ? ? ? // 接受新的連接
? ? ? ? ? ? ? ? ? ? ? ? SocketChannel client = socketChannel.accept();
? ? ? ? ? ? ? ? ? ? ? ? // 設(shè)置讀非阻塞
? ? ? ? ? ? ? ? ? ? ? ? client.configureBlocking(false);
? ? ? ? ? ? ? ? ? ? ? ? // 注冊(cè)到多路復(fù)用器
? ? ? ? ? ? ? ? ? ? ? ? client.register(selector, SelectionKey.OP_READ);
? ? ? ? ? ? ? ? ? ? } else if (selectionKey.isReadable()) {
? ? ? ? ? ? ? ? ? ? ? ? // 通道可讀
? ? ? ? ? ? ? ? ? ? ? ? SocketChannel client = (SocketChannel) selectionKey.channel();
? ? ? ? ? ? ? ? ? ? ? ? int len = client.read(buffer);
? ? ? ? ? ? ? ? ? ? ? ? if (len > 0){
? ? ? ? ? ? ? ? ? ? ? ? ? ? buffer.flip();
? ? ? ? ? ? ? ? ? ? ? ? ? ? byte[] readArr = new byte[buffer.limit()];
? ? ? ? ? ? ? ? ? ? ? ? ? ? buffer.get(readArr);
? ? ? ? ? ? ? ? ? ? ? ? ? ? System.out.println(client.socket().getPort() + "端口數(shù)據(jù):" + new String(readArr));
? ? ? ? ? ? ? ? ? ? ? ? ? ? buffer.clear();
? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? } catch (Exception e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }
? ? }
}
API 案例:客戶(hù)端模擬
public class SecClient {
public static void main(String[] args) {
try {
// 連接服務(wù)端
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("127.0.0.1", 8089));
ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
String conVar = "[hello-8089]";
writeBuffer.put(conVar.getBytes()); writeBuffer.flip();
// 每隔5S發(fā)送一次數(shù)據(jù)
while (true) { Thread.sleep(5000);
writeBuffer.rewind();
socketChannel.write(writeBuffer);
writeBuffer.clear();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
SelectionKey 綁定 Selector 和 Chanel 之間的關(guān)聯(lián)螺句,并且可以獲取就緒狀態(tài)下的 Channel 集合明未。
IO 流同系列文章: