Java的IO和NIO

<meta charset="utf-8">

Java的IO和NIO

一莲绰、Java的IO

Java的IO功能在java.io包下,包括輸入观堂、輸出兩種IO流让网,每種輸入、輸出流又可分為字節(jié)流和字符流兩大類(lèi)型将。字節(jié)流以字節(jié)(8位)為單位處理輸入輸出,字符流以字符(16位)為單位來(lái)處理輸入輸出荐虐。

1.1 流的分類(lèi)

  1. 輸入輸出流

Java的輸入流主要有InputStreamReader作為基類(lèi)七兜,而輸出流主要由OutputStreamWriter作為基類(lèi)。他們是一些抽象類(lèi)福扬,無(wú)法直接創(chuàng)建實(shí)例腕铸。

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

處理字節(jié)流:InputStream、OutputStream铛碑;

處理字符流:Reader狠裹、Writer。

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

節(jié)點(diǎn)流:可以直接從IO設(shè)備(如磁盤(pán)汽烦、網(wǎng)絡(luò))讀寫(xiě)數(shù)據(jù)的流稱(chēng)為節(jié)點(diǎn)流涛菠,也被稱(chēng)為低級(jí)流,比如撇吞,處理文件的輸入流:FileInputStream和FileReader俗冻;

處理流:對(duì)一個(gè)已存在的流進(jìn)行連接和封裝,通過(guò)封裝后的流來(lái)實(shí)現(xiàn)數(shù)據(jù)讀寫(xiě)功能牍颈,也被稱(chēng)為高級(jí)流迄薄,比如PrintStream。

當(dāng)使用節(jié)點(diǎn)流時(shí)煮岁,程序可以直接連接到實(shí)際的數(shù)據(jù)源讥蔽。當(dāng)使用處理流時(shí)涣易,程序并不會(huì)直接連接到實(shí)際的數(shù)據(jù)源。

使用處理流的好處就是冶伞,只要使用相同的處理流新症,程序就可以采用完全相同的輸入輸出代碼來(lái)訪問(wèn)不同的數(shù)據(jù)源,隨著處理流所包裝節(jié)點(diǎn)流的變化碰缔,程序?qū)嶋H訪問(wèn)的數(shù)據(jù)源也會(huì)相應(yīng)的發(fā)生改變账劲。實(shí)際上這是一種裝飾者模式,處理流也被稱(chēng)為包裝流金抡。通過(guò)使用處理流瀑焦,Java程序無(wú)須理會(huì)輸入輸出節(jié)點(diǎn)是磁盤(pán)還是網(wǎng)絡(luò),還是其他設(shè)備梗肝,程序只要將這些節(jié)點(diǎn)流包裝成處理流榛瓮,就可以使用相同的輸入輸出代碼來(lái)讀寫(xiě)不同設(shè)備上的數(shù)據(jù)。

識(shí)別處理流很簡(jiǎn)單巫击,主要流的構(gòu)造參數(shù)不是一個(gè)物理節(jié)點(diǎn)禀晓,而是已經(jīng)存在的流丰刊;而節(jié)點(diǎn)流都是直接以物理IO節(jié)點(diǎn)作為構(gòu)造器參數(shù)的茉帅。比如FileInputStream的構(gòu)造器是FileInputStream(String name)

