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)建實例。

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

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

處理字符流:Reader、Writer。

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

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

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

當(dāng)使用節(jié)點流時,程序可以直接連接到實際的數(shù)據(jù)源。當(dāng)使用處理流時,程序并不會直接連接到實際的數(shù)據(jù)源。

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

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

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

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

  • 對開發(fā)人員來講,使用處理流進(jìn)行輸入輸出操作更簡單;
  • 使用處理流的執(zhí)行效率更高。

使用節(jié)點流的例子:

package com.wangjun.othersOfJava;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
/*
 * 節(jié)點流演示
 */
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方法返回實際讀取的字節(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é)點流輸出流,使用println輸入字符和對象
 * PrintStream輸出功能非常強大,我們平時常用的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é)流的功能比字符流的功能強大,因為計算機(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ìn)制文件,其實計算機(jī)所有的文件都是二進(jìn)制文件,當(dāng)二進(jìn)制文件內(nèi)容恰好能被正常解析成字符時,則該二進(jìn)制文件就變成了文本文件。

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

1.3 轉(zhuǎn)換流

上面的表格中列舉了java提供的兩個轉(zhuǎn)換流。InputStreamReader和OutputStreamWriter用于將字節(jié)流轉(zhuǎn)換成字符流。為什么要這么做呢?前面提到了字節(jié)流的功能比較強大,字符流使用起來方便,如果有一個字節(jié)流內(nèi)容都是文本內(nèi)容,那么將它轉(zhuǎn)換成字符流更方便操作。

代碼實例

package com.wangjun.othersOfJava;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

/*
 * 字節(jié)流轉(zhuǎn)字符流示例
 * System.in是鍵盤輸入,是InputStream(字節(jié)流)的實例,因為鍵盤輸入都是字符,所以轉(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ù)組時才會從原始輸入流中讀取。

代碼實例

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)。

代碼實例

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()方法可以運行平臺上的其他程序,該方法產(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)程的輸出流。

代碼實例

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ù)
        // 運行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());
            }
        }
    }
}

輸出到其他程序,代碼運行沒有成功,待分析...

Java的NIO

BufferedReader有一個特征,就是讀取輸入流中的數(shù)據(jù)時,如果沒有讀到有效數(shù)據(jù),程序?qū)⒃诖颂幾枞摼€程的執(zhí)行(使用InputStream的read方法從流中讀取數(shù)據(jù)時,也有這樣的特性),java.io下面的輸入輸出流都是阻塞式的。不僅如此,傳統(tǒng)的輸入輸出流都是通過字節(jié)的移動來處理的,及時我們不直接去處理字節(jié)流,單底層的實現(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é)點流獲取的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)閉的資源必須實現(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可使一個單獨的線程管理多個 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ū)寫入通道也類似。因為NIO將阻塞交給了后臺線程執(zhí)行,非阻塞IO的空余時間可以用于其他通道上進(jìn)行IO,因此可以一個線程可以管理多個通道,而IO是阻塞的,阻塞式的IO每個連接必須開一個線程來處理,每個線程都需要珍貴的CPU資源,等待IO的時候,CPU資源沒有被釋放就被浪費掉了。
  • Selectors(選擇器):選擇器允許一個單獨的線程可以監(jiān)聽多個數(shù)據(jù)通道((網(wǎng)絡(luò)連接或文件),你可以注冊多個通道使用一個選擇器,然后使用一個單獨的線程來“選擇”通道:這些通道里已經(jīng)有可以處理的輸入,或者選擇已準(zhǔn)備寫入的通道。這種選擇機(jī)制,使得一個單獨的線程很容易來管理多個通道。但付出的代價是解析數(shù)據(jù)可能會比從一個阻塞流中讀取數(shù)據(jù)更復(fù)雜。

使用場景

NIO

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

傳統(tǒng)的IO

  • 適用于一個線程管理一個通道的情況;因為其中的流數(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)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容