Java梳理之理解NIO(二)

由上篇文章中知道,通道Channel和緩沖器Buffer兩者需要共同作用,但是選擇器Selector只會作用在繼承了抽象類SelectableChannel的網(wǎng)絡IO中,下面由簡單的FileChannel開始了解nio包的使用。

FileChannel

之前已經(jīng)說過,在java 1.4的時候,改寫了傳統(tǒng)IO包下的三個類用以生成通道類FileChannel,這里使用緩沖器類ByteBuffer來對文件進行操作,如下所示:

/**
**FileChannel示例 文件拷貝
**/
public static void main(String[] arg0) throws IOException{
        FileChannel inputChanel = new FileInputStream("/test/test.text").getChannel();
        FileChannel outputChanel = new FileOutputStream("/test/test1.text").getChannel();   
        ByteBuffer buf = ByteBuffer.allocate(1024);
        int len = 0;
        while((len = inputChanel.read(buf))!= -1){
            buf.flip();
            outputChanel.write(buf);
            buf.clear();
        }       
    }

如上所示,代碼會將test.text中的數(shù)據(jù)拷貝到test1.text中,其中將通道中的數(shù)據(jù)讀取到ByteBuffer后,經(jīng)過三個步驟,即flip()方法調(diào)用,調(diào)用寫通道outputChannelwrite()方法,最后調(diào)用ByteBufferclear()方法。雖說完成了功能,但是這種方法的效率并不是很高的,因為每一次讀的時候都需要在while循環(huán)中調(diào)用了幾步。在FileChannel類中還有兩個方法transferFrom(ReadableByteChannel src,long position, long count)transferTo(long position, long count,WritableByteChannel target),可以通過這兩個方法來做到文件拷貝,如下所示:

/**
**FileChannel示例 文件拷貝 transferTo() & transferFrom()
**/
public static void main(String[] arg0) throws IOException{
        FileChannel inputChanel = new FileInputStream("/test/nio1.text").getChannel();
        FileChannel outputChanel = new FileOutputStream("/test/test.text").getChannel();    
//      inputChanel.transferTo(0, inputChanel.size(), outputChanel);
        outputChanel.transferFrom(inputChanel, 0, inputChanel.size());
    }

上面的例子運行完之后,通過將兩個通道連接也能正確的拷貝文件。需要注意的是文中的代碼為了簡便全部是直接拋出異常也沒有關閉流,如果實際書寫請酌情處理。看了FileChannel操作普通文件,那么可以看一下怎么操作大文件的,記得之前說過這樣一個類MappedByteBuffer,使用它即可快速操作,如下:

/**
**MappedByteBuffer示例 操作文件
**/
public static void main(String[] arg0) throws IOException{
        FileChannel randomAccessChannel = new RandomAccessFile("/test/nio1.text", "rw").getChannel();
        FileChannel outputChannel = new FileOutputStream("/test/test.text").getChannel();
        long size = randomAccessChannel.size();
        MappedByteBuffer mapBuffer = randomAccessChannel.map(MapMode.READ_ONLY, 0, size);
        int len = 1024;
        byte[] buf = new byte[len];
        long cycle = size/len;
        while(mapBuffer.hasRemaining()&&cycle>=0){
            if(cycle==0){
                len = (int)size % len;
            }
            mapBuffer.get(buf,0,len);
            System.out.println("--"+new String(buf,0,len));
            cycle--;
        }
    }

如上通過類FileChannel映射文件,會打印出文件中的所有數(shù)據(jù)。在這里對于速度的提升可能無法看出,但是換一個大文件對比一下普通的ByteBuffer就可以很容易的看出來,如下所示:

/**
**MappedByteBuffer對比普通ByteBuffer
**/
public static void main(String[] arg0) throws IOException{
        FileChannel randomAccessChannel = new RandomAccessFile("/test/cpicgxwx.war", "r").getChannel();
        long size = randomAccessChannel.size();
        long cur = System.currentTimeMillis();
        MappedByteBuffer mapBuffer = randomAccessChannel.map(MapMode.READ_ONLY, 0, size);
        System.out.println("MappedByteBuffer spend "+(System.currentTimeMillis()-cur));
        ByteBuffer buf = ByteBuffer.allocate(1024*1024*150);
        cur = System.currentTimeMillis();
        randomAccessChannel.read(buf);
        System.out.println("ByteBuffer spend "+(System.currentTimeMillis()-cur));
}
輸出:
MappedByteBuffer spend 4
ByteBuffer spend 259

