Java之NIO(非阻塞IO)

【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ì)忽略它谒撼。


image.png

② 從輸入通道中讀取 5 個(gè)字節(jié)數(shù)據(jù)寫入緩沖區(qū)中,此時(shí) position 移動(dòng)設(shè)置為 5雾狈,limit 保持不變廓潜。


image.png

③ 在將緩沖區(qū)的數(shù)據(jù)寫到輸出通道之前,需要先調(diào)用 flip() 方法善榛,這個(gè)方法將 limit 設(shè)置為當(dāng)前 position辩蛋,并將 position 設(shè)置為 0。

image.png

④ 從緩沖區(qū)中取 4 個(gè)字節(jié)到輸出緩沖中移盆,此時(shí) position 設(shè)為 4悼院。


image.png

⑤ 最后需要調(diào)用 clear() 方法來(lái)清空緩沖區(qū),此時(shí) position 和 limit 都被設(shè)置為最初位置咒循。

image.png

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)有意義。


image.png

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é)果:
image.png

效果對(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í)間為:
image.png

使用傳統(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í)間為:
image.png
<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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市类垦,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌城须,老刑警劉巖蚤认,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異糕伐,居然都是意外死亡砰琢,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門良瞧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)陪汽,“玉大人,你說(shuō)我怎么就攤上這事褥蚯≈吭” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵赞庶,是天一觀的道長(zhǎng)训挡。 經(jīng)常有香客問(wèn)我,道長(zhǎng)歧强,這世上最難降的妖魔是什么澜薄? 我笑而不...
    開(kāi)封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮摊册,結(jié)果婚禮上肤京,老公的妹妹穿的比我還像新娘。我一直安慰自己茅特,他們只是感情好忘分,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布棋枕。 她就那樣靜靜地躺著,像睡著了一般饭庞。 火紅的嫁衣襯著肌膚如雪戒悠。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天舟山,我揣著相機(jī)與錄音绸狐,去河邊找鬼。 笑死累盗,一個(gè)胖子當(dāng)著我的面吹牛寒矿,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播若债,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼符相,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了蠢琳?” 一聲冷哼從身側(cè)響起啊终,我...
    開(kāi)封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎傲须,沒(méi)想到半個(gè)月后蓝牲,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡泰讽,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年例衍,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片已卸。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡佛玄,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出累澡,到底是詐尸還是另有隱情梦抢,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布愧哟,位于F島的核電站惑申,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏翅雏。R本人自食惡果不足惜圈驼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望望几。 院中可真熱鬧绩脆,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至玉锌,卻和暖如春名挥,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背主守。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工禀倔, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人参淫。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓救湖,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親涎才。 傳聞我的和親對(duì)象是個(gè)殘疾皇子鞋既,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • Java NIO(New IO)是從Java 1.4版本開(kāi)始引入的一個(gè)新的IO API,可以替代標(biāo)準(zhǔn)的Java I...
    JackChen1024閱讀 7,555評(píng)論 1 143
  • Java NIO(New IO)是從Java 1.4版本開(kāi)始引入的一個(gè)新的IO API耍铜,可以替代標(biāo)準(zhǔn)的Java I...
    編碼前線閱讀 2,269評(píng)論 0 5
  • Java NIO(New IO)是從Java 1.4版本開(kāi)始引入的一個(gè)新的IO API邑闺,可以替代標(biāo)準(zhǔn)的Java I...
    zhisheng_blog閱讀 1,120評(píng)論 0 7
  • 原文 先來(lái)回顧一下傳統(tǒng)的IO模式的,將傳統(tǒng)的IO模式的相關(guān)類理清楚(因?yàn)镮O的類很多)棕兼。 但是陡舅,發(fā)現(xiàn)在整理的過(guò)程已...
    baby_honour閱讀 1,418評(píng)論 2 35
  • 感恩來(lái)訪者的信任,今天一連接待了我的三位來(lái)訪者程储,雖然時(shí)間安排的很滿,但陪伴他們心靈成長(zhǎng)的過(guò)程中臂寝。豐富了我的人生章鲤,增...
    善默勤容閱讀 103評(píng)論 0 0