【1】NIO的與IO的區(qū)別:
總的來(lái)說(shuō)java 中的IO 和NIO的區(qū)別主要有3點(diǎn):
1)IO是面向流的埠居,NIO是面向緩沖的查牌;
2)IO是阻塞的,NIO是非阻塞的滥壕;
3)IO是單線程的纸颜,NIO 是通過(guò)選擇器來(lái)模擬多線程的;
1. 通道
通道 Channel 是對(duì)原 I/O 包中的流的模擬绎橘,可以通過(guò)它讀取和寫入數(shù)據(jù)胁孙。
通道與流的不同之處在于,流只能在一個(gè)方向上移動(dòng)(一個(gè)流必須是 InputStream 或者 OutputStream 的子類),而通道是雙向的涮较,可以用于讀稠鼻、寫或者同時(shí)用于讀寫。
通道包括以下類型:
- FileChannel:從文件中讀寫數(shù)據(jù)狂票;
- DatagramChannel:通過(guò) UDP 讀寫網(wǎng)絡(luò)中數(shù)據(jù)候齿;
- SocketChannel:通過(guò) TCP 讀寫網(wǎng)絡(luò)中數(shù)據(jù);
- ServerSocketChannel:可以監(jiān)聽(tīng)新進(jìn)來(lái)的 TCP 連接闺属,對(duì)每一個(gè)新進(jìn)來(lái)的連接都會(huì)創(chuàng)建一個(gè) SocketChannel慌盯。
2. 緩沖區(qū)
發(fā)送給一個(gè)通道的所有數(shù)據(jù)都必須首先放到緩沖區(qū)中,同樣地屋剑,從通道中讀取的任何數(shù)據(jù)都要先讀到緩沖區(qū)中润匙。也就是說(shuō),不會(huì)直接對(duì)通道進(jìn)行讀寫數(shù)據(jù)唉匾,而是要先經(jīng)過(guò)緩沖區(qū)孕讳。
緩沖區(qū)實(shí)質(zhì)上是一個(gè)數(shù)組,但它不僅僅是一個(gè)數(shù)組巍膘。緩沖區(qū)提供了對(duì)數(shù)據(jù)的結(jié)構(gòu)化訪問(wèn)厂财,而且還可以跟蹤系統(tǒng)的讀/寫進(jìn)程。
緩沖區(qū)包括以下類型:
- ByteBuffer
- CharBuffer
- ShortBuffer
- IntBuffer
- LongBuffer
- FloatBuffer
- DoubleBuffer
緩沖區(qū)狀態(tài)變量
- capacity:最大容量峡懈;
- position:當(dāng)前已經(jīng)讀寫的字節(jié)數(shù)璃饱;
- limit:還可以讀寫的字節(jié)數(shù)。
狀態(tài)變量的改變過(guò)程舉例:
① 新建一個(gè)大小為 8 個(gè)字節(jié)的緩沖區(qū)肪康,此時(shí) position 為 0荚恶,而 limit = capacity = 8。capacity 變量不會(huì)改變磷支,下面的討論會(huì)忽略它谒撼。
② 從輸入通道中讀取 5 個(gè)字節(jié)數(shù)據(jù)寫入緩沖區(qū)中,此時(shí) position 移動(dòng)設(shè)置為 5雾狈,limit 保持不變廓潜。
③ 在將緩沖區(qū)的數(shù)據(jù)寫到輸出通道之前,需要先調(diào)用 flip() 方法善榛,這個(gè)方法將 limit 設(shè)置為當(dāng)前 position辩蛋,并將 position 設(shè)置為 0。
④ 從緩沖區(qū)中取 4 個(gè)字節(jié)到輸出緩沖中移盆,此時(shí) position 設(shè)為 4悼院。
⑤ 最后需要調(diào)用 clear() 方法來(lái)清空緩沖區(qū),此時(shí) position 和 limit 都被設(shè)置為最初位置咒循。
3. 選擇器
NIO 常常被叫做非阻塞 IO据途,主要是因?yàn)?NIO 在網(wǎng)絡(luò)通信中的非阻塞特性被廣泛使用钮呀。
NIO 實(shí)現(xiàn)了 IO 多路復(fù)用中的 Reactor 模型,一個(gè)線程 Thread 使用一個(gè)選擇器 Selector 通過(guò)輪詢的方式去監(jiān)聽(tīng)多個(gè)通道 Channel 上的事件昨凡,從而讓一個(gè)線程就可以處理多個(gè)事件。
通過(guò)配置監(jiān)聽(tīng)的通道 Channel 為非阻塞蚁署,那么當(dāng) Channel 上的 IO 事件還未到達(dá)時(shí)便脊,就不會(huì)進(jìn)入阻塞狀態(tài)一直等待,而是繼續(xù)輪詢其它 Channel光戈,找到 IO 事件已經(jīng)到達(dá)的 Channel 執(zhí)行哪痰。
因?yàn)閯?chuàng)建和切換線程的開(kāi)銷很大,因此使用一個(gè)線程來(lái)處理多個(gè)事件而不是一個(gè)線程處理一個(gè)事件久妆,對(duì)于 IO 密集型的應(yīng)用具有很好地性能晌杰。
應(yīng)該注意的是,只有套接字 Channel 才能配置為非阻塞筷弦,而 FileChannel 不能肋演,為 FileChannel 配置非阻塞也沒(méi)有意義。
NIO在基礎(chǔ)的IO流上發(fā)展處新的特點(diǎn)烂琴,分別是:內(nèi)存映射技術(shù)爹殊,字符及編碼,非阻塞I/O和文件鎖定奸绷。下面我們分別就這些技術(shù)做一些說(shuō)明
【2】NIO新特性:
內(nèi)存映射技術(shù)梗夸,字符及編碼,非阻塞I/O和文件鎖定
<1>內(nèi)存映射
這個(gè)功能主要是為了提高大文件的讀寫速度而設(shè)計(jì)的号醉。內(nèi)存映射文件(memory-mappedfile)能讓你創(chuàng)建和修改那些大到無(wú)法讀入內(nèi)存的文件反症。有了內(nèi)存映射文件,你就可以認(rèn)為文件已經(jīng)全部讀進(jìn)了內(nèi)存畔派,然后把它當(dāng)成一個(gè)非常大的數(shù)組來(lái)訪問(wèn)了铅碍。將文件的一段區(qū)域映射到內(nèi)存中,比傳統(tǒng)的文件處理速度要快很多父虑。內(nèi)存映射文件它雖然最終也是要從磁盤讀取數(shù)據(jù)该酗,但是它并不需要將數(shù)據(jù)讀取到OS內(nèi)核緩沖區(qū),而是直接將進(jìn)程的用戶私有地址空間中的一部分區(qū)域與文件對(duì)象建立起映射關(guān)系士嚎,就好像直接從內(nèi)存中讀呜魄、寫文件一樣,速度當(dāng)然快了莱衩。
NIO中內(nèi)存映射主要用到以下兩個(gè)類:
[1] java.nio.MappedByteBuffer
[2] java.nio.channels.FileChannel
下面我們通過(guò)一個(gè)例子來(lái)看一下內(nèi)存映射讀取文件和普通的IO流讀取一個(gè)大文件(文件大小為102603KB)的速度對(duì)比:
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
/**
* @author lm
* @create 2018-09-09 8:32
* @desc NIO特性之內(nèi)存映射
**/
public class MemoryMapper {
public static void main(String[] args) {
try {
RandomAccessFile file = new RandomAccessFile("F:\\IDEAWorkSpace\\AlgorithmTrain\\src\\com\\lm\\source\\triplets.txt","rw");
FileChannel channel = file.getChannel();
MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY,0,channel.size());
ByteBuffer buffer1 = ByteBuffer.allocate(1024);
byte[] b = new byte[1024];
long len = file.length();
long startTime = System.currentTimeMillis();
//讀取內(nèi)存映射文件
for(int i=0;i<file.length();i+=1024*10){
if (len - i > 1024) {
buffer.get(b);
} else {
buffer.get(new byte[(int)(len - i)]);
}
}
long endTime = System.currentTimeMillis();
System.out.println("使用內(nèi)存映射方式讀取文件總耗時(shí): "+(endTime - startTime));
//普通IO流方式
long startTime1 = System.currentTimeMillis();
while(channel.read(buffer1) > 0){
buffer1.flip();
buffer1.clear();
}
long endTime1 = System.currentTimeMillis();
System.out.println("使用普通IO流方式讀取文件總耗時(shí): "+(endTime1 - startTime1));
} catch (Exception e) {
e.printStackTrace();
}
}
}
運(yùn)行結(jié)果:效果對(duì)比還是挺明顯的爵嗅。我們看到在上面程序中調(diào)用FileChannel類的map方法進(jìn)行內(nèi)存映射,第一個(gè)參數(shù)設(shè)置映射模式,現(xiàn)在支持3種模式:
1)FileChannel.MapMode.READ_ONLY:只讀緩沖區(qū)笨蚁,在緩沖區(qū)中如果發(fā)生寫操作則會(huì)產(chǎn)生ReadOnlyBufferException睹晒;
2)FileChannel.MapMode.READ_WRITE:讀寫緩沖區(qū)趟庄,任何時(shí)刻如果通過(guò)內(nèi)存映射的方式修改了文件則立刻會(huì)對(duì)磁盤上的文件執(zhí)行相應(yīng)的修改操作。別的進(jìn)程如果也共享了同一個(gè)映射伪很,則也會(huì)同步看到變化戚啥。而不是像標(biāo)準(zhǔn)IO那樣每個(gè)進(jìn)程有各自的內(nèi)核緩沖區(qū),比如JAVA代碼中锉试,沒(méi)有執(zhí)行 IO輸出流的 flush() 或者 close() 操作猫十,那么對(duì)文件的修改不會(huì)更新到磁盤去,除非進(jìn)程運(yùn)行結(jié)束呆盖;
3)FileChannel.MapMode.PRIVATE :這個(gè)比較狠拖云,可寫緩沖區(qū),但任何修改是緩沖區(qū)私有的应又,不會(huì)回到文件中宙项。所以盡情的修改吧,結(jié)局跟突然停電是一樣的株扛。
內(nèi)存映射文件的優(yōu)點(diǎn):
<1>用戶進(jìn)程將文件數(shù)據(jù)視為內(nèi)存尤筐,因此不需要發(fā)出read()或write()系統(tǒng)調(diào)用。
<2>當(dāng)用戶進(jìn)程觸摸映射的內(nèi)存空間時(shí)席里,將自動(dòng)生成頁(yè)面錯(cuò)誤叔磷,以從磁盤引入文件數(shù)據(jù)。 如果用戶修改映射的內(nèi)存空間奖磁,受影響的頁(yè)面將自動(dòng)標(biāo)記為臟改基,并隨后刷新到磁盤以更新文件。
<3>操作系統(tǒng)的虛擬內(nèi)存子系統(tǒng)將執(zhí)行頁(yè)面的智能緩存咖为,根據(jù)系統(tǒng)負(fù)載自動(dòng)管理內(nèi)存秕狰。
<4>數(shù)據(jù)始終是頁(yè)面對(duì)齊的,不需要緩沖區(qū)復(fù)制躁染。
<5>可以映射非常大的文件鸣哀,而不消耗大量?jī)?nèi)存來(lái)復(fù)制數(shù)據(jù)。
對(duì)比使用NIO的內(nèi)存映射 和使用傳統(tǒng)IO流復(fù)制文件(文件大小為463486KB)性能:
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
/**
* @author lm
* @create 2018-09-09 14:51
* @desc NIO使用內(nèi)存映射進(jìn)行文件讀寫
**/
public class MemMapReadWrite {
private static int len;
/**
* 讀文件
*
* @param fileName
* @return
*/
public static ByteBuffer readFile(String fileName) {
try {
RandomAccessFile file = new RandomAccessFile(fileName, "rw");
len = (int) file.length();
FileChannel channel = file.getChannel();
MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, len);
return buffer.get(new byte[(int) file.length()]);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 寫文件
*
* @param readFileName
* @param writeFileName
*/
public static void writeFile(String readFileName, String writeFileName) {
try {
RandomAccessFile file = new RandomAccessFile(writeFileName, "rw");
FileChannel channel = file.getChannel();
ByteBuffer buffer = readFile(readFileName);
MappedByteBuffer bytebuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, len);
long startTime = System.currentTimeMillis();
for (int i = 0; i < len; i++) {
bytebuffer.put(i, buffer.get(i));
}
bytebuffer.flip();
long endTime = System.currentTimeMillis();
System.out.println("寫文件耗時(shí): " + (endTime - startTime));
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
String readFileName = "F:\\IDEAWorkSpace\\AlgorithmTrain\\src\\com\\lm\\source\\20180701_nt.txt";
String writeFileName = "F:\\IDEAWorkSpace\\AlgorithmTrain\\src\\com\\lm\\source\\copy_20180701_nt.txt";
writeFile(readFileName, writeFileName);
}
}
時(shí)間為:使用傳統(tǒng)IO流復(fù)制:
import java.io.*;
/**
* @author lm
* @create 2018-09-09 14:57
* @desc IO流進(jìn)行文件讀寫
**/
public class ReadWrite {
public static void main(String[] args) {
try {
//讀取源文件文件
String filePath = "F:\\IDEAWorkSpace\\AlgorithmTrain\\src\\com\\lm\\source\\20180701_nt.txt";
File file = new File(filePath);
InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream(file), "UTF-8");
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String line = bufferedReader.readLine();
String targetFilePath = "F:\\IDEAWorkSpace\\AlgorithmTrain\\src\\com\\lm\\source\\copyWithIO_20180701_nt.txt";
//輸出流
File fileOut = new File(targetFilePath);
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(new FileOutputStream(fileOut), "UTF-8");
BufferedWriter bufferedWriter = new BufferedWriter(outputStreamWriter);
long startTime = System.currentTimeMillis();
while (bufferedReader.readLine() != null) {
bufferedWriter.write(line);
bufferedWriter.newLine();
}
long endTime = System.currentTimeMillis();
System.out.println("寫文件耗時(shí): " + (endTime - startTime));
//關(guān)閉輸出流
bufferedWriter.close();
outputStreamWriter.close();
//關(guān)閉輸入流
bufferedReader.close();
inputStreamReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
時(shí)間為:<2>多路復(fù)用IO
在多路復(fù)用IO模型中吞彤,會(huì)有一個(gè)線程不斷去輪詢多個(gè)socket的狀態(tài)我衬,只有當(dāng)socket真正有讀寫事件時(shí),才真正調(diào)用實(shí)際的IO讀寫操作饰恕。因?yàn)樵诙嗦窂?fù)用IO模型中挠羔,只需要使用一個(gè)線程就可以管理多個(gè)socket,系統(tǒng)不需要建立新的進(jìn)程或者線程埋嵌,也不必維護(hù)這些線程和進(jìn)程破加,并且只有在真正有socket讀寫事件進(jìn)行時(shí),才會(huì)使用IO資源雹嗦,所以它大大減少了資源占用范舀。
NIO 的非阻塞 I/O 機(jī)制是圍繞 選擇器和 通道構(gòu)建的合是。 Channel 類表示服務(wù)器和客戶機(jī)之間的一種通信機(jī)制。Selector 類是 Channel 的多路復(fù)用器锭环。 Selector 類將傳入客戶機(jī)請(qǐng)求多路分用并將它們分派到各自的請(qǐng)求處理程序聪全。NIO 設(shè)計(jì)背后的基石是反應(yīng)器(Reactor)設(shè)計(jì)模式。
關(guān)于Reactor模式在此就不多做介紹辅辩,網(wǎng)上很多荔烧。Reactor負(fù)責(zé)IO事件的響應(yīng),一旦有事件發(fā)生汽久,便廣播發(fā)送給相應(yīng)的handler去處理。而NIO的設(shè)計(jì)則是完全按照Reactor模式來(lái)設(shè)計(jì)的踊餐。Selector發(fā)現(xiàn)某個(gè)channel有數(shù)據(jù)時(shí)景醇,會(huì)通過(guò)SelectorKey來(lái)告知,然后實(shí)現(xiàn)事件和handler的綁定吝岭。
在Reactor模式中,包含如下角色:
1)Reactor 將I/O事件發(fā)派給對(duì)應(yīng)的Handler
2)Acceptor 處理客戶端連接請(qǐng)求
3)Handlers 執(zhí)行非阻塞讀/寫
一個(gè)利用了Reactor模式的NIO服務(wù)端:
public class NIOServer {
private static final Logger LOGGER = LoggerFactory.getLogger(NIOServer.class);
public static void main(String[] args) throws IOException {
Selector selector = Selector.open();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.bind(new InetSocketAddress(1234));
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
if(selector.selectNow() < 0) {
continue;
}
//獲取注冊(cè)的channel
Set<SelectionKey> keys = selector.selectedKeys();
//遍歷所有的key
Iterator<SelectionKey> iterator = keys.iterator();
while(iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
//如果通道上有事件發(fā)生
if (key.isAcceptable()) {
//獲取該通道
ServerSocketChannel acceptServerSocketChannel = (ServerSocketChannel) key.channel();
SocketChannel socketChannel = acceptServerSocketChannel.accept();
socketChannel.configureBlocking(false);
LOGGER.info("Accept request from {}", socketChannel.getRemoteAddress());
//同時(shí)將SelectionKey標(biāo)記為可讀,以便讀取隅津。
SelectionKey readKey = socketChannel.register(selector, SelectionKey.OP_READ);
//利用SelectionKey的attache功能綁定Acceptor 如果有事情韧涨,觸發(fā)Acceptor
//Processor對(duì)象為自定義處理請(qǐng)求的類
readKey.attach(new Processor());
} else if (key.isReadable()) {
Processor processor = (Processor) key.attachment();
processor.process(key);
}
}
}
}
}
/**
* Processor類中設(shè)置一個(gè)線程池來(lái)處理請(qǐng)求,
* 這樣就可以充分利用多線程的優(yōu)勢(shì)
*/
class Processor {
private static final Logger LOGGER = LoggerFactory.getLogger(Processor.class);
private static final ExecutorService service = Executors.newFixedThreadPool(16);
public void process(final SelectionKey selectionKey) {
service.submit(new Runnable() {
@Override
public void run() {
ByteBuffer buffer = null;
SocketChannel socketChannel = null;
try {
buffer = ByteBuffer.allocate(1024);
socketChannel = (SocketChannel) selectionKey.channel();
int count = socketChannel.read(buffer);
if (count < 0) {
socketChannel.close();
selectionKey.cancel();
LOGGER.info("{}\t Read ended", socketChannel);
} else if(count == 0) {
}
} catch (IOException e) {
e.printStackTrace();
}
LOGGER.info("{}\t Read message {}", socketChannel, new String(buffer.array()));
}
});
}
}
這種方式帶來(lái)的好處也是不言而喻的幕帆。利用多路復(fù)用機(jī)制避免了線程的阻塞获搏,提高了連接的數(shù)量。一個(gè)線程就可以管理多個(gè)socket失乾,只有當(dāng)socket真正有讀寫事件發(fā)生才會(huì)占用資源來(lái)進(jìn)行實(shí)際的讀寫操作常熙。雖然多線程+ 阻塞IO 達(dá)到類似的效果,但是由于在多線程 + 阻塞IO 中碱茁,每個(gè)socket對(duì)應(yīng)一個(gè)線程裸卫,這樣會(huì)造成很大的資源占用,并且尤其是對(duì)于長(zhǎng)連接來(lái)說(shuō)纽竣,線程的資源一直不會(huì)釋放墓贿,如果后面陸續(xù)有很多連接的話,就會(huì)造成性能上的瓶頸蜓氨。
另外多路復(fù)用IO為何比非阻塞IO模型的效率高是因?yàn)樵诜亲枞鸌O中聋袋,不斷地詢問(wèn)socket狀態(tài)時(shí)通過(guò)用戶線程去進(jìn)行的,而在多路復(fù)用IO中语盈,輪詢每個(gè)socket狀態(tài)是內(nèi)核在進(jìn)行的舱馅,這個(gè)效率要比用戶線程要高的多。
<3>文件鎖定
NIO中的文件通道(FileChannel)在讀寫數(shù)據(jù)的時(shí)候主 要使用了阻塞模式刀荒,它不能支持非阻塞模式的讀寫代嗤,而且FileChannel的對(duì)象是不能夠直接實(shí)例化的棘钞, 他的實(shí)例只能通過(guò)getChannel()從一個(gè)打開(kāi)的文件對(duì)象上邊讀取(RandomAccessFile干毅、 FileInputStream宜猜、FileOutputStream),并且通過(guò)調(diào)用getChannel()方法返回一個(gè) Channel對(duì)象去連接同一個(gè)文件硝逢,也就是針對(duì)同一個(gè)文件進(jìn)行讀寫操作姨拥。
文件鎖的出現(xiàn)解決了很多Java應(yīng)用程序和非Java程序之間共享文件數(shù)據(jù)的問(wèn)題,在以前的JDK版本中渠鸽,沒(méi)有文件鎖機(jī)制使得Java應(yīng)用程序和其他非Java進(jìn)程程序之間不能夠針對(duì)同一個(gè)文件共享 數(shù)據(jù)叫乌,有可能造成很多問(wèn)題,JDK1.4里面有了FileChannel徽缚,它的鎖機(jī)制使得文件能夠針對(duì)很多非 Java應(yīng)用程序以及其他Java應(yīng)用程序可見(jiàn)憨奸。但是Java里面 的文件鎖機(jī)制主要是基于共 享鎖模型,在不支持共享鎖模型的操作系統(tǒng)上凿试,文件鎖本身也起不了作用排宰,JDK1.4使用文件通道讀寫方式可以向一些文件 發(fā)送鎖請(qǐng)求,
FileChannel的 鎖模型主要針對(duì)的是每一個(gè)文件那婉,并不是每一個(gè)線程和每一個(gè)讀寫通道板甘,也就是以文件為中心進(jìn)行共享以及獨(dú)占,也就是文件鎖本身并不適合于同一個(gè)JVM的不同 線程之間详炬。
相應(yīng)api:
// 如果請(qǐng)求的鎖定范圍是有效的盐类,阻塞直至獲取鎖
public final FileLock lock()
// 嘗試獲取鎖非阻塞,立刻返回結(jié)果
public final FileLock tryLock()
// 第一個(gè)參數(shù):要鎖定區(qū)域的起始位置
// 第二個(gè)參數(shù):要鎖定區(qū)域的尺寸,
// 第三個(gè)參數(shù):true為共享鎖呛谜,false為獨(dú)占鎖
public abstract FileLock lock (long position, long size, boolean shared)
public abstract FileLock tryLock (long position, long size, boolean shared)
鎖定區(qū)域的范圍不一定要限制在文件的size值以內(nèi)傲醉,鎖可以擴(kuò)展從而超出文件尾。因此呻率,我們可以提前把待寫入數(shù)據(jù)的區(qū)域鎖定硬毕,我們也可以鎖定一個(gè)不包含任何文件內(nèi)容的區(qū)域,比如文件最后一個(gè)字節(jié)以外的區(qū)域礼仗。如果之后文件增長(zhǎng)到達(dá)那塊區(qū)域吐咳,那么你的文件鎖就可以保護(hù)該區(qū)域的文件內(nèi)容了。相反地元践,如果你鎖定了文件的某一塊區(qū)域韭脊,然后文件增長(zhǎng)超出了那塊區(qū)域,那么新增加 的文件內(nèi)容將不會(huì)受到您的文件鎖的保護(hù)单旁。
文件鎖示例:
public class NIOLock {
private static final Logger LOGGER = LoggerFactory.getLogger(NIOServer.class);
public static void main(String[] args) throws IOException {
FileChannel fileChannel = new RandomAccessFile("c://1.txt", "rw").getChannel();
// 寫入4個(gè)字節(jié)
fileChannel.write(ByteBuffer.wrap("abcd".getBytes()));
// 將前2個(gè)字節(jié)區(qū)域鎖定(共享鎖)
FileLock lock1 = fileChannel.lock(0, 2, true);
// 當(dāng)前鎖持有鎖的類型(共享鎖/獨(dú)占鎖)
lock1.isShared();
// IOException 不能修改只讀的共享區(qū)域
// fileChannel.write(ByteBuffer.wrap("a".getBytes()));
// 可以修改共享鎖之外的區(qū)域沪羔,從第三個(gè)字節(jié)開(kāi)始寫入
fileChannel.write(ByteBuffer.wrap("ef".getBytes()), 2);
// OverlappingFileLockException 重疊的文件鎖異常
// FileLock lock2 = fileChannel.lock(0, 3, true);
// FileLock lock3 = fileChannel.lock(0, 3, false);
//得到創(chuàng)建鎖的通道
lock1.channel();
//鎖的起始位置
long position = lock1.position();
//鎖的范圍
long size = lock1.size();
//判斷鎖是否與指定文件區(qū)域有重疊
lock1.overlaps(position, size);
// 記得用try/catch/finally{release()}方法釋放鎖
lock1.release();
}
}
完整的NIO Socket客戶端和服務(wù)端代碼示例:
服務(wù)端:
public class Server {
//標(biāo)識(shí)數(shù)字/
private int flag = 0;
//緩沖區(qū)大小/
private int BLOCK = 4096;
//接受數(shù)據(jù)緩沖區(qū)/
private ByteBuffer sendbuffer = ByteBuffer.allocate(BLOCK);
//發(fā)送數(shù)據(jù)緩沖區(qū)/
private ByteBuffer receivebuffer = ByteBuffer.allocate(BLOCK);
private Selector selector;
public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
int port = 7788;
Server server = new Server(port);
server.listen();
}
public Server(int port) throws IOException {
// 打開(kāi)服務(wù)器套接字通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 服務(wù)器配置為非阻塞
serverSocketChannel.configureBlocking(false);
// 檢索與此通道關(guān)聯(lián)的服務(wù)器套接字
ServerSocket serverSocket = serverSocketChannel.socket();
// 進(jìn)行服務(wù)的綁定
serverSocket.bind(new InetSocketAddress(port));
// 通過(guò)open()方法找到Selector
selector = Selector.open();
// 注冊(cè)到selector,等待連接
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("Server Start----7788:");
}
// 監(jiān)聽(tīng)
private void listen() throws IOException {
while (true) {
// 選擇一組鍵,并且相應(yīng)的通道已經(jīng)打開(kāi)
selector.select();
// 返回此選擇器的已選擇鍵集蔫饰。
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
iterator.remove();
handleKey(selectionKey);
}
}
}
// 處理請(qǐng)求
private void handleKey(SelectionKey selectionKey) throws IOException {
// 接受請(qǐng)求
ServerSocketChannel server = null;
SocketChannel client = null;
String receiveText;
String sendText;
int count = 0;
// 測(cè)試此鍵的通道是否已準(zhǔn)備好接受新的套接字連接琅豆。
if (selectionKey.isAcceptable()) {
// 返回為之創(chuàng)建此鍵的通道。
server = (ServerSocketChannel) selectionKey.channel();
// 接受到此通道套接字的連接篓吁。
// 此方法返回的套接字通道(如果有)將處于阻塞模式茫因。
client = server.accept();
// 配置為非阻塞
client.configureBlocking(false);
// 注冊(cè)到selector,等待連接
client.register(selector, SelectionKey.OP_READ);
} else if (selectionKey.isReadable()) {
// 返回為之創(chuàng)建此鍵的通道杖剪。
client = (SocketChannel) selectionKey.channel();
//將緩沖區(qū)清空以備下次讀取
receivebuffer.clear();
//讀取服務(wù)器發(fā)送來(lái)的數(shù)據(jù)到緩沖區(qū)中
count = client.read(receivebuffer);
if (count > 0) {
receiveText = new String(receivebuffer.array(), 0, count);
System.out.println("服務(wù)器端接受客戶端數(shù)據(jù)--:" + receiveText);
client.register(selector, SelectionKey.OP_WRITE);
}
} else if (selectionKey.isWritable()) {
//將緩沖區(qū)清空以備下次寫入
sendbuffer.clear();
// 返回為之創(chuàng)建此鍵的通道冻押。
client = (SocketChannel) selectionKey.channel();
sendText = "message from server--" + flag++;
//向緩沖區(qū)中輸入數(shù)據(jù)
sendbuffer.put(sendText.getBytes());
//將緩沖區(qū)各標(biāo)志復(fù)位,因?yàn)橄蚶锩鎝ut了數(shù)據(jù)標(biāo)志被改變要想從中讀取數(shù)據(jù)發(fā)向服務(wù)器,就要復(fù)位
sendbuffer.flip();
//輸出到通道
client.write(sendbuffer);
System.out.println("服務(wù)器端向客戶端發(fā)送數(shù)據(jù)--:" + sendText);
client.register(selector, SelectionKey.OP_READ);
}
}
}
客戶端:
public class Client {
//標(biāo)識(shí)數(shù)字/
private static int flag = 0;
//緩沖區(qū)大小/
private static int BLOCK = 4096;
//接受數(shù)據(jù)緩沖區(qū)/
private static ByteBuffer sendbuffer = ByteBuffer.allocate(BLOCK);
//發(fā)送數(shù)據(jù)緩沖區(qū)/
private static ByteBuffer receivebuffer = ByteBuffer.allocate(BLOCK);
//服務(wù)器端地址/
private final static InetSocketAddress SERVER_ADDRESS = new InetSocketAddress(
"localhost", 7788);
public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
// 打開(kāi)socket通道
SocketChannel socketChannel = SocketChannel.open();
// 設(shè)置為非阻塞方式
socketChannel.configureBlocking(false);
// 打開(kāi)選擇器
Selector selector = Selector.open();
// 注冊(cè)連接服務(wù)端socket動(dòng)作
socketChannel.register(selector, SelectionKey.OP_CONNECT);
// 連接
socketChannel.connect(SERVER_ADDRESS);
// 分配緩沖區(qū)大小內(nèi)存
Set<SelectionKey> selectionKeys;
Iterator<SelectionKey> iterator;
SelectionKey selectionKey;
SocketChannel client;
String receiveText;
String sendText;
int count = 0;
while (true) {
//選擇一組鍵,其相應(yīng)的通道已為 I/O 操作準(zhǔn)備就緒盛嘿。
//此方法執(zhí)行處于阻塞模式的選擇操作洛巢。
selector.select();
//返回此選擇器的已選擇鍵集。
selectionKeys = selector.selectedKeys();
//System.out.println(selectionKeys.size());
iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
selectionKey = iterator.next();
if (selectionKey.isConnectable()) {
System.out.println("client connect");
client = (SocketChannel) selectionKey.channel();
// 判斷此通道上是否正在進(jìn)行連接操作次兆。
// 完成套接字通道的連接過(guò)程狼渊。
if (client.isConnectionPending()) {
client.finishConnect();
System.out.println("完成連接!");
sendbuffer.clear();
sendbuffer.put("Hello,Server".getBytes());
sendbuffer.flip();
client.write(sendbuffer);
}
client.register(selector, SelectionKey.OP_READ);
} else if (selectionKey.isReadable()) {
client = (SocketChannel) selectionKey.channel();
//將緩沖區(qū)清空以備下次讀取
receivebuffer.clear();
//讀取服務(wù)器發(fā)送來(lái)的數(shù)據(jù)到緩沖區(qū)中
count = client.read(receivebuffer);
if (count > 0) {
receiveText = new String(receivebuffer.array(), 0, count);
System.out.println("客戶端接受服務(wù)器端數(shù)據(jù)--:" + receiveText);
client.register(selector, SelectionKey.OP_WRITE);
}
} else if (selectionKey.isWritable()) {
sendbuffer.clear();
client = (SocketChannel) selectionKey.channel();
sendText = "message from client--" + (flag++);
sendbuffer.put(sendText.getBytes());
//將緩沖區(qū)各標(biāo)志復(fù)位,因?yàn)橄蚶锩鎝ut了數(shù)據(jù)標(biāo)志被改變要想從中讀取數(shù)據(jù)發(fā)向服務(wù)器,就要復(fù)位
sendbuffer.flip();
client.write(sendbuffer);
System.out.println("客戶端向服務(wù)器端發(fā)送數(shù)據(jù)--:" + sendText);
client.register(selector, SelectionKey.OP_READ);
}
}
selectionKeys.clear();
}
}
}
【引用文章】:https://blog.csdn.net/a953713428/article/details/64907250