Java的IO和NIO

Java的IO和NIO

一泞辐、Java的IO

Java的IO功能在java.io包下毅贮,包括輸入、輸出兩種IO流驾凶,每種輸入牙甫、輸出流又可分為字節(jié)流和字符流兩大類。字節(jié)流以字節(jié)(8位)為單位處理輸入輸出调违,字符流以字符(16位)為單位來處理輸入輸出窟哺。

1.1 流的分類

  1. 輸入輸出流

Java的輸入流主要有InputStreamReader作為基類,而輸出流主要由OutputStreamWriter作為基類技肩。他們是一些抽象類且轨,無法直接創(chuàng)建實(shí)例。

  1. 字節(jié)流和字符流

處理字節(jié)流:InputStream虚婿、OutputStream旋奢;

處理字符流:Reader、Writer然痊。

  1. 節(jié)點(diǎn)流和處理流

節(jié)點(diǎn)流:可以直接從IO設(shè)備(如磁盤至朗、網(wǎng)絡(luò))讀寫數(shù)據(jù)的流稱為節(jié)點(diǎn)流,也被稱為低級流剧浸,比如锹引,處理文件的輸入流:FileInputStream和FileReader;

處理流:對一個已存在的流進(jìn)行連接和封裝唆香,通過封裝后的流來實(shí)現(xiàn)數(shù)據(jù)讀寫功能嫌变,也被稱為高級流,比如PrintStream躬它。

當(dāng)使用節(jié)點(diǎn)流時腾啥,程序可以直接連接到實(shí)際的數(shù)據(jù)源。當(dāng)使用處理流時冯吓,程序并不會直接連接到實(shí)際的數(shù)據(jù)源倘待。

使用處理流的好處就是,只要使用相同的處理流桑谍,程序就可以采用完全相同的輸入輸出代碼來訪問不同的數(shù)據(jù)源延柠,隨著處理流所包裝節(jié)點(diǎn)流的變化,程序?qū)嶋H訪問的數(shù)據(jù)源也會相應(yīng)的發(fā)生改變锣披。實(shí)際上這是一種裝飾者模式贞间,處理流也被稱為包裝流贿条。通過使用處理流,Java程序無須理會輸入輸出節(jié)點(diǎn)是磁盤還是網(wǎng)絡(luò)增热,還是其他設(shè)備整以,程序只要將這些節(jié)點(diǎn)流包裝成處理流,就可以使用相同的輸入輸出代碼來讀寫不同設(shè)備上的數(shù)據(jù)峻仇。

識別處理流很簡單公黑,主要流的構(gòu)造參數(shù)不是一個物理節(jié)點(diǎn),而是已經(jīng)存在的流摄咆;而節(jié)點(diǎn)流都是直接以物理IO節(jié)點(diǎn)作為構(gòu)造器參數(shù)的凡蚜。比如FileInputStream的構(gòu)造器是FileInputStream(String name)

FileInputStream(File file),那么它就是一個節(jié)點(diǎn)流吭从,再比如PrintStream的構(gòu)造器是PrintStream(OutputStream out)朝蜘,那么它就是一個處理流。

使用處理流的優(yōu)點(diǎn)

  • 對開發(fā)人員來講涩金,使用處理流進(jì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");
        //寫入文件
        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輸入字符和對象
 * PrintStream輸出功能非常強(qiáng)大,我們平時常用的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個類全度。

下面將常用的一些類做一下分類匯總煮剧。其中粗體的為處理流。

分類 字節(jié)輸入流 字節(jié)輸出流 字符輸入流 字符輸出流
基類 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
對象流 ObjectInputStream ObjectOutputStream
打印流 PrintStream PrintWriter
推回輸入流 PushbackInputStream PushbackReader

通常來說将鸵,我們認(rèn)為字節(jié)流的功能比字符流的功能強(qiáng)大轿秧,因?yàn)橛?jì)算機(jī)里所有的數(shù)據(jù)都是二進(jìn)制的,而字節(jié)流可以處理所有的二進(jìn)制文件咨堤,但問題就是如果使用字節(jié)流處理文本文件,則需要使用合適的方式將這些字節(jié)轉(zhuǎn)換成字符漩符,這就增加了編程的復(fù)雜度一喘。

所以通常有這么一個規(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)容恰好能被正常解析成字符時,則該二進(jìn)制文件就變成了文本文件舆逃。

注意2:上面的表格中列舉了4種訪問管道的流蚂维,它們都是用于實(shí)現(xiàn)進(jìn)程之間通信功能的戳粒。

