1. 程式人生 > 程式設計 >Java基礎複習之旅(2)—IO/NIO篇

Java基礎複習之旅(2)—IO/NIO篇

1.基本概念

1.1 什麼是IO流?

\quad 他是一種資料的流,從源頭流到目的地。比如檔案的拷貝,輸入流從檔案中讀取到程式,輸出流從程式寫入檔案中。

1.2 位元組流與字元流的區別?

\quad 位元組流在JDK1.0的時候就被引入了,用以操作字符集型別為ASCII的資料。為了能夠操作Unicode型別的資料,JDK1.1引入了字元流。

1.3 ASCII Unicode 和 UTF-8

\quad 我們知道,計算機內部所有的資訊最終都是以位元組的形式儲存的,一個位元組有8位,每一位上都是0或者1。那麼總共有256種組合方式。在上世紀60年代,美國製定了一套英語字元與二進位制位之間的關係。這種關係被稱為ASCII碼,他一共規定了128個字元的編碼,比如空格是32,大寫的A是65。這128個字元只佔用了一個位元組的後七位,第一位設定為0。
\quad

但是世界上的語言不止包括128個字元,並且相同的ASCII碼也會被翻譯成不同的字元。因此,想開啟一個文字檔案,就必須知道他的編碼方式。這時候Unicode就出現了,他將世界上所有的符號都納入其中,每個符號都給予一個編碼,那麼亂碼的問題就解決了。
\quad 但是Unicode也存在問題,那就是只規定了符號的程式碼,並沒有規定二進位製程式碼如何儲存。比如說某個字元佔兩個位元組,那麼計算機怎麼知道他是代表兩個佔一個位元組的字元還是一個佔兩個位元組的字元呢?
\quad 網際網路的普及,迫切需要一種統一的編碼方式。UTF-8就是Unicode使用最廣泛的一種實現,還有UTF-16(用兩個或四個位元組表示一個字元),UTF-32(用四個位元組表示一個字元)。UTF-8最大的特點就是採用一種變長的編碼方式儲存字元。
UTF-8的編碼規則:

  • 1.對於單位元組的符號,位元組的第一位設為0,後面7位為這個符號的 Unicode 碼。因此對於英語字母,UTF-8 編碼和 ASCII 碼是相同的。

  • 2.對於n位元組的符號(n > 1),第一個位元組的前n位都設為1,第n + 1位設為0,後面位元組的前兩位一律設為10。剩下的沒有提及的二進位制位,全部為這個符號的 Unicode 碼。

舉例:
\quad對於單位元組字元:0XXXXXXX
\quad對於雙位元組字元:110XXXXXX | 10XXXXXX
\quad對於三位元組字元:1110XXXXX | 10XXXXXX | 10XXXXXX

1.4 Java中流的超類有哪些?

  • OutputStream/InputStream
  • Reader/Writer

舉例:

1.5 檔案拷貝的時候,哪一種流可以提升更高的屬性?

\quad 對於位元組流,可以採用BufferedInputStream或者BufferedOutputStream。
\quad 對於字元流,可以採用BufferedReader或者BufferedWriter。

1.6 FileInputStream與FileOutputStream是什麼?

\quad 這是在讀寫檔案時用到的兩個類,對於小檔案他們的效能表現的還不錯,但是對於大檔案時,儘量使用BufferedReader或者BufferedWriter。

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

\quad System是Java.lang包下的一個final類,out是其中的一個PrintStream型別的靜態成員變數,println是他的一個方法。

1.8 Java中的File類代表什麼?

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

1.9 什麼是NIO?

\quad NIO被翻譯為(new IO)或者(Non-blocking IO-非阻塞的IO),在Java7的時候被引入,與IO直接磁碟讀寫不同的是,NIO採用快取的方法對檔案進行操作。

1.10 IO與NIO的區別,他們各自的優缺點體現在哪些方面?

  • IO是面向流的,JVM訪問磁碟或者網路時,都是基於像流一樣的位元組來進行操作的。優點在於它非常直觀並且容易理解,但是缺點也非常明顯,就是慢。因為他是面向流的,也就意味著當這個執行緒在進行讀寫操作時,這個執行緒被阻塞,如果讀寫操作沒有完成,那麼這個執行緒就不能進行其他操作。
  • 而NIO是面向緩衝區(它由ByteBuffer抽象類中的byte[] hb實現)的,也就是說資料先被讀取到緩衝區,當緩衝區的資料量達到一定的大小後(比如BufferReader.readLine() ),一次寫入虛擬機器器中,減少了CPU等待磁碟讀寫所浪費的時間。並且NIO的基本資料型別是基於塊的,並且塊與塊之間是沒有順序的,這就保證了NIO的讀寫操作的可以同時進行。缺點就是資料處理的過程變得複雜,不容易理解。
  • 注意,在Windows系統中換行是\r\n,在Linux系統中是\n

2 分別從網路、程式中讀取字串

2.1 從網路中讀取

程式碼如下:

public static void getByteFromWeb() {
        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpGet httpGet = new HttpGet("https://www.baidu.com/");

        try (CloseableHttpResponse httpResponse = httpClient.execute(httpGet)) {
        //注意,我這裡並沒有設定編碼字符集
            InputStream inputStream = httpResponse.getEntity().getContent();
            int byteToInt;
            while ((byteToInt = inputStream.read()) != -1) {
                System.out.print((char) byteToInt);
            }
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }
複製程式碼

列印結果如下:

可以看到結果中有很多亂碼,這時候需要使用InputStreamRead的構造方法,為InputStream設定字符集:

InputStreamReader inputStream = new InputStreamReader(httpResponse.getEntity().getContent(),Charsets.UTF_8);
複製程式碼

這時候再列印結果就能看到我們想要的情形:

2.2 從程式中讀取

程式碼如下:

 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);
        }

    }
複製程式碼

通過這個發現好玩的事情,在new ProcessBuilder("{commend}")中寫入:

("cmd.exe","/C","start")
複製程式碼

可以通過程式碼啟動cmd,也可以通過

("C:\\Windows\\System32\\calc.exe")
複製程式碼

啟動本機的計算器。

3. 對文字檔案進行讀寫操作

3.1 將字元寫入檔案的方法一

\quad使用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 將字元寫入檔案的方法二

\quad使用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 將字元寫入檔案的方法三

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

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

3.4 將字元寫入檔案的方法四

\quad使用IOUtils.writeLines()整個寫入:

    public static void writeLinesToFiles4(List<String> lines,File file) {
        try {
         // 需要設定append為true,將位元組寫入檔案中    
            Writer writer = new FileWriter(file,true);
            IOUtils.writeLines(lines,null,writer);
         // OutputStream outputStream = new FileOutputStream(file);
         // IOUtils.writeLines(lines,outputStream,Charset.defaultCharset());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

複製程式碼

3.5 從檔案中讀取字元的方法一

\quad使用InputStream.read()方法逐個讀取:

public static List<String> readFile1(File file) {
        List<String> list = new ArrayList<>();
        //使用StringBuilder將獲取的單個字元拼接成字串
        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 從檔案中讀取字元的方法二

\quad使用BufferReader.readLine()方法逐行讀取:

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 從檔案中讀取字元的方法三

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

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 從檔案中讀取字元的方法四

\quad使用FileUtils.readFileToString()方法整個檔案讀取:

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 從檔案中讀取字元的方法五

\quad使用IOUtils.readLines()方法整個檔案讀取:

 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);
        }
    }
複製程式碼

4.參考資料