12.06 NIO?看完這篇文章,別再說不懂IO

Java程序員開發三年還不懂IO/NIO?看完這篇文章,別再說不懂IO/NIO

一.基本概念

1.1 什麼是IO流?

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

1.2 字節流與字符流的區別?

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

1.3 ASCII Unicode 和 UTF-8

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

但是世界上的語言不止包括128個字符,並且相同的ASCII碼也會被翻譯成不同的字符。因此,想打開一個文本文件,就必須知道他的編碼方式。這時候Unicode就出現了,他將世界上所有的符號都納入其中,每個符號都給予一個編碼,那麼亂碼的問題就解決了。

但是Unicode也存在問題,那就是隻規定了符號的代碼,並沒有規定二進制代碼如何存儲。比如說某個字符佔兩個字節,那麼計算機怎麼知道他是代表兩個佔一個字節的字符還是一個佔兩個字節的字符呢?

互聯網的普及,迫切需要一種統一的編碼方式。UTF-8就是Unicode使用最廣泛的一種實現,還有UTF-16(用兩個或四個字節表示一個字符),UTF-32(用四個字節表示一個字符)。UTF-8最大的特點就是採用一種變長的編碼方式存儲字符。

UTF-8的編碼規則

  • 對於單字節的符號,字節的第一位設為0,後面7位為這個符號的 Unicode 碼。因此對於英語字母,UTF-8 編碼和 ASCII 碼是相同的。
  • 對於n字節的符號(n > 1),第一個字節的前n位都設為1,第n + 1位設為0,後面字節的前兩位一律設為10。剩下的沒有提及的二進制位,全部為這個符號的 Unicode 碼。

舉例:

對於單字節字符:0XXXXXXX
對於雙字節字符:110XXXXXX | 10XXXXXX
對於三字節字符:1110XXXXX | 10XXXXXX | 10XXXXXX

1.4 Java中流的超類有哪些?

  • OutputStream/InputStream
  • Reader/Writer

舉例:

Java程序員開發三年還不懂IO/NIO?看完這篇文章,別再說不懂IO/NIO

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

  • 對於字節流,可以採用BufferedInputStream或者BufferedOutputStream。
  • 對於字符流,可以採用BufferedReader或者BufferedWriter。

1.6 FileInputStream與FileOutputStream是什麼?

這是在讀寫文件時用到的兩個類,對於小文件他們的額性能表現的還不錯,但是對於大文件時,儘量使用BufferedReader或者BufferedWriter。

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

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

1.8 Java中的File類代表什麼?

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

1.9 什麼是NIO?

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。


Java程序員開發三年還不懂IO/NIO?看完這篇文章,別再說不懂IO/NIO

二. 分別從網絡、進程中讀取字符串

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

打印結果如下:

Java程序員開發三年還不懂IO/NIO?看完這篇文章,別再說不懂IO/NIO

可以看到結果中有很多亂碼,這時候需要使用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")

啟動本機的計算器。


Java程序員開發三年還不懂IO/NIO?看完這篇文章,別再說不懂IO/NIO

三. 對文本文件進行讀寫操作

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

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

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

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

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

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

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

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

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

使用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;
}
/<string>/<string>
Java程序員開發三年還不懂IO/NIO?看完這篇文章,別再說不懂IO/NIO

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

使用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;
}
/<string>/<string>

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

使用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;
}
/<string>/<string>

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

使用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;
}
/<string>/<string>

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

使用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);
}
}
/<string>

讀者福利:我把近一年經歷過的Java崗位面試,和一些刷過的面試題都做成了PDF,PDF都是可以免費分享給大家的,只要關注私信我:【888】,就能獲取免費領取方式!


分享到:


相關文章: