1.數據流

程序很少作為一個孤島存在。程序通常以某種方式與“外部世界”交互。這可以通過從鍵盤讀取數據、發送消息、從 Internet 下載頁面,或者相反,將文件上傳到遠程服務器來實現。

我們可以用一個詞來指代所有這些行為:程序與外界之間的數據交換。等等,這不僅僅是一個詞。

當然,數據交換本身可以分為接收數據和發送數據兩部分。例如,您使用Scanner對像從鍵盤讀取數據——這就是接收數據。然後您使用命令在屏幕上顯示數據System.out.println()——這就是發送數據。

在編程中,術語“流”用於描述數據交換。這個詞是從哪裡來的?

在現實生活中,你可以有一股水流,也可以有一股意識流。在編程中,我們有數據流

流是一種多功能工具。它們允許程序從任何地方接收數據(輸入流)並向任何地方發送數據(輸出流)。因此,有兩種類型:

  • 輸入流用於接收數據
  • 輸出流用於發送數據

為了使流“有形”,Java 的創建者編寫了兩個類:InputStreamOutputStream.

該類InputStream有一個read()方法可以讓您從中讀取數據。該類OutputStream有一個write()方法可以讓你向它寫入數據。他們還有其他方法,但稍後會詳細介紹。

字節流

我們在談論什麼樣的數據?它採用什麼格式?換句話說,這些類支持哪些數據類型?

這些是通用類,因此它們支持最常見的數據類型 — byte. AnOutputStream可以寫入字節(和字節數組),而InputStream對象可以讀取字節(或字節數組)。就是這樣——它們不支持任何其他數據類型。

因此,這些流也被稱為字節流

流的一個特點是它們的數據只能按順序讀取(或寫入)。如果不讀取流之前的所有數據,就無法從流的中間讀取數據。

這就是通過類從鍵盤讀取數據的方式Scanner:您逐行順序地從鍵盤讀取數據。我們讀一行,然後讀下一行,再讀下一行,依此類推。恰當地,讀取行的方法稱為nextLine()

將數據寫入 anOutputStream也是順序發生的。一個很好的例子是控制台輸出。您輸出一行,然後是另一行。這是順序輸出。你不能輸出第一行,然後是第十行,然後是第二行。所有數據僅按順序寫入輸出流。

字符流

您最近了解到字符串是第二流行的數據類型,而且確實如此。許多信息以字符和整個字符串的形式傳遞。計算機擅長以字節形式發送和接收所有內容,但人類並不是那麼完美。

考慮到這一事實,Java 程序員編寫了另外兩個類:ReaderWriter. 該類Reader與類類似InputStream,但其read()方法讀取的不是字節,而是字符 ( char)。班級Writer對應班級OutputStream。就像Reader類一樣,它適用於字符 ( char),而不是字節。

如果我們比較這四個類,我們會得到下圖:

字節(字節) 字符(字符)
讀取數據
InputStream
Reader
寫入數據
OutputStream
Writer

實際應用

、和類本身不被任何人直接使用,因為它們不與InputStream任何可從中讀取數據(或可寫入數據)的具體對象相關聯。但是這四個類有很多可以做很多事情的後代類。OutputStreamReaderWriter


2.InputStream

該類InputStream很有趣,因為它是數百個後代類的父類。它自己沒有任何數據,但它有所有派生類都繼承的方法。

一般情況下,流對像很少在內部存儲數據。流是讀取/寫入數據的工具,但不是存儲。也就是說,也有例外。

InputStream該類及其所有子類的方法:

方法 描述
int read()
從流中讀取一個字節
int read(byte[] buffer)
從流中讀取字節數組
byte[] readAllBytes()
從流中讀取所有字節
long skip(long n)
跳過n流中的字節(讀取並丟棄它們)
int available()
檢查流中剩餘的字節數
void close()
關閉流

讓我們簡要介紹一下這些方法:

read()方法

該方法從流中read()讀取一個字節並將其返回。您可能會對返回類型感到困惑int。選擇此類型是因為它int是標準整數類型。的前三個字節int將為零。

read(byte[] buffer)方法

這是該方法的第二個變體read()InputStream它允許您一次從 all 中讀取一個字節數組。將存儲字節的數組必須作為參數傳遞。該方法返回一個數字——實際讀取的字節數。