1.3 轉(zhuǎn)換流

上面的表格中列舉了java提供的兩個轉(zhuǎn)換流。InputStreamReader和OutputStreamWriter用于將字節(jié)流轉(zhuǎn)換成字符流虫啥。為什么要這么做呢蔚约?前面提到了字節(jié)流的功能比較強(qiáng)大,字符流使用起來方便涂籽,如果有一個字節(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是鍵盤輸入评雌,是InputStream(字節(jié)流)的實(shí)例树枫,因?yàn)殒I盤輸入都是字符,所以轉(zhuǎn)換成字符流操作更方便
 * 普通的Reader讀取內(nèi)容時依然不方便景东,可以包裝成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具有一個readLine方法,可以非常方便的一次讀取一行內(nèi)容耐薯,所以經(jīng)常把讀取文本的輸出流包裝成BufferedReader舔清,用來方便的讀取輸入流中的文本內(nèi)容。

1.4 推回輸入流

在java的IO體系中提供了兩個特殊的流曲初,PushbackInputStream和PushbackReader体谒。它們提供了unread方法用于將字節(jié)/字符推回到推回緩沖區(qū)里面,從而允許讀取剛剛讀取的內(nèi)容臼婆。

它們帶有一個推回緩沖區(qū)抒痒,程序調(diào)用read方法時總是先從推回緩沖區(qū)里面讀取,只有完全讀取了推回緩沖區(qū)的內(nèi)容颁褂,并且還沒有裝滿read所需的數(shù)組時才會從原始輸入流中讀取故响。

代碼實(shí)例

package com.wangjun.othersOfJava;

import java.io.FileReader;
import java.io.IOException;
import java.io.PushbackReader;

/*
 * 推回緩沖流測試
 * 如果文件中包含LeetCode就把LeetCode周邊的字符打印出來
 */
public class PushbackReaderTest {

    public static void main(String[] args) throws IOException {
        //推回緩沖區(qū)的長度為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);
                //這一次會先讀取緩沖區(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)輸入輸出分別通過System.in和System.out來代表,默認(rèn)情況下他們代表鍵盤和顯示器颁独,在System類里面提供了3個重定向的方法: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)容就會打印到ps中,而不是Console控制臺
        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ī)讀寫其他進(jìn)程的數(shù)據(jù)

Runtime對象的exec()方法可以運(yùn)行平臺上的其他程序靠柑,該方法產(chǎn)生一個Process對象寨辩,Process對象代表由該Java程序啟動的子進(jìn)程。Process提供了如下3個方法用于程序和其子進(jìn)程進(jìn)行通信:

  • InputStream getErrorStream():獲取子進(jìn)程的錯誤流歼冰;
  • 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)程的錯誤流創(chuàng)建BufferedReader對象
        // 這個錯誤流對本程序是輸入流甸怕,對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)行沒有成功,待分析...

Java的NIO

BufferedReader有一個特征蕾各,就是讀取輸入流中的數(shù)據(jù)時扒磁,如果沒有讀到有效數(shù)據(jù),程序?qū)⒃诖颂幾枞摼€程的執(zhí)行(使用InputStream的read方法從流中讀取數(shù)據(jù)時式曲,也有這樣的特性)妨托,java.io下面的輸入輸出流都是阻塞式的。不僅如此吝羞,傳統(tǒng)的輸入輸出流都是通過字節(jié)的移動來處理的兰伤,及時我們不直接去處理字節(jié)流,單底層的實(shí)現(xiàn)還是依賴于字節(jié)處理钧排,也就是說摹迷,面向流的輸入輸出系統(tǒng)一次只能處理一個字節(jié)怠苔,因此面向流的輸入輸出系統(tǒng)通常效率都不高蛔琅。

為了解決上面的問題男杈,NIO就出現(xiàn)了。NIO采用內(nèi)存映射文件來處理輸入輸出糟袁,NIO將文件或文件的一段區(qū)域映射到內(nèi)存中判族,這樣就可以像訪問內(nèi)存一樣來訪問文件了。(這種方式模擬了操作系統(tǒng)上虛擬內(nèi)存的概念)项戴,通過這種方式進(jìn)行輸入輸出要快得多形帮。