FileInputStream(File file),那么它就是一個(gè)節(jié)點(diǎn)流勺像,再比如PrintStream的構(gòu)造器是PrintStream(OutputStream out)顷级,那么它就是一個(gè)處理流凫乖。

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

  • 對(duì)開(kāi)發(fā)人員來(lái)講,使用處理流進(jìn)行輸入輸出操作更簡(jiǎ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");
        //寫(xiě)入文件
        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輸入字符和對(duì)象
 * PrintStream輸出功能非常強(qiáng)大导街,我們平時(shí)常用的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個(gè)類(lèi)。

下面將常用的一些類(lèi)做一下分類(lèi)匯總纤子。其中粗體的為處理流搬瑰。

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

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

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

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

1.3 轉(zhuǎn)換流

上面的表格中列舉了java提供的兩個(gè)轉(zhuǎn)換流膊毁。InputStreamReader和OutputStreamWriter用于將字節(jié)流轉(zhuǎn)換成字符流胀莹。為什么要這么做呢?前面提到了字節(jié)流的功能比較強(qiáng)大婚温,字符流使用起來(lái)方便描焰,如果有一個(gè)字節(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是鍵盤(pán)輸入荆秦,是InputStream(字節(jié)流)的實(shí)例,因?yàn)殒I盤(pán)輸入都是字符力图,所以轉(zhuǎn)換成字符流操作更方便
 * 普通的Reader讀取內(nèi)容時(shí)依然不方便步绸,可以包裝成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具有一個(gè)readLine方法吃媒,可以非常方便的一次讀取一行內(nèi)容瓤介,所以經(jīng)常把讀取文本的輸出流包裝成BufferedReader,用來(lái)方便的讀取輸入流中的文本內(nèi)容晓折。

1.4 推回輸入流

在java的IO體系中提供了兩個(gè)特殊的流惑朦,PushbackInputStream和PushbackReader兽泄。它們提供了unread方法用于將字節(jié)/字符推回到推回緩沖區(qū)里面漓概,從而允許讀取剛剛讀取的內(nèi)容病梢。

它們帶有一個(gè)推回緩沖區(qū)觅彰,程序調(diào)用read方法時(shí)總是先從推回緩沖區(qū)里面讀取填抬,只有完全讀取了推回緩沖區(qū)的內(nèi)容飒责,并且還沒(méi)有裝滿read所需的數(shù)組時(shí)才會(huì)從原始輸入流中讀取宏蛉。

代碼實(shí)例

package com.wangjun.othersOfJava;

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

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

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

Runtime對(duì)象的exec()方法可以運(yùn)行平臺(tái)上的其他程序,該方法產(chǎn)生一個(gè)Process對(duì)象默赂,Process對(duì)象代表由該Java程序啟動(dòng)的子進(jìn)程缆八。Process提供了如下3個(gè)方法用于程序和其子進(jìn)程進(jìn)行通信:

  • InputStream getErrorStream():獲取子進(jìn)程的錯(cuò)誤流;
  • 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)程的錯(cuò)誤流創(chuàng)建BufferedReader對(duì)象
        // 這個(gè)錯(cuò)誤流對(duì)本程序是輸入流蜡峰,對(duì)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)行沒(méi)有成功湿颅,待分析...

Java的NIO

BufferedReader有一個(gè)特征,就是讀取輸入流中的數(shù)據(jù)時(shí)油航,如果沒(méi)有讀到有效數(shù)據(jù)崭庸,程序?qū)⒃诖颂幾枞摼€程的執(zhí)行(使用InputStream的read方法從流中讀取數(shù)據(jù)時(shí)谊囚,也有這樣的特性)怕享,java.io下面的輸入輸出流都是阻塞式的。不僅如此镰踏,傳統(tǒng)的輸入輸出流都是通過(guò)字節(jié)的移動(dòng)來(lái)處理的,及時(shí)我們不直接去處理字節(jié)流奠伪,單底層的實(shí)現(xiàn)還是依賴(lài)于字節(jié)處理绊率,也就是說(shuō)脸狸,面向流的輸入輸出系統(tǒng)一次只能處理一個(gè)字節(jié)顽聂,因此面向流的輸入輸出系統(tǒng)通常效率都不高。

為了解決上面的問(wèn)題全景,NIO就出現(xiàn)了耀石。NIO采用內(nèi)存映射文件來(lái)處理輸入輸出,NIO將文件或文件的一段區(qū)域映射到內(nèi)存中,這樣就可以像訪問(wèn)內(nèi)存一樣來(lái)訪問(wèn)文件了滞伟。(這種方式模擬了操作系統(tǒng)上虛擬內(nèi)存的概念)揭鳞,通過(guò)這種方式進(jìn)行輸入輸出要快得多。

