Java程序員開(kāi)發(fā)三年還不懂IO/NIO?看完這篇文章,別再說(shuō)不懂IO/NIO

一.基本概念

1.1 什么是IO流?

他是一種數(shù)據(jù)的流,從源頭流到目的地。比如文件的拷貝,輸入流從文件中讀取到進(jìn)程,輸出流從進(jìn)程寫入文件中。

1.2 字節(jié)流與字符流的區(qū)別?

字節(jié)流在JDK1.0的時(shí)候就被引入了,用以操作字符集類型為ASCII的數(shù)據(jù)。為了能夠操作Unicode類型的數(shù)據(jù),JDK1.1引入了字符流。

1.3 ASCII Unicode 和 UTF-8

我們知道,計(jì)算機(jī)內(nèi)部所有的信息最終都是以字節(jié)的形式存儲(chǔ)的,一個(gè)字節(jié)有8位,每一位上都是0或者1。那么總共有256種組合方式。在上世紀(jì)60年代,美國(guó)制定了一套英語(yǔ)字符與二進(jìn)制位之間的關(guān)系。這種關(guān)系被稱為ASCII碼,他一共規(guī)定了128個(gè)字符的編碼,比如空格是32,大寫的A是65。這128個(gè)字符只占用了一個(gè)字節(jié)的最后一位,第一位設(shè)置為0。

但是世界上的語(yǔ)言不止包括128個(gè)字符,并且相同的ASCII碼也會(huì)被翻譯成不同的字符。因此,想打開(kāi)一個(gè)文本文件,就必須知道他的編碼方式。這時(shí)候Unicode就出現(xiàn)了,他將世界上所有的符號(hào)都納入其中,每個(gè)符號(hào)都給予一個(gè)編碼,那么亂碼的問(wèn)題就解決了。

但是Unicode也存在問(wèn)題,那就是只規(guī)定了符號(hào)的代碼,并沒(méi)有規(guī)定二進(jìn)制代碼如何存儲(chǔ)。比如說(shuō)某個(gè)字符占兩個(gè)字節(jié),那么計(jì)算機(jī)怎么知道他是代表兩個(gè)占一個(gè)字節(jié)的字符還是一個(gè)占兩個(gè)字節(jié)的字符呢?

互聯(lián)網(wǎng)的普及,迫切需要一種統(tǒng)一的編碼方式。UTF-8就是Unicode使用最廣泛的一種實(shí)現(xiàn),還有UTF-16(用兩個(gè)或四個(gè)字節(jié)表示一個(gè)字符),UTF-32(用四個(gè)字節(jié)表示一個(gè)字符)。UTF-8最大的特點(diǎn)就是采用一種變長(zhǎng)的編碼方式存儲(chǔ)字符。

UTF-8的編碼規(guī)則

  • 對(duì)于單字節(jié)的符號(hào),字節(jié)的第一位設(shè)為0,后面7位為這個(gè)符號(hào)的 Unicode 碼。因此對(duì)于英語(yǔ)字母,UTF-8 編碼和 ASCII 碼是相同的。
  • 對(duì)于n字節(jié)的符號(hào)(n > 1),第一個(gè)字節(jié)的前n位都設(shè)為1,第n + 1位設(shè)為0,后面字節(jié)的前兩位一律設(shè)為10。剩下的沒(méi)有提及的二進(jìn)制位,全部為這個(gè)符號(hào)的 Unicode 碼。

舉例:

對(duì)于單字節(jié)字符:0XXXXXXX
對(duì)于雙字節(jié)字符:110XXXXXX | 10XXXXXX
對(duì)于三字節(jié)字符:1110XXXXX | 10XXXXXX | 10XXXXXX

1.4 Java中流的超類有哪些?

  • OutputStream/InputStream
  • Reader/Writer

舉例:

1.5 文件拷貝的時(shí)候,哪一種流可以提升更高的屬性?

  • 對(duì)于字節(jié)流,可以采用BufferedInputStream或者BufferedOutputStream。
  • 對(duì)于字符流,可以采用BufferedReader或者BufferedWriter。

1.6 FileInputStream與FileOutputStream是什么?

這是在讀寫文件時(shí)用到的兩個(gè)類,對(duì)于小文件他們的額性能表現(xiàn)的還不錯(cuò),但是對(duì)于大文件時(shí),盡量使用BufferedReader或者BufferedWriter。

1.7 System.out.println()的三個(gè)部分分別是什么?

System是Java.lang包下的一個(gè)final類,out是其中的一個(gè)PrintStream類型的靜態(tài)成員變量,println是他的一個(gè)方法。

1.8 Java中的File類代表什么?