Channel(通道)和Buffer(緩沖)是NIO中兩個核心對象。Channel是對傳統(tǒng)的輸入輸出系統(tǒng)的模擬周叮,在NIO系統(tǒng)中所有的數(shù)據(jù)都需要通過通道傳輸辩撑,Channel與傳統(tǒng)的InputStream、OutputStream最大的區(qū)別就是提供了一個map()方法仿耽,通過它可以直接將“一塊數(shù)據(jù)”映射到內(nèi)存中合冀。如果說傳統(tǒng)IO是面向流的處理,那么NIO是面向塊的處理项贺。

Buffer可以被理解為一個容器水慨,它的本質(zhì)是一個數(shù)組,發(fā)送到Channel中所有的對象都必須首先放到Buffer中敬扛,從而Channel中讀取的數(shù)據(jù)也必須先放到Buffer中。Buffer既可以一次次從Channel取數(shù)據(jù)朝抖,也可以使用Channel直接將文件的某塊數(shù)據(jù)映射成Buffer啥箭。

除了Channel和Buffer之外,NIO還提供了用于將Unicode字符串映射成字節(jié)序列以及逆映射操作的Charset類治宣,也提供了用于支持非阻塞式輸入輸出的Selector類急侥。其中Selector是非阻塞IO的核心砌滞。?

Buffer介紹

在Buffer中有三個重要的概念:

  • 容量(capacity):表示Buffer的大小,創(chuàng)建后不能呢過改變坏怪;
  • 界限(limit):第一個不能被讀寫的緩沖區(qū)的位置贝润,也就是后面的數(shù)據(jù)不能被讀寫;
  • 位置(position):用于指明下一個可以被讀寫的緩沖區(qū)位置索引铝宵。當(dāng)從Channel讀取數(shù)據(jù)的時候打掘,position的值就等于讀到了多少數(shù)據(jù)。

Buffer的主要作用就是裝入數(shù)據(jù)鹏秋,然后輸出數(shù)據(jù)尊蚁。當(dāng)裝入數(shù)據(jù)結(jié)束時,調(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ù)的放入和讀取古瓤,分為相對和絕對兩種:

  • 相對:從Buffer當(dāng)前position位置開始讀取或者寫入數(shù)據(jù),然后將position的值按處理元素的個數(shù)增加腺阳;
  • 絕對:直接根據(jù)索引向Buffer中讀取和寫入數(shù)據(jù)落君,使用絕對方式訪問Buffer里的數(shù)據(jù)時,不會影響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("加入三個元素后绎速,position:" + cb.position());
        cb.flip();
        System.out.println("執(zhí)行flip()后,limit:" + cb.limit());
        System.out.println("執(zhí)行flip()后焙蚓,position:" + cb.position());
        System.out.println("取出第一個元素: " + cb.get());
        System.out.println("取出第一個元素后纹冤,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ù)沒有清空,第三個值是:" + cb.get(2));
        System.out.println("執(zhí)行絕對讀取后宏浩,position:" + cb.position());
    }

}

通過allocate方法創(chuàng)建的是普通Buffer知残,還可以通過allocateDirect方法來創(chuàng)建直接Buffer,雖然創(chuàng)建成本比較高比庄,但是讀寫快求妹。因此適用于長期生存的Buffer乏盐,使用方法和普通Buffer類似。注意制恍,只有ByteBuffer提供了此方法父能,其他類型的想用,可以將該Buffer轉(zhuǎn)成其他類型的Buffer净神。

Channel(通道)介紹

Channel類似傳統(tǒng)的流對象何吝,主要區(qū)別如下:

  • Channel可以直接將指定文件的部分或全部直接映射成Buffer。
  • 程序不能直接訪問Channel中的數(shù)據(jù)强挫,只能通過Buffer交互岔霸。

所有的Channel都不應(yīng)該通過構(gòu)造器來創(chuàng)建,而是通過傳統(tǒng)的InputStream俯渤、OutputStream的getChannel()方法來返回對應(yīng)的Channel呆细,不同的節(jié)點(diǎn)流獲取的Channel不一樣,比如FileInputStream返回的是FileChannel八匠。