Channel(通道)和Buffer(緩沖)是NIO中兩個(gè)核心對(duì)象梆奈。Channel是對(duì)傳統(tǒng)的輸入輸出系統(tǒng)的模擬野崇,在NIO系統(tǒng)中所有的數(shù)據(jù)都需要通過(guò)通道傳輸,Channel與傳統(tǒng)的InputStream亩钟、OutputStream最大的區(qū)別就是提供了一個(gè)map()方法乓梨,通過(guò)它可以直接將“一塊數(shù)據(jù)”映射到內(nèi)存中。如果說(shuō)傳統(tǒng)IO是面向流的處理清酥,那么NIO是面向塊的處理扶镀。

Buffer可以被理解為一個(gè)容器,它的本質(zhì)是一個(gè)數(shù)組焰轻,發(fā)送到Channel中所有的對(duì)象都必須首先放到Buffer中臭觉,從而Channel中讀取的數(shù)據(jù)也必須先放到Buffer中。Buffer既可以一次次從Channel取數(shù)據(jù)辱志,也可以使用Channel直接將文件的某塊數(shù)據(jù)映射成Buffer蝠筑。

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

Buffer介紹

在Buffer中有三個(gè)重要的概念:

  • 容量(capacity):表示Buffer的大小旭从,創(chuàng)建后不能呢過(guò)改變稳强;
  • 界限(limit):第一個(gè)不能被讀寫(xiě)的緩沖區(qū)的位置,也就是后面的數(shù)據(jù)不能被讀寫(xiě)和悦;
  • 位置(position):用于指明下一個(gè)可以被讀寫(xiě)的緩沖區(qū)位置索引退疫。當(dāng)從Channel讀取數(shù)據(jù)的時(shí)候,position的值就等于讀到了多少數(shù)據(jù)鸽素。

Buffer的主要作用就是裝入數(shù)據(jù)褒繁,然后輸出數(shù)據(jù)。當(dāng)裝入數(shù)據(jù)結(jié)束時(shí)馍忽,調(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ù)的放入和讀取碗啄,分為相對(duì)和絕對(duì)兩種:

  • 相對(duì):從Buffer當(dāng)前position位置開(kāi)始讀取或者寫(xiě)入數(shù)據(jù),然后將position的值按處理元素的個(gè)數(shù)增加稳摄;
  • 絕對(duì):直接根據(jù)索引向Buffer中讀取和寫(xiě)入數(shù)據(jù)稚字,使用絕對(duì)方式訪問(wèn)Buffer里的數(shù)據(jù)時(shí),不會(huì)影響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("加入三個(gè)元素后尉共,position:" + cb.position());
        cb.flip();
        System.out.println("執(zhí)行flip()后,limit:" + cb.limit());
        System.out.println("執(zhí)行flip()后弃锐,position:" + cb.position());
        System.out.println("取出第一個(gè)元素: " + cb.get());
        System.out.println("取出第一個(gè)元素后袄友,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ù)沒(méi)有清空,第三個(gè)值是:" + cb.get(2));
        System.out.println("執(zhí)行絕對(duì)讀取后旋廷,position:" + cb.position());
    }

}

通過(guò)allocate方法創(chuàng)建的是普通Buffer鸠按,還可以通過(guò)allocateDirect方法來(lái)創(chuàng)建直接Buffer,雖然創(chuàng)建成本比較高饶碘,但是讀寫(xiě)快目尖。因此適用于長(zhǎng)期生存的Buffer,使用方法和普通Buffer類(lèi)似扎运。注意瑟曲,只有ByteBuffer提供了此方法,其他類(lèi)型的想用豪治,可以將該Buffer轉(zhuǎn)成其他類(lèi)型的Buffer洞拨。