假設您有一個 10 KB 的緩衝區,並且您正在使用該類從文件中讀取數據FileInputStream。如果文件僅包含 2 KB,則所有數據都將加載到緩衝區數組中,並且該方法將返回數字 2048(2 KB)。

readAllBytes()方法

一個很好的方法。它只是從中讀取所有數​​據,InputStream直到用完並將其作為單字節數組返回。這對於讀取小文件非常方便。大文件在物理上可能不適合內存,並且該方法將拋出異常。

skip(long n)方法

此方法允許您跳過對象的前 n 個字節InputStream。因為數據是嚴格按順序讀取的,所以此方法只是從流中讀取前 n 個字節並將其丟棄。

返回實際跳過的字節數(如果流在n跳過字節之前結束)。

int available()方法

該方法返回流中剩餘的字節數

void close()方法

close()方法關閉數據流並釋放與之關聯的外部資源。一旦流關閉,就不能再從中讀取數據。

讓我們編寫一個示例程序來複製一個非常大的文件。我們不能使用該readAllBytes()方法將整個文件讀入內存。例子:

代碼 筆記
String src = "c:\\projects\\log.txt";
String dest = "c:\\projects\\copy.txt";

try(FileInputStream input = new FileInputStream(src);
FileOutputStream output = new FileOutputStream(dest))
{
   byte[] buffer = new byte[65536]; // 64Kb
   while (input.available() > 0)
   {
      int real = input.read(buffer);
      output.write(buffer, 0, real);
   }
}



InputStream用於從文件中讀取
OutputStream用於寫入

文件 將數據讀入的緩衝區
只要流中有數據

將數據讀入緩衝區
將緩衝區中的數據寫入第二個流

在此示例中,我們使用了兩個類:是用於從文件讀取數據FileInputStream的後代,以及用於將數據寫入文件的後代。稍後我們將討論第二類。InputStreamFileOutputStreamOutputStream

這裡另一個有趣的點是real變量。當從文件中讀取最後一個數據塊時,它的數據量很可能少於 64KB。因此,我們需要輸出的不是整個緩衝區,而是其中的一部分——第一個real字節。這正是write()方法中發生的事情。



3.Reader

該類Reader是該類的完整類比InputStream。唯一的區別是它使用字符 ( char),而不是字節。就像InputStream類一樣,Reader類本身不會在任何地方使用:它是數百個子類的父類,並為所有子類定義通用方法。

Reader該類(及其所有後代類)的方法:

方法 描述
int read()
char從流中讀取一個
int read(char[] buffer)
char從流中讀取數組
long skip(long n)
跳過n chars流(讀取並丟棄它們)
boolean ready()
檢查流中是否還有剩餘的東西
void close()
關閉流

這些方法與類中的方法非常相似InputStream,儘管存在細微差別。

int read()方法

此方法char從流中讀取一個並返回它。類型char擴展為int,但結果的前兩個字節始終為零。

int read(char[] buffer)方法

這是該方法的第二個變體read()Reader它使您可以一次從 a 中讀取一個 char 數組。將存儲字符的數組必須作為參數傳遞。該方法返回一個數字——實際讀取的字符數。

skip(long n)方法

此方法允許您跳過對象的前 n 個字符Reader。它的工作原理與類的類似方法完全相同InputStream。返回實際跳過的字符數。

boolean ready()方法

true如果流中有未讀字節則返回。

void close()方法

close()方法關閉數據流並釋放與之關聯的外部資源。一旦流關閉,就不能再從中讀取數據。

為了比較,讓我們編寫一個複製文本文件的程序:

代碼 筆記
String src = "c:\\projects\\log.txt";
String dest = "c:\\projects\\copy.txt";

try(FileReader reader = new FileReader(src);
FileWriter writer = new FileWriter(dest))
{
   char[] buffer = new char[65536]; // 128Kb
   while (reader.ready())
   {
      int real = reader.read(buffer);
      writer.write(buffer, 0, real);
   }
}



Reader用於從文件中
Writer讀取 用於寫入文件

我們將讀取數據的緩衝區
只要流中有數據

將數據讀入緩衝區
將緩衝區中的數據寫入第二個流