Channel常用的方法有三類:map()絮爷、read()、write()梨树。map方法將Channel對應(yīng)的部分或全部數(shù)據(jù)映射成ByteBuffer坑夯;read和write方法都有一系列的重載形式,這些方法用于從Buffer中讀取/寫入數(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括號內(nèi)的資源會在try語句結(jié)束后自動釋放柜蜈,前提是這些可關(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方法的三個參數(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字符集來創(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對應(yīng)的文件過大藻雪,使用map方法一次性將所有文件內(nèi)容映射到內(nèi)存中可能會因此性能下降
//這時候我們可以適應(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();
  ){
    //定義一個ByteBuffer對象秘噪,用于重復(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選擇器類管理著一個被注冊的通道集合的信息和它們的就緒狀態(tài)。通道是和選擇器一起被注冊的勉耀,并且使用選擇器來更新通道的就緒狀態(tài)指煎。利用 Selector可使一個單獨(dú)的線程管理多個 Channel,selector 是非阻塞 IO 的核心便斥。

應(yīng)用

當(dāng)通道使用register(Selector sel, int ops)方法將通道注冊選擇器時至壤,選擇器對通道事件進(jìn)行監(jiān)聽,通過第二個參數(shù)指定監(jiān)聽的事件類型枢纠。

其中可監(jiān)聽的事件類型包括以下:

讀 : SelectionKey.OP_READ

寫 : SelectionKey.OP_WRITE

連接 : SelectionKey.OP_CONNECT

接收 : SelectionKey.OP_ACCEPT

如果需要監(jiān)聽多個事件可以使用位或操作符:

int key = SelectionKey.OP_READ | SelectionKey.OP_WRITE ; //表示同時監(jiān)聽讀寫操作

參考:

https://blog.csdn.net/dd864140130/article/details/50299687

https://www.cnblogs.com/qq-361807535/p/6670529.html

https://blog.csdn.net/dd864140130/article/details/50299687

文件鎖

在NIO中像街,Java提供了文件鎖的支持,使用FileLock來支持文件鎖定功能,在FileChannel中提供lock()/tryLock()方法來獲取文件鎖FileLock對象宅广,從而鎖定文件。lock和tryLock的區(qū)別是前者無法得到文件鎖的時候會阻塞些举,后者不會阻塞跟狱。也支持鎖定部分內(nèi)容,使用lock(long position, long size, boolean shared)即可户魏,其中shared為true時驶臊,表明該鎖是一個共享鎖,可以允許多個縣城讀取文件叼丑,但阻止其他進(jìn)程獲得該文件的排他鎖关翎。當(dāng)shared為false時,表明是一個排他鎖鸠信,它將鎖住對該文件的讀寫纵寝。

默認(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();
                ){
            //使用非阻塞方式對指定文件加鎖
            FileLock lock = fc.tryLock();
            Thread.sleep(3000);
            lock.release();//釋放鎖
        }
    }
}

Java的NIO2

Java7對原來的NIO進(jìn)行了重大改進(jìn):

  • 提供了全面的文件IO和文件系統(tǒng)訪問支持星立;
  • 基于異步Channel的IO爽茴。

這里先簡單介紹一下對文件系統(tǒng)的支持,后續(xù)繼續(xù)學(xué)習(xí)绰垂。

NIO2提供了一下接口和工具類:

  • Path接口:通過和Paths工具類結(jié)合使用產(chǎn)生Path對象室奏,可以獲取文件根路徑、絕對路徑劲装、路徑數(shù)量等胧沫;
  • Files工具類:提供了很多靜態(tài)方法,比如復(fù)制文件占业、一次性讀取文件所有行绒怨、判斷是否為隱藏文件、判斷文件大小纺酸、遍歷文件和子目錄窖逗、訪問文件屬性等;
  • FileVisitor接口:代表一個文件訪問器餐蔬,F(xiàn)iles工具類使用walkFileTree方法遍歷文件和子目錄時碎紊,都會觸發(fā)FileVisitor中相應(yīng)的方法,比如訪問目錄之前樊诺、之后仗考,訪問文件時,訪問文件失敗時词爬;
  • 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)行操作的,沒有被緩存在任何地方,而NIO是基于通道(Channel)和緩沖區(qū)(Buffer)進(jìn)行操作锅锨,數(shù)據(jù)總是從通道讀取到緩沖區(qū)中叽赊,或者從緩沖區(qū)寫入到通道中,因此可以前后移動流中的數(shù)據(jù)必搞;
  • Asynchronous IO(異步IO):Java NIO可以讓你異步的使用IO必指,例如:當(dāng)線程從通道讀取數(shù)據(jù)到緩沖區(qū)時,線程還是可以進(jìn)行其他事情恕洲。當(dāng)數(shù)據(jù)被寫入到緩沖區(qū)時塔橡,線程可以繼續(xù)處理它。從緩沖區(qū)寫入通道也類似霜第。因?yàn)镹IO將阻塞交給了后臺線程執(zhí)行葛家,非阻塞IO的空余時間可以用于其他通道上進(jìn)行IO,因此可以一個線程可以管理多個通道泌类,而IO是阻塞的癞谒,阻塞式的IO每個連接必須開一個線程來處理,每個線程都需要珍貴的CPU資源末誓,等待IO的時候扯俱,CPU資源沒有被釋放就被浪費(fèi)掉了。
  • Selectors(選擇器):選擇器允許一個單獨(dú)的線程可以監(jiān)聽多個數(shù)據(jù)通道((網(wǎng)絡(luò)連接或文件)喇澡,你可以注冊多個通道使用一個選擇器迅栅,然后使用一個單獨(dú)的線程來“選擇”通道:這些通道里已經(jīng)有可以處理的輸入,或者選擇已準(zhǔn)備寫入的通道晴玖。這種選擇機(jī)制读存,使得一個單獨(dú)的線程很容易來管理多個通道。但付出的代價(jià)是解析數(shù)據(jù)可能會比從一個阻塞流中讀取數(shù)據(jù)更復(fù)雜呕屎。

使用場景

NIO

  • 優(yōu)勢在于一個線程管理多個通道让簿;但是數(shù)據(jù)的處理將會變得復(fù)雜;
  • 如果需要管理同時打開的成千上萬個連接秀睛,這些連接每次只是發(fā)送少量的數(shù)據(jù)尔当,采用這種;

傳統(tǒng)的IO

  • 適用于一個線程管理一個通道的情況蹂安;因?yàn)槠渲械牧鲾?shù)據(jù)的讀取是阻塞的椭迎;
  • 如果需要管理同時打開不太多的連接,這些連接會發(fā)送大量的數(shù)據(jù)田盈;