Channel(通道)介紹

Channel類(lèi)似傳統(tǒng)的流對(duì)象,主要區(qū)別如下:

  • Channel可以直接將指定文件的部分或全部直接映射成Buffer负拟。
  • 程序不能直接訪問(wèn)Channel中的數(shù)據(jù)烦衣,只能通過(guò)Buffer交互。

所有的Channel都不應(yīng)該通過(guò)構(gòu)造器來(lái)創(chuàng)建掩浙,而是通過(guò)傳統(tǒng)的InputStream花吟、OutputStream的getChannel()方法來(lái)返回對(duì)應(yīng)的Channel,不同的節(jié)點(diǎn)流獲取的Channel不一樣厨姚,比如FileInputStream返回的是FileChannel衅澈。

Channel常用的方法有三類(lèi):map()、read()遣蚀、write()矾麻。map方法將Channel對(duì)應(yīng)的部分或全部數(shù)據(jù)映射成ByteBuffer;read和write方法都有一系列的重載形式芭梯,這些方法用于從Buffer中讀取/寫(xiě)入數(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括號(hào)內(nèi)的資源會(huì)在try語(yǔ)句結(jié)束后自動(dòng)釋放,前提是這些可關(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方法的三個(gè)參數(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字符集來(lái)創(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對(duì)應(yīng)的文件過(guò)大贬派,使用map方法一次性將所有文件內(nèi)容映射到內(nèi)存中可能會(huì)因此性能下降
//這時(shí)候我們可以適應(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();
  ){
    //定義一個(gè)ByteBuffer對(duì)象,用于重復(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選擇器類(lèi)管理著一個(gè)被注冊(cè)的通道集合的信息和它們的就緒狀態(tài)澎媒。通道是和選擇器一起被注冊(cè)的搞乏,并且使用選擇器來(lái)更新通道的就緒狀態(tài)。利用 Selector可使一個(gè)單獨(dú)的線程管理多個(gè) Channel戒努,selector 是非阻塞 IO 的核心请敦。

應(yīng)用

當(dāng)通道使用register(Selector sel, int ops)方法將通道注冊(cè)選擇器時(shí),選擇器對(duì)通道事件進(jìn)行監(jiān)聽(tīng)储玫,通過(guò)第二個(gè)參數(shù)指定監(jiān)聽(tīng)的事件類(lèi)型侍筛。

其中可監(jiān)聽(tīng)的事件類(lèi)型包括以下:

讀 : SelectionKey.OP_READ

寫(xiě) : SelectionKey.OP_WRITE

連接 : SelectionKey.OP_CONNECT

接收 : SelectionKey.OP_ACCEPT

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

int key = SelectionKey.OP_READ | SelectionKey.OP_WRITE ; //表示同時(shí)監(jiān)聽(tīng)讀寫(xiě)操作

參考:

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來(lái)支持文件鎖定功能匣椰,在FileChannel中提供lock()/tryLock()方法來(lái)獲取文件鎖FileLock對(duì)象,從而鎖定文件端礼。lock和tryLock的區(qū)別是前者無(wú)法得到文件鎖的時(shí)候會(huì)阻塞禽笑,后者不會(huì)阻塞。也支持鎖定部分內(nèi)容蛤奥,使用lock(long position, long size, boolean shared)即可蒲每,其中shared為true時(shí),表明該鎖是一個(gè)共享鎖喻括,可以允許多個(gè)縣城讀取文件邀杏,但阻止其他進(jìn)程獲得該文件的排他鎖。當(dāng)shared為false時(shí)唬血,表明是一個(gè)排他鎖望蜡,它將鎖住對(duì)該文件的讀寫(xiě)。

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

Java的NIO2

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

  • 提供了全面的文件IO和文件系統(tǒng)訪問(wèn)支持脖律;
  • 基于異步Channel的IO。

這里先簡(jiǎn)單介紹一下對(duì)文件系統(tǒng)的支持腕侄,后續(xù)繼續(xù)學(xué)習(xí)小泉。

NIO2提供了一下接口和工具類(lèi):

  • Path接口:通過(guò)和Paths工具類(lèi)結(jié)合使用產(chǎn)生Path對(duì)象芦疏,可以獲取文件根路徑、絕對(duì)路徑微姊、路徑數(shù)量等酸茴;
  • Files工具類(lèi):提供了很多靜態(tài)方法,比如復(fù)制文件兢交、一次性讀取文件所有行薪捍、判斷是否為隱藏文件、判斷文件大小配喳、遍歷文件和子目錄酪穿、訪問(wèn)文件屬性等;
  • FileVisitor接口:代表一個(gè)文件訪問(wèn)器晴裹,F(xiàn)iles工具類(lèi)使用walkFileTree方法遍歷文件和子目錄時(shí)被济,都會(huì)觸發(fā)FileVisitor中相應(yīng)的方法,比如訪問(wèn)目錄之前涧团、之后溉潭,訪問(wèn)文件時(shí),訪問(wèn)文件失敗時(shí)少欺;
  • 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)行操作的,沒(méi)有被緩存在任何地方赞别,而NIO是基于通道(Channel)和緩沖區(qū)(Buffer)進(jìn)行操作畏陕,數(shù)據(jù)總是從通道讀取到緩沖區(qū)中,或者從緩沖區(qū)寫(xiě)入到通道中仿滔,因此可以前后移動(dòng)流中的數(shù)據(jù)惠毁;
  • Asynchronous IO(異步IO):Java NIO可以讓你異步的使用IO,例如:當(dāng)線程從通道讀取數(shù)據(jù)到緩沖區(qū)時(shí)崎页,線程還是可以進(jìn)行其他事情鞠绰。當(dāng)數(shù)據(jù)被寫(xiě)入到緩沖區(qū)時(shí),線程可以繼續(xù)處理它飒焦。從緩沖區(qū)寫(xiě)入通道也類(lèi)似蜈膨。因?yàn)镹IO將阻塞交給了后臺(tái)線程執(zhí)行,非阻塞IO的空余時(shí)間可以用于其他通道上進(jìn)行IO牺荠,因此可以一個(gè)線程可以管理多個(gè)通道翁巍,而IO是阻塞的,阻塞式的IO每個(gè)連接必須開(kāi)一個(gè)線程來(lái)處理休雌,每個(gè)線程都需要珍貴的CPU資源灶壶,等待IO的時(shí)候,CPU資源沒(méi)有被釋放就被浪費(fèi)掉了杈曲。
  • Selectors(選擇器):選擇器允許一個(gè)單獨(dú)的線程可以監(jiān)聽(tīng)多個(gè)數(shù)據(jù)通道((網(wǎng)絡(luò)連接或文件)驰凛,你可以注冊(cè)多個(gè)通道使用一個(gè)選擇器胸懈,然后使用一個(gè)單獨(dú)的線程來(lái)“選擇”通道:這些通道里已經(jīng)有可以處理的輸入,或者選擇已準(zhǔn)備寫(xiě)入的通道恰响。這種選擇機(jī)制趣钱,使得一個(gè)單獨(dú)的線程很容易來(lái)管理多個(gè)通道。但付出的代價(jià)是解析數(shù)據(jù)可能會(huì)比從一個(gè)阻塞流中讀取數(shù)據(jù)更復(fù)雜渔隶。

使用場(chǎng)景

NIO

  • 優(yōu)勢(shì)在于一個(gè)線程管理多個(gè)通道羔挡;但是數(shù)據(jù)的處理將會(huì)變得復(fù)雜洁奈;
  • 如果需要管理同時(shí)打開(kāi)的成千上萬(wàn)個(gè)連接间唉,這些連接每次只是發(fā)送少量的數(shù)據(jù),采用這種利术;

傳統(tǒng)的IO

  • 適用于一個(gè)線程管理一個(gè)通道的情況呈野;因?yàn)槠渲械牧鲾?shù)據(jù)的讀取是阻塞的;
  • 如果需要管理同時(shí)打開(kāi)不太多的連接印叁,這些連接會(huì)發(fā)送大量的數(shù)據(jù)被冒;

參考:BIO、NIO轮蜕、AIO

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

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

Netty簡(jiǎn)介

Netty是一個(gè)java開(kāi)源框架昨悼。Netty提供異步的、事件驅(qū)動(dòng)的網(wǎng)絡(luò)應(yīng)用程序框架和工具跃洛,用以快速開(kāi)發(fā)高性能率触、高可靠性的網(wǎng)絡(luò)服務(wù)器和客戶(hù)端程序。

Netty是一個(gè)NIO客戶(hù)端汇竭、服務(wù)端框架葱蝗。允許快速簡(jiǎn)單的開(kāi)發(fā)網(wǎng)絡(luò)應(yīng)用程序。例如:服務(wù)端和客戶(hù)端之間的協(xié)議细燎。它最牛逼的地方在于簡(jiǎn)化了網(wǎng)絡(luò)編程規(guī)范两曼。例如:TCP和UDP的Socket服務(wù)。

作者:王小冬
鏈接:http://www.reibang.com/p/4c8d7b9edec9
來(lái)源:簡(jiǎn)書(shū)
著作權(quán)歸作者所有玻驻。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán)悼凑,非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末璧瞬,一起剝皮案震驚了整個(gè)濱河市佛析,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌彪蓬,老刑警劉巖寸莫,帶你破解...
    沈念sama閱讀 216,591評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異档冬,居然都是意外死亡膘茎,警方通過(guò)查閱死者的電腦和手機(jī)桃纯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)披坏,“玉大人态坦,你說(shuō)我怎么就攤上這事“舴鳎” “怎么了伞梯?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,823評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)帚屉。 經(jīng)常有香客問(wèn)我谜诫,道長(zhǎng),這世上最難降的妖魔是什么攻旦? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,204評(píng)論 1 292
  • 正文 為了忘掉前任喻旷,我火速辦了婚禮,結(jié)果婚禮上牢屋,老公的妹妹穿的比我還像新娘且预。我一直安慰自己,他們只是感情好烙无,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,228評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布锋谐。 她就那樣靜靜地躺著,像睡著了一般截酷。 火紅的嫁衣襯著肌膚如雪涮拗。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,190評(píng)論 1 299
  • 那天合搅,我揣著相機(jī)與錄音多搀,去河邊找鬼。 笑死灾部,一個(gè)胖子當(dāng)著我的面吹牛康铭,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播赌髓,決...
    沈念sama閱讀 40,078評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼从藤,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了锁蠕?” 一聲冷哼從身側(cè)響起夷野,我...
    開(kāi)封第一講書(shū)人閱讀 38,923評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎荣倾,沒(méi)想到半個(gè)月后悯搔,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,334評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡舌仍,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,550評(píng)論 2 333
  • 正文 我和宋清朗相戀三年妒貌,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了通危。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,727評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡灌曙,死狀恐怖菊碟,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情在刺,我是刑警寧澤逆害,帶...
    沈念sama閱讀 35,428評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站蚣驼,受9級(jí)特大地震影響魄幕,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜隙姿,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,022評(píng)論 3 326
  • 文/蒙蒙 一梅垄、第九天 我趴在偏房一處隱蔽的房頂上張望厂捞。 院中可真熱鬧输玷,春花似錦、人聲如沸靡馁。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,672評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)臭墨。三九已至赔嚎,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間胧弛,已是汗流浹背尤误。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,826評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留结缚,地道東北人损晤。 一個(gè)月前我還...
    沈念sama閱讀 47,734評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像红竭,于是被迫代替她去往敵國(guó)和親尤勋。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,619評(píng)論 2 354