引用Java doc中的描述:
An abstract representation of file and directory pathnames.-對(duì)文件及文件路徑的抽象代表。意思就是File類只代表這個(gè)文件的路徑,而不代表文件對(duì)象。在Java7引入的nio包中的Path類與io中的File本質(zhì)上是一個(gè)東西。(Path.toFile()=File.toPath(),二者之間可以相互轉(zhuǎn)換)。

1.9 什么是NIO?

NIO被翻譯為(new IO)或者(Non-blocking IO-非阻塞的IO),在Java7的時(shí)候被引入,與IO直接磁盤讀寫不同的是,NIO采用緩存的方法對(duì)文件進(jìn)行操作。

1.10 IO與NIO的區(qū)別,他們各自的優(yōu)缺點(diǎn)體現(xiàn)在哪些方面?

  • IO是面向流的,JVM訪問(wèn)磁盤或者網(wǎng)絡(luò)時(shí),都是基于像流一樣的字節(jié)來(lái)進(jìn)行操作的。優(yōu)點(diǎn)在于它非常直觀并且容易理解,但是缺點(diǎn)也非常明顯,就是慢。因?yàn)樗敲嫦蛄鞯?,也就意味著?dāng)這個(gè)線程在進(jìn)行讀寫操作時(shí),這個(gè)線程被阻塞,如果讀寫操作沒(méi)有完成,那么這個(gè)線程就不能進(jìn)行其他操作。
  • 而NIO是面向緩沖區(qū)(它由ByteBuffer抽象類中的byte[] hb實(shí)現(xiàn))的,也就是說(shuō)數(shù)據(jù)先被讀取到緩沖區(qū),當(dāng)緩沖區(qū)的數(shù)據(jù)量達(dá)到一定的大小后(比如BufferReader.readLine() ),一次寫入虛擬機(jī)中,減少了CPU等待磁盤讀寫所浪費(fèi)的時(shí)間。并且NIO的基本數(shù)據(jù)類型是基于塊的,并且塊與塊之間是沒(méi)有順序的,這就保證了NIO的讀寫操作的可以同時(shí)進(jìn)行。缺點(diǎn)就是數(shù)據(jù)處理的過(guò)程變得復(fù)雜,不容易理解。
  • 注意,在Windows系統(tǒng)中換行是\r\n,在Linux系統(tǒng)中是\n。

二. 分別從網(wǎng)絡(luò)、進(jìn)程中讀取字符串

2.1 從網(wǎng)絡(luò)中讀取

代碼如下:

public static void getByteFromWeb() {
    CloseableHttpClient httpClient = HttpClients.createDefault();
    HttpGet httpGet = new HttpGet("https://www.baidu.com/");
    try (CloseableHttpResponse httpResponse = httpClient.execute(httpGet)) {
        //注意,我這里并沒(méi)有設(shè)置編碼字符集
        InputStream inputStream = httpResponse.getEntity().getContent();
        int byteToint;
        while ((byteToint = inputStream.read()) != -1) {
            System.out.print((char) byteToint);
        }
    }
    catch (Exception e){
        throw new RuntimeException(e);
    }
}

打印結(jié)果如下:

可以看到結(jié)果中有很多亂碼,這時(shí)候需要使用InputStreamRead的構(gòu)造方法,為InputStream設(shè)置字符集:

InputStreamReader inputStream = new InputStreamReader(httpResponse.getEntity().getContent(),Charsets.UTF_8);

這時(shí)候再打印結(jié)果就能看到我們想要的情形:

2.2 從進(jìn)程中讀取

代碼如下:

public static void getByteFromProcess(){
    try {
        ProcessBuilder processBuilder = new ProcessBuilder("{commend}");
        Process start = processBuilder.start();
        InputStreamReader inputStream = new InputStreamReader(start.getInputStream(),Charsets.UTF_8);
        int byteToint;
        while ((byteToint = inputStream.read())!=-1){
            System.out.print((char)byteToint);
        }
    }
    catch (Exception e){
        throw new RuntimeException(e);
    }
}

通過(guò)這個(gè)發(fā)現(xiàn)好玩的事情,在new ProcessBuilder("{commend}")中寫入:

("cmd.exe", "/C", "start")

可以通過(guò)代碼啟動(dòng)cmd,也可以通過(guò)

("C:\\Windows\\System32\\calc.exe")

啟動(dòng)本機(jī)的計(jì)算器。

三. 對(duì)文本文件進(jìn)行讀寫操作

3.1 將字符寫入文件的方法一

使用commons-io包里面的FileUtils.writeLines()方法,引入Maven依賴后:

public static void writeLinesToFile1(List<String> lines, File file) {
    try {
        FileUtils.writeLines(file, lines);
    }
    catch (IOException e) {
        throw new RuntimeException(e);
    }
}

3.2 將字符寫入文件的方法二

使用BufferedWriter.write()方法逐行寫入:

public static void writeLinesToFile2(List<String> lines, File file) {
    try {
        Writer writer = new FileWriter(file);
        BufferedWriter bufferedWriter = new BufferedWriter(writer);
        for (String s : lines
                    ) {
            bufferedWriter.write(s);
            bufferedWriter.write("n");
            }
            bufferedWriter.close();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

3.3 將字符寫入文件的方法三

使用Files工具中的write()方法整個(gè)寫入:

public static void writeLinesToFile3(List<String> lines, File file) {
    try {
        Files.write(file.toPath(), lines);
    }
    catch (IOException e) {
        throw new RuntimeException(e);
    }
}

3.4 將字符寫入文件的方法四

使用IOUtils.writeLines()整個(gè)寫入:

public static void writeLinesToFiles4(List<String> lines, File file) {
    try {
        // 二選一
        Writer writer = new FileWriter(file, true);
        IOUtils.writeLines(lines, null, writer);
        // OutputStream outputStream = new FileOutputStream(file);
        // IOUtils.writeLines(lines, null, outputStream, Charset.defaultCharset());
    }
    catch (Exception e) {
        throw new RuntimeException(e);
    }
}

3.5 從文件中讀取字符的方法一

使用InputStream.read()方法逐個(gè)讀?。?/p>

public static List<String> readFile1(File file) {
    List<String> list = new ArrayList<>();
    //使用StringBuilder將獲取的單個(gè)字符拼接成字符串
    StringBuilder stringBuilder = new StringBuilder();
    try {
        InputStream inputStream = new FileInputStream(file);
        int byteToint;
        while ((byteToint = inputStream.read()) != -1) {
            char c = (char) byteToint;
            if (c != 'r' && c != 'n') {
                    stringBuilder.append((char) byteToInt);
                } else {
                    if (!stringBuilder.toString().equals("")) {
                        list.add(stringBuilder.toString());
                        stringBuilder.delete(0, stringBuilder.length());
                    }
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return list;
    }

3.6 從文件中讀取字符的方法二

使用BufferReader.readLine()方法逐行讀?。?/p>

public static List<String> readFile2(File file) {
    List<String> list = new ArrayList<>();
    try {
        BufferedReader bufferedReader = new BufferedReader(new FileReader(file));
        String s;
        while ((s = bufferedReader.readLine()) != null) {
            list.add(s);
        }
    }
    catch (Exception e) {
        throw new RuntimeException(e);
    }
    return list;
}

3.7 從文件中讀取字符的方法三

使用Files工具中的readAllLines()方法整個(gè)讀取:

public static List<String> readFile3(File file) {
    List<String> list;
    try {
        list = Files.readAllLines(file.toPath());
    }
    catch (IOException e) {
        throw new RuntimeException(e);
    }
    System.out.println();
    return list;
}

3.8 從文件中讀取字符的方法四

使用FileUtils.readFileToString()方法整個(gè)文檔讀?。?/p>

public static List<String> readFile4(File file){
    List<String> list = new ArrayList<>();
    try {
        list.add(FileUtils.readFileToString(file, Charset.defaultCharset()));
    }
    catch (IOException e) {
        throw new RuntimeException(e);
    }
    return list;
}

3.9 從文件中讀取字符的方法五

使用IOUtils.readLines()方法整個(gè)文檔讀?。?/p>

public static List<String> readFile5(File file) {
    try {
        InputStream inputStream = new FileInputStream(file);
        return IOUtils.readLines(inputStream, Charset.defaultCharset());
    }
    catch (IOException e) {
        throw new RuntimeException(e);
    }
}

推薦閱讀

Java架構(gòu)師進(jìn)階之路<常用資料分享>

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • 轉(zhuǎn)自 http://www.ibm.com/developerworks/cn/education/java/j-...
    抓兔子的貓閱讀 2,484評(píng)論 0 22
  • tags:io categories:總結(jié) date: 2017-03-28 22:49:50 不僅僅在JAVA領(lǐng)...
    行徑行閱讀 2,300評(píng)論 0 3
  • 五、IO流 1、IO流概述 (1)用來(lái)處理設(shè)備(硬盤,控制臺(tái),內(nèi)存)間的數(shù)據(jù)。(2)java中對(duì)數(shù)據(jù)的操作都是通過(guò)...
    佘大將軍閱讀 585評(píng)論 0 0
  • Java的IO和NIO 一、Java的IO Java的IO功能在java.io包下,包括輸入、輸出兩種IO流,每種...
    王小冬閱讀 1,076評(píng)論 0 9
  • Java NIO(New IO)是從Java 1.4版本開(kāi)始引入的一個(gè)新的IO API,可以替代標(biāo)準(zhǔn)的Java I...
    JackChen1024閱讀 7,939評(píng)論 1 143

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