這樣很明顯就看的出來兩者的效率,對于一個大于100M的文件,MappedByteBufferByteBuffer效率高了60多倍,更不用說幾個G大小的文件了。
這里會不會有疑惑,為什么類MappedByteBuffer可以這么快?其實是因為它并不是讀取文件到內(nèi)存中而是像類名Mapped一樣映射文件。當然這個類其實也存在一些問題,如內(nèi)存占用和文件關閉,被類MappedByteBuffer打開的文件只有在垃圾收集的時候才關閉,而這個垃圾收集的點是不確定的,在網(wǎng)上有人提供了這樣一個方法來關閉這個文件映射,如下所示:

/**
**MappedByteBuffer 文件映射 Clear()
**/
public static void main(String[] arg0) throws Exception{
        File file = new File("/test/nio.text");
        RandomAccessFile randomAccessFile = new RandomAccessFile(file,"r");
        FileChannel channel = randomAccessFile.getChannel();
        long size = channel.size();
        long cur = System.currentTimeMillis();
        MappedByteBuffer mapBuffer = channel.map(MapMode.READ_ONLY, 0, size);
        System.out.println("MappedByteBuffer spend "+(System.currentTimeMillis()-cur));
        ByteBuffer buf = ByteBuffer.allocate(1024*1024*150);
        cur = System.currentTimeMillis();
        channel.read(buf);
        System.out.println("ByteBuffer spend "+(System.currentTimeMillis()-cur));
        channel.close();
        randomAccessFile.close();
//      clean(mapBuffer);
        System.out.println(file.delete());
    }
public static void clean(final Object buffer) throws Exception {
        AccessController.doPrivileged(new PrivilegedAction() {
            public Object run() {
            try {
               Method getCleanerMethod = buffer.getClass().getMethod("cleaner",new Class[0]);
               getCleanerMethod.setAccessible(true);
               sun.misc.Cleaner cleaner =(sun.misc.Cleaner)getCleanerMethod.invoke(buffer,new Object[0]);
               cleaner.clean();
            } catch(Exception e) {
               e.printStackTrace();
            }
               return null;}});
        
    }
輸出:
MappedByteBuffer spend 0
ByteBuffer spend 157
false

如上所示:在注銷掉clean(mapBuffer);時,由于系統(tǒng)還持有這個文件的句柄,無法刪除文件,導致打印出false,加上這個方法調(diào)用后就可以了。除了反射調(diào)用sun.misc.Cleaner cleaner外,還可以直接調(diào)用,如下:

/**
**sun.misc.Cleaner調(diào)用
**/
public static void unmap(MappedByteBuffer buffer){
       sun.misc.Cleaner cleaner = ((DirectBuffer) buffer).cleaner();
       cleaner.clean(); 
    }

這樣調(diào)用也是可以刪除文件的,這樣就補足了上面所說的問題。

在java 1.4還加入了文件加鎖機制FileLock,它允許我們同步訪問某個作為共享資源的文件,這個文件鎖直接通過映射本地操作系統(tǒng)的加鎖工具,所以對其他操作系統(tǒng)的進程也是可見的。這個文件鎖可以通過FileChanneltryLock()lock()方法獲取,當然也提供了有參數(shù)的方法來加鎖文件的一部分,如lock(long position, long size, boolean shared)tryLock(long position, long size, boolean shared),這里參數(shù)boolean shared指是否共享鎖。這個就不放實例了,感興趣可以試試。
下面看看其他的網(wǎng)絡Channel

SocketChannel

在上一篇就說道NIO的強大功能部分來自它的非阻塞特性,這一點在網(wǎng)絡IO中效果表現(xiàn)的更加明顯,如對ServerSocketaccept()方法會等待某一個客戶端連接而導致阻塞,或者InputStreamread方法阻塞到數(shù)據(jù)完全讀完。一般來說,我們在調(diào)用一個方法之前并不知道它是不是會阻塞,但是NIO提供了這樣的方法來配置它的阻塞行為,以實現(xiàn)非阻塞信道。

