Java IO 體系看起來類不少,感受很複雜,但實際上是 IO 涉及的因素太多了。在設計 IO 相關的類時,編寫者也不是從同一個方面考慮的,因此會給人一種很亂的感受,而且還有設計模式的使用,更加難以使用這些 IO 類,因此特意對 Java 的 IO 作一個總結。java
IO 類設計出來,確定是爲了解決 IO 相關的操做的,想想哪裏會有 IO 操做?網絡、磁盤。網絡操做相關的類是在 java.net 包下,不在本文的總結範圍內。提到磁盤,你可能會想到文件,文件操做在 IO 中是比較典型的操做。在 Java 中引入了 「流」 的概念,它表示任何有能力產生數據源或有能力接收數據源的對象。數據源能夠想象成水源,海水、河水、湖水、一杯水等等。數據傳輸能夠想象爲水的運輸,古代有用桶運水,用竹管運水的,如今有鋼管運水,不一樣的運輸方式對應不一樣的運輸特性。git
從數據來源或者說是操做對象角度看,IO 類能夠分爲:編程
數據源節點也能夠再進行二次處理,使數據更加容易使用,因此還能夠劃分紅節點流和處理流,這裏涉及到設計模式,後面會有專門的文章說。 設計模式
從數據傳輸方式或者說是運輸方式角度看,能夠將 IO 類分爲:字節流是以一個字節單位來運輸的,好比一杯一杯的取水。而字符流是以多個字節來運輸的,好比一桶一桶的取水,一桶水又能夠分爲幾杯水。數組
字節流和字符流的區別:緩存
字節流讀取單個字節,字符流讀取單個字符(一個字符根據編碼的不一樣,對應的字節也不一樣,如 UTF-8 編碼是 3 個字節,中文編碼是 2 個字節。)字節流用來處理二進制文件(圖片、MP三、視頻文件),字符流用來處理文本文件(能夠看作是特殊的二進制文件,使用了某種編碼,人能夠閱讀)。簡而言之,字節是個計算機看的,字符纔是給人看的。微信
字節流和字符流的劃分能夠看下面這張圖。 網絡
不能否認,Java IO 相關的類確實不少,但咱們並非全部的類都會用到,咱們經常使用的也就是文件相關的幾個類,如文件最基本的讀寫類 File 開頭的、文件讀寫帶緩衝區的類 Buffered 開頭的類,對象序列化反序列化相關的類 Object 開頭的類。app
IO 類雖然不少,但最基本的是 4 個抽象類:InputStream、OutputStream、Reader、Writer。最基本的方法也就是一個讀 read() 方法、一個寫 write() 方法。方法具體的實現仍是要看繼承這 4 個抽象類的子類,畢竟咱們平時使用的也是子類對象。這些類中的一些方法都是(Native)本地方法、因此並無 Java 源代碼,這裏給出筆者以爲不錯的 Java IO 源碼分析 傳送門,按照上面這個思路看,先看子類基本方法,而後在看看子類中還新增了那些方法,相信你也能夠看懂的,我這裏就只對上後面說的經常使用的類進行總結。源碼分析
先來看 InputStream 和 OutStream 中的方法簡介,由於都是抽象類、大都是抽象方法、因此就不貼源碼嘍!注意這裏的讀取和寫入,其實就是獲取(輸入)數據和輸出數據。
InputStream 類
方法 | 方法介紹 |
---|---|
public abstract int read() | 讀取數據 |
public int read(byte b[]) | 將讀取到的數據放在 byte 數組中,該方法其實是根據下面的方法實現的,off 爲 0,len 爲數組的長度 |
public int read(byte b[], int off, int len) | 從第 off 位置讀取 len 長度字節的數據放到 byte 數組中,流是以 -1 來判斷是否讀取結束的(注意這裏讀取的雖然是一個字節,可是返回的倒是 int 類型 4 個字節,這裏固然是有緣由,這裏就再也不細說了,推薦這篇文章,連接) |
public long skip(long n) | 跳過指定個數的字節不讀取,想一想看電影跳過片頭片尾 |
public int available() | 返回可讀的字節數量 |
public void close() | 讀取完,關閉流,釋放資源 |
public synchronized void mark(int readlimit) | 標記讀取位置,下次還能夠從這裏開始讀取,使用前要看當前流是否支持,可使用 markSupport() 方法判斷 |
public synchronized void reset() | 重置讀取位置爲上次 mark 標記的位置 |
public boolean markSupported() | 判斷當前流是否支持標記流,和上面兩個方法配套使用 |
OutputStream 類
方法 | 方法介紹 |
---|---|
public abstract void write(int b) | 寫入一個字節,能夠看到這裏的參數是一個 int 類型,對應上面的讀方法,int 類型的 32 位,只有低 8 位才寫入,高 24 位將捨棄。 |
public void write(byte b[]) | 將數組中的全部字節寫入,和上面對應的 read() 方法相似,實際調用的也是下面的方法。 |
public void write(byte b[], int off, int len) | 將 byte 數組從 off 位置開始,len 長度的字節寫入 |
public void flush() | 強制刷新,將緩衝中的數據寫入 |
public void close() | 關閉輸出流,流被關閉後就不能再輸出數據了 |
再來看 Reader 和 Writer 類中的方法,你會發現和上面兩個抽象基類中的方法很像。
Reader 類
方法 | 方法介紹 |
---|---|
public int read(java.nio.CharBuffer target) | 讀取字節到字符緩存中 |
public int read() | 讀取單個字符 |
public int read(char cbuf[]) | 讀取字符到指定的 char 數組中 |
abstract public int read(char cbuf[], int off, int len) | 從 off 位置讀取 len 長度的字符到 char 數組中 |
public long skip(long n) | 跳過指定長度的字符數量 |
public boolean ready() | 和上面的 available() 方法相似 |
public boolean markSupported() | 判斷當前流是否支持標記流 |
public void mark(int readAheadLimit) | 標記讀取位置,下次還能夠從這裏開始讀取,使用前要看當前流是否支持,可使用 markSupport() 方法判斷 |
public void reset() | 重置讀取位置爲上次 mark 標記的位置 |
abstract public void close() | 關閉流釋放相關資源 |
Writer 類
方法 | 方法介紹 |
---|---|
public void write(int c) | 寫入一個字符 |
public void write(char cbuf[]) | 寫入一個字符數組 |
abstract public void write(char cbuf[], int off, int len) | 從字符數組的 off 位置寫入 len 數量的字符 |
public void write(String str) | 寫入一個字符串 |
public void write(String str, int off, int len) | 從字符串的 off 位置寫入 len 數量的字符 |
public Writer append(CharSequence csq) | 追加吸入一個字符序列 |
public Writer append(CharSequence csq, int start, int end) | 追加寫入一個字符序列的一部分,從 start 位置開始,end 位置結束 |
public Writer append(char c) | 追加寫入一個 16 位的字符 |
abstract public void flush() | 強制刷新,將緩衝中的數據寫入 |
abstract public void close() | 關閉輸出流,流被關閉後就不能再輸出數據了 |
下面咱們就直接使用他們的子類,在使用中再介紹下面沒有的新方法。
一、讀取控制檯中的輸入
import java.io.*;
public class IOTest {
public static void main(String[] args) throws IOException {
// 三個測試方法
// test01();
// test02();
test03();
}
public static void test01() throws IOException {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
System.out.println("請輸入一個字符");
char c;
c = (char) bufferedReader.read();
System.out.println("你輸入的字符爲"+c);
}
public static void test02() throws IOException {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
System.out.println("請輸入一個字符,按 q 鍵結束");
char c;
do {
c = (char) bufferedReader.read();
System.out.println("你輸入的字符爲"+c);
} while (c != 'q');
}
public static void test03() throws IOException {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
System.out.println("請輸入一行字符");
String str = bufferedReader.readLine();
System.out.println("你輸入的字符爲" + str);
}
}
複製代碼
至於控制檯的輸出,咱們其實一直都在使用呢,System.out.println()
,out 實際上是 PrintStream 類對象的引用,PrintStream 類中固然也有 write() 方法,可是咱們更經常使用 print() 方法和 println() 方法,由於這兩個方法能夠輸出的內容種類更多,好比一個打印一個對象,實際調用的對象的 toString() 方法。
二、二進制文件的寫入和讀取
注意這裏文件的路徑,能夠根據本身狀況改一下,雖然這裏的文件後綴是txt,但該文件倒是一個二進制文件,並不能直接查看。
@Test
public void test04() throws IOException {
byte[] bytes = {12,21,34,11,21};
FileOutputStream fileOutputStream = new FileOutputStream(new File("").getAbsolutePath()+"/io/test.txt");
// 寫入二進制文件,直接打開會出現亂碼
fileOutputStream.write(bytes);
fileOutputStream.close();
}
@Test
public void test05() throws IOException {
FileInputStream fileInputStream = new FileInputStream(new File("").getAbsolutePath()+"/io/test.txt");
int c;
// 讀取寫入的二進制文件,輸出字節數組
while ((c = fileInputStream.read()) != -1) {
System.out.print(c);
}
}
複製代碼
三、文本文件的寫入和讀取
write() 方法和 append() 方法並非像方法名那樣,一個是覆蓋內容,一個是追加內容,append() 內部也是 write() 方法實現的,也非說區別,也就是 append() 方法能夠直接寫 null,而 write() 方法須要把 null 當成一個字符串寫入,因此二者並沒有本質的區別。須要注意的是這裏並無指定文件編碼,可能會出現亂碼的問題。
@Test
public void test06() throws IOException {
FileWriter fileWriter = new FileWriter(new File("").getAbsolutePath()+"/io/test.txt");
fileWriter.write("Hello,world!\n歡迎來到 java 世界\n");
fileWriter.write("不會覆蓋文件本來的內容\n");
// fileWriter.write(null); 不能直接寫入 null
fileWriter.append("並非追加一行內容,不要被方法名迷惑\n");
fileWriter.append(null);
fileWriter.flush();
System.out.println("文件的默認編碼爲" + fileWriter.getEncoding());
fileWriter.close();
}
@Test
public void test07() throws IOException {
FileWriter fileWriter = new FileWriter(new File("").getAbsolutePath()+"/io/test.txt", false); // 關閉追加模式,變爲覆蓋模式
fileWriter.write("Hello,world!歡迎來到 java 世界\n");
fileWriter.write("我來覆蓋文件本來的內容");
fileWriter.append("我是下一行");
fileWriter.flush();
System.out.println("文件的默認編碼爲" + fileWriter.getEncoding());
fileWriter.close();
}
@Test
public void test08() throws IOException {
FileReader fileReader = new FileReader(new File("").getAbsolutePath()+"/io/test.txt");
BufferedReader bufferedReader = new BufferedReader(fileReader);
String str;
while ((str = bufferedReader.readLine()) != null) {
System.out.println(str);
}
fileReader.close();
bufferedReader.close();
}
@Test
public void test09() throws IOException {
FileReader fileReader = new FileReader(new File("").getAbsolutePath()+"/io/test.txt");
int c;
while ((c = fileReader.read()) != -1) {
System.out.print((char) c);
}
}
複製代碼
使用字節流和字符流的轉換類 InputStreamReader 和 OutputStreamWriter 能夠指定文件的編碼,使用 Buffer 相關的類來讀取文件的每一行。
@Test
public void test10() throws IOException {
FileOutputStream fileOutputStream = new FileOutputStream(new File("").getAbsolutePath()+"/io/test2.txt");
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream, "GBK"); // 使用 GBK 編碼文件
outputStreamWriter.write("Hello,world!\n歡迎來到 java 世界\n");
outputStreamWriter.append("另一行內容");
outputStreamWriter.flush();
System.out.println("文件的編碼爲" + outputStreamWriter.getEncoding());
outputStreamWriter.close();
fileOutputStream.close();
}
@Test
public void test11() throws IOException {
FileInputStream fileInputStream = new FileInputStream(new File("").getAbsolutePath()+"/io/test2.txt");
InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream, "GBK"); // 使用 GBK 解碼文件
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String str;
while ((str = bufferedReader.readLine()) != null) {
System.out.println(str);
}
bufferedReader.close();
inputStreamReader.close();
}
複製代碼
四、複製文件
這裏筆者作了一些測試,不使用緩衝對文件複製時間的影響,文件的複製實質仍是文件的讀寫。緩衝流是處理流,是對節點流的裝飾。
注:這裏的時間是在我這臺華碩筆記本上測試獲得的,只是爲了說明使用緩衝對文件的讀寫有好處。
@Test
public void test12() throws IOException {
// 輸入和輸出都使用緩衝流
FileInputStream in = new FileInputStream("E:\\視頻資料\\大數據原理與應用\\1.1大數據時代.mp4");
BufferedInputStream inBuffer = new BufferedInputStream(in);
FileOutputStream out = new FileOutputStream("1.1大數據時代.mp4");
BufferedOutputStream outBuffer = new BufferedOutputStream(out);
int len = 0;
byte[] bs = new byte[1024];
long begin = System.currentTimeMillis();
while ((len = inBuffer.read(bs)) != -1) {
outBuffer.write(bs, 0, len);
}
System.out.println("複製文件所需的時間:" + (System.currentTimeMillis() - begin)); // 平均時間約 200 多毫秒
inBuffer.close();
in.close();
outBuffer.close();
out.close();
}
@Test
public void test13() throws IOException {
// 只有輸入使用緩衝流
FileInputStream in = new FileInputStream("E:\\視頻資料\\大數據原理與應用\\1.1大數據時代.mp4");
BufferedInputStream inBuffer = new BufferedInputStream(in);
FileOutputStream out = new FileOutputStream("1.1大數據時代.mp4");
int len = 0;
byte[] bs = new byte[1024];
long begin = System.currentTimeMillis();
while ((len = inBuffer.read(bs)) != -1) {
out.write(bs, 0, len);
}
System.out.println("複製文件所需時間:" + (System.currentTimeMillis() - begin)); // 平均時間約 500 多毫秒
inBuffer.close();
in.close();
out.close();
}
@Test
public void test14() throws IOException {
// 輸入和輸出都不使用緩衝流
FileInputStream in = new FileInputStream("E:\\視頻資料\\大數據原理與應用\\1.1大數據時代.mp4");
FileOutputStream out = new FileOutputStream("1.1大數據時代.mp4");
int len = 0;
byte[] bs = new byte[1024];
long begin = System.currentTimeMillis();
while ((len = in.read(bs)) != -1) {
out.write(bs, 0, len);
}
System.out.println("複製文件所需時間:" + (System.currentTimeMillis() - begin)); // 平均時間 700 多毫秒
in.close();
out.close();
}
@Test
public void test15() throws IOException {
// 不使用緩衝
FileInputStream in = new FileInputStream("E:\\視頻資料\\大數據原理與應用\\1.1大數據時代.mp4");
FileOutputStream out = new FileOutputStream("1.1大數據時代.mp4");
int len = 0;
long begin = System.currentTimeMillis();
while ((len = in.read()) != -1) {
out.write(len);
}
System.out.println("複製文件所需時間:" + (System.currentTimeMillis() - begin)); // 平均時間約 160000 毫秒,約 2 分多鐘
in.close();
out.close();
}
複製代碼
關於序列化和反序列化的內容,這裏給出我以前寫的博客,傳送門。 總結:Java IO 類不少,可是把握住整個體系,掌握關鍵的方法,學習起來就會輕鬆不少,看完這篇文章,你是否以爲 Java IO 並無你想的那麼難呢?歡迎你在下方留言,和咱們討論。
歡迎關注下方的微信公衆號哦,另外還有各類學習資料免費分享!