參考:BIO畜号、NIO、AIO

https://blog.csdn.net/anxpp/article/details/51512200

http://bbym010.iteye.com/blog/2100868

Netty簡介

Netty是一個java開源框架允瞧。Netty提供異步的简软、事件驅(qū)動的網(wǎng)絡(luò)應(yīng)用程序框架和工具蛮拔,用以快速開發(fā)高性能、高可靠性的網(wǎng)絡(luò)服務(wù)器和客戶端程序痹升。

Netty是一個NIO客戶端建炫、服務(wù)端框架。允許快速簡單的開發(fā)網(wǎng)絡(luò)應(yīng)用程序疼蛾。例如:服務(wù)端和客戶端之間的協(xié)議踱卵。它最牛逼的地方在于簡化了網(wǎng)絡(luò)編程規(guī)范。例如:TCP和UDP的Socket服務(wù)据过。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市妒挎,隨后出現(xiàn)的幾起案子绳锅,更是在濱河造成了極大的恐慌,老刑警劉巖酝掩,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鳞芙,死亡現(xiàn)場離奇詭異,居然都是意外死亡期虾,警方通過查閱死者的電腦和手機(jī)原朝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來镶苞,“玉大人喳坠,你說我怎么就攤上這事∶荆” “怎么了壕鹉?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長聋涨。 經(jīng)常有香客問我晾浴,道長,這世上最難降的妖魔是什么牍白? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任脊凰,我火速辦了婚禮,結(jié)果婚禮上茂腥,老公的妹妹穿的比我還像新娘狸涌。我一直安慰自己,他們只是感情好础芍,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布杈抢。 她就那樣靜靜地躺著,像睡著了一般仑性。 火紅的嫁衣襯著肌膚如雪惶楼。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天,我揣著相機(jī)與錄音歼捐,去河邊找鬼何陆。 笑死,一個胖子當(dāng)著我的面吹牛豹储,可吹牛的內(nèi)容都是我干的贷盲。 我是一名探鬼主播,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼剥扣,長吁一口氣:“原來是場噩夢啊……” “哼巩剖!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起钠怯,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤佳魔,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后晦炊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鞠鲜,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年断国,在試婚紗的時候發(fā)現(xiàn)自己被綠了贤姆。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡稳衬,死狀恐怖霞捡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情薄疚,我是刑警寧澤弄砍,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站输涕,受9級特大地震影響音婶,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜莱坎,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一衣式、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧檐什,春花似錦碴卧、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至瓮具,卻和暖如春荧飞,著一層夾襖步出監(jiān)牢的瞬間凡人,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工叹阔, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留挠轴,地道東北人。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓耳幢,卻偏偏與公主長得像岸晦,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子睛藻,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評論 2 353

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