/**
**nio非阻塞信道配置
**/
channel.configureBlocking(false);

非阻塞信道的優(yōu)勢在于它調(diào)用的方法都會有一個即時返回,用來指示所請求的操作的完成程度。下面用一個例子來演示,客戶端使用NIO非阻塞信道,服務器使用IO實現(xiàn),如下:

/**
**SocketChannel示例
**/
public static void main(String[] arg0) throws InterruptedException{
        new Thread(){
            public void run() {
                    server();
            }
        }.start();
        new Thread(){
            public void run() {
                try {
                    client();
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }.start();
        
        
    }
    public static void client() throws InterruptedException{
        SocketChannel client = null;
        ByteBuffer buf = ByteBuffer.allocate(1024);
        try {
            client = SocketChannel.open();
            client.configureBlocking(false);
            client.connect(new InetSocketAddress("192.168.191.5",8080));
            if(client.finishConnect()){
                int i = 0;
                while(true){
                    Thread.sleep(3000);
                    String test = "test "+i+" from client";
                    i++;
                    buf.clear();
                    buf.put(test.getBytes());
                    buf.flip();
                    while(buf.hasRemaining()){
                        System.out.println(buf);
                        client.write(buf);
                    }
                }
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    
    public static void server(){
        ServerSocket server = null;
        InputStream in = null;
        
        try {
            server = new ServerSocket(8080);
             int recvMsgSize = 0;
             byte[] recvBuf = new byte[1024];
             while(true){
                  System.out.println("mark in server 1");
                  Socket clntSocket = server.accept();
                  System.out.println("mark in server 2");
                  SocketAddress clientAddress = clntSocket.getRemoteSocketAddress();
                  System.out.println("Handling client at "+clientAddress);
                  in = clntSocket.getInputStream();
                  while((recvMsgSize=in.read(recvBuf))!=-1){
                      byte[] temp = new byte[recvMsgSize];
                      System.arraycopy(recvBuf, 0, temp, 0, recvMsgSize);
                      System.out.println(new String(temp));
                  }
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
輸出:
mark in server 1
mark in server 2
Handling client at /192.168.191.5:57982
java.nio.HeapByteBuffer[pos=0 lim=18 cap=1024]
test 0 from client
java.nio.HeapByteBuffer[pos=0 lim=18 cap=1024]
test 1 from client
...

在上面的例子中,如果將client方法先啟動就會出現(xiàn)只打印mark in server 1,后面就不會打印了,造成這樣的情況是因為client.finishConnect()方法返回false直接往程序后面跑了,并不會繼續(xù)阻塞直到連上服務端,但是換過個順序讓server先啟動時,返回true就能連接成功,因為會在accept()方法阻塞,直到有客戶端client()連接。這里就可以看到這個非阻塞的特點。

當然不僅僅只有客戶端client有這也的非阻塞特性,服務器端也是存在的。

ServerSocketChannel

類似于類SocketChannel,網(wǎng)絡IO服務器端的非阻塞特性是通過類ServerSocketChannel來實現(xiàn)的。可以從下面這個例子看出:

/**
**ServerSocketChannel示例
**/
public static void server(){
        ServerSocketChannel server = null;
        try {
            server = ServerSocketChannel.open();
            server.socket().bind(new InetSocketAddress(8080));
            server.configureBlocking(false);
            ByteBuffer buf = ByteBuffer.allocate(1024);
            byte[] bytes = new byte[512];
            System.out.println("--服務器啟動-- ");
            while(true){
                  SocketChannel socket = server.accept();
                 while(socket!=null&&socket.isConnected()){
                     buf.clear();
                     int len = socket.read(buf);
                     if(len == -1){
                         socket.close();
                         System.out.println("連接斷開");
                     }
                     buf.flip();
                     while(buf.hasRemaining()){
                         buf.get(bytes,0,buf.remaining());
                         System.out.println(new String(bytes));
                     }
                 }
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
輸出:
--服務器啟動-- 
test 0 from client
test 1 from client
...

這個例子簡單的將上一個例子改換了一下(其中的各種異常和資源的操作都沒有完善,實際開發(fā)請勿使用),服務器如果將 while(socket!=null)換成if(socket!=null),那么這個程序只會打印前面一次從客戶端發(fā)送過來的數(shù)據(jù),連接后只讀取了一次。在這里通過int len = socket.read(buf);中的len判斷后面是否還會有傳來數(shù)據(jù),如果數(shù)據(jù)長度是-1,則關閉連接。這樣改過后就是非阻塞的網(wǎng)絡IO。

直到這里,都只是使用了前面兩個重要的概念BufferChannel,現(xiàn)在可以了解選擇器Selector并配合使用。

Selector & SelectionKey

選擇器這部分,不僅僅只有類Selector,它還有一個特別重要的類SelectionKey。在之前的文章中簡單的了解過這兩個類,這里可以回顧一下:要注冊Selector則需要這個Channel繼承類SelectableChannel,這里只有通道類FileChannel沒有繼承;注冊的Selector實體可以返回一個SelectionKey集合,通過這個集合可以對不同的通道做出相應的操作,這樣就避免了傳統(tǒng)的網(wǎng)絡IO為每一個連接創(chuàng)建一個線程而花費大量的資源,只用一個線程就可以解決問題。Selector管理多個Channel的結構圖如下所示:

Selector結構圖.png

下面可以看一下結合了選擇器類Selector后構建的簡單服務器代碼:

/**
**Selector 示例,簡單服務器
**/
public static void server(){
        System.out.println("--開始啟動服務器");
        Selector selector = null;
        ServerSocketChannel server = null;
        try {
            server = ServerSocketChannel.open();            
            server.configureBlocking(false);
            server.socket().bind(new InetSocketAddress(8080));
            System.out.println("--監(jiān)聽8080端口");
            selector = Selector.open();
            server.register(selector,SelectionKey.OP_ACCEPT);
            System.out.println("--服務器已啟動成功");
            while(true){
                 int num = selector.select();
                 if(num == 0){
                     continue;
                 }
                 Iterator<SelectionKey> selectionKeys = selector.selectedKeys().iterator();
                 while(selectionKeys.hasNext()){
                     SelectionKey selectionKey = selectionKeys.next();
                     selectionKeys.remove();
                     if(selectionKey.isAcceptable()){
                         System.out.println("-連接請求:"); 
                         ServerSocketChannel serverSocket = (ServerSocketChannel)selectionKey.channel();
                         SocketChannel socket = serverSocket.accept();
                         socket.configureBlocking(false);
                         socket.register(selector, SelectionKey.OP_READ);
                     }else if(selectionKey.isReadable() && selectionKey.isValid()){
                         System.out.println("-讀取:"); 
                         SocketChannel channel = (SocketChannel) selectionKey.channel();
                         ByteBuffer buf = ByteBuffer.allocate(1024);
                         byte[] bytes = new byte[1024];
                         while(channel.isConnected()){
                             buf.clear();
                             int len = channel.read(buf);
                             if(len == -1){
                                 channel.close();
                                 selector.selectNow();
                                 System.out.println("-連接關閉:"+channel.isConnected());
                             }
                             if(len > 0){
                                 channel.write(ByteBuffer.wrap("收到消息啦".getBytes()));
                                 System.out.println(buf+"-buf-length -"+len);
                                 buf.flip();
                                 while(buf.hasRemaining()){
                                     buf.get(bytes, 0, len);
                                     System.out.println(new String(bytes));
                                 }
                             }
                             
                        }
                         
                     }
                 }
                 
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
輸出:
--開始啟動服務器
--監(jiān)聽8080端口
--服務器已啟動成功
-連接請求:
-讀取:
java.nio.HeapByteBuffer[pos=18 lim=1024 cap=1024]-buf-length -18
test 0 from client
...

上面的例子中,通過selector注冊相應的通道,通過selector獲取的selectionkey來做對應的動作。在這個例子一直遇到了異常如ClosedChannelException或者提示遠程關閉了一個連接,導致一直很疑惑,因為我明明調(diào)用了SelectableChannel.close()方法的。查過資料才知道,關閉一個已經(jīng)注冊的SelectableChannel需要兩個步驟:

1.取消注冊的key,這個可以通過SelectionKey.cancel方法,也可以通過SelectableChannel.close方法,或者中斷阻塞在該channel上的IO操作的線程來做到。
2.后續(xù)的Selector.selectXXX()方法的調(diào)用才真正地關閉 本地Socket

因此,如果調(diào)用了close()方法后沒有調(diào)用selectXXX()方法,那么本地socket將進入CLOSE-WAIT 狀態(tài)。就是這個原因造成在buf.read(bytes)時發(fā)生CloseChannelException,因為在上面這個例子中我使用了while(channel.isConnected())來進行條件循環(huán),如果轉(zhuǎn)換一下思路,不用while循環(huán),而是把多次傳遞的信息分成多個Channel來發(fā)送,是不是就會好一點。每一次接收的都是新SocketChannel實例,而不在一個實例中循環(huán),造成上面那樣的不調(diào)用Selector.selectXXX()無法真正關閉連接的問題。
這里的SelectorSelectorKey還有很多細節(jié)的地方需要再細細研磨,操作。當然現(xiàn)在也可以選擇成熟的NIO框架如Netty使用,以免進入一些不了解的坑中。

DatagramChannel

類似于之前的類SocketChannel,類DatagramChannel處理的也是網(wǎng)絡IO,但是它對應的是UDP連接,因為UDP是無連接數(shù)據(jù)包的網(wǎng)絡協(xié)議,所以它并不能像其他通道一樣讀取和寫入數(shù)據(jù),但是提供了receive()方法和send()方法來使用。如下所示:

/**
**DatagramChannel示例
**/
服務器端
public static void testDatagramChannel(){
        DatagramChannel datagramChannel = null;
        Selector selector = null;
        try {
            selector = Selector.open();
            datagramChannel = DatagramChannel.open();
            datagramChannel.configureBlocking(false);
            datagramChannel.socket().bind(new InetSocketAddress(8080));
            datagramChannel.register(selector, SelectionKey.OP_READ);
            System.out.println("---服務器啟動");
            while(true){
                int num = selector.select();
                if(num == 0)continue;
                Iterator<SelectionKey> selectionKeys = selector.selectedKeys().iterator();
                while(selectionKeys.hasNext()){
                    SelectionKey selectionKey = selectionKeys.next();
                    selectionKeys.remove();
                    if(selectionKey.isReadable()){
                        DatagramChannel channel = (DatagramChannel)selectionKey.channel();
                        ByteBuffer buf = ByteBuffer.allocate(1024);
                        channel.receive(buf);
                        buf.flip();
                        byte[] bytes = new byte[1024];
                        int len = buf.remaining();
                        buf.get(bytes,0,len);
                        String receive = new String(bytes,0,len);
                        System.out.println(receive);
                    }
                    if(selectionKey.isWritable()){
                        System.out.println("--write");
                    }
                }
            }
            
            
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
客戶端:
public static void testClient(){
        DatagramChannel datagramChannel = null;
        try {
            System.out.println("--客戶端開始");
            datagramChannel = DatagramChannel.open();
            datagramChannel.configureBlocking(false);
            ByteBuffer buf = ByteBuffer.allocate(1024);
            buf.put("這是個Demo".getBytes());
            buf.flip();
            datagramChannel.send(buf, new InetSocketAddress("192.168.191.3", 8080));
            datagramChannel.close();
            System.out.println(buf);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }
輸出:
--客戶端開始
java.nio.HeapByteBuffer[pos=10 lim=10 cap=1024]
---服務器啟動
這是個Demo

在上面這個demo中有幾點可以說一下,類DatagramChannel是個注冊到Selector中后,這個selector.select()方法是個阻塞方法,只有等新連接進入時才會繼續(xù)向下執(zhí)行,并不會因為之前對DatagramChannel設置了非阻塞而使這個方法非阻塞。相對于SocketChannel類來說,變化并不大。

Pipe

看到這個名字,就已經(jīng)很眼熟了,就像之前的PipedInputStream/PipedOutputStreamPipedReader/PipedWriter,這個類也是實現(xiàn)線程間通信的功能。在上篇文章中也有提到,在這個類中是使用兩個靜態(tài)內(nèi)部類SourceChannelSinkChannel來實現(xiàn)功能的,代碼如下所示:

/**
**Pipe 管道示例
**/
public static void testPipe() throws IOException{
        final Pipe pipe = Pipe.open();
        ExecutorService executor = Executors.newScheduledThreadPool(2);
        executor.submit(new Runnable() {
            
            @Override
            public void run() {
                // TODO Auto-generated method stub
                Pipe.SinkChannel sinkChannel = pipe.sink();
                ByteBuffer buf = ByteBuffer.allocate(1024);
                int i = 0;
                try {
                    while(i<4){

                        Thread.sleep(1000);
                        buf.clear();
                        String text = "Pipe test "+ i;
                        buf.put(text.getBytes());
                        buf.flip();
                        sinkChannel.write(buf);
                        i++;
                    }
                    sinkChannel.close();
                } catch (InterruptedException e1) {
                    // TODO Auto-generated catch block
                    e1.printStackTrace();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        });
        executor.submit(new Runnable() {
            
            @Override
            public void run() {
                // TODO Auto-generated method stub
                Pipe.SourceChannel sourceChannel = pipe.source();
                ByteBuffer buf = ByteBuffer.allocate(1024);
                while(sourceChannel.isOpen()){
                    try {
                        buf.clear();
                        int len = sourceChannel.read(buf);
                        if(len == -1)sourceChannel.close();
                        if(len>0){
                            byte[] bytes = new byte[1024];
                            buf.flip();
                            buf.get(bytes, 0, len);
                            System.out.println(new String(bytes,0,len));
                        }
                        
                    } catch (IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
                
            }
        });
        
    }
輸出:
Pipe test 0
Pipe test 1
Pipe test 2
Pipe test 3

如上所示,管道Pipe的操作和之前的幾個Channel的操作并沒有太大的變化,這個類完成線程間的通信靠的是它的兩個靜態(tài)內(nèi)部類,把握住著一點,其余就需要研究書寫細節(jié)了。

總的來說,這部分其實很重要,這里也只是先打一點基礎,如果想學的更深入的話,可以找找相關的框架進行學習,如Netty。有錯誤疑惑的地方還請麻煩指出一起學習,對于這部分我也是看了相關內(nèi)容并沒有在實際的工作中用到,很多地方可能并不深入或細節(jié)并不完善,還請指出,之后會一一完善。

本文參考
Java NIO 系列教程
攻破JAVA NIO技術壁壘
通俗編程——白話NIO之Selector
NIO的SelectableChannel關閉的一個問題
TCP和UDP的區(qū)別(轉(zhuǎn))

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,546評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,570評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,505評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,017評論 1 313
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,786評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,219評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,287評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,438評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,971評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,796評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,995評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,540評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,230評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,918評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,697評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

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

  • Java NIO(New IO)是從Java 1.4版本開始引入的一個新的IO API,可以替代標準的Java I...
    JackChen1024閱讀 7,566評論 1 143
  • 這兩天了解了一下關于NIO方面的知識,網(wǎng)上關于這一塊的介紹只是介紹了一下基本用法,沒有系統(tǒng)的解釋NIO與阻塞、非阻...
    Ruheng閱讀 7,140評論 5 48
  • 簡介 Java NIO 是由 Java 1.4 引進的異步 IO.Java NIO 由以下幾個核心部分組成: Ch...
    永順閱讀 1,807評論 0 15
  • (轉(zhuǎn)載說明:本文非原創(chuàng),轉(zhuǎn)載自http://ifeve.com/java-nio-all/) Java NIO: ...
    數(shù)獨題閱讀 816評論 0 3
  • 以前nio包可能會要求高級才會需要,但是現(xiàn)在這部分內(nèi)容已經(jīng)屬于常見的基礎技能了,因為對nio這部分并不熟悉,所以花...
    _小二_閱讀 614評論 1 1