Java 基礎之詳解 Java IO

Java IO 基本概念

Java IO:即 Java 輸入 / 輸出系統。

區分 Java 的輸入和輸出:把自己當成程序, 當你從外邊讀數據到自己這裡就用輸入(InputStream/Reader), 向外邊寫數據就用輸出(OutputStream/Writer)。

Stream:Java 中將數據的輸入輸出抽象為,流是一組有順序的,單向的,有起點和終點的數據集合,就像水流。按照流中的最小數據單元又分為字節流和字符流。

1,字節流:以 8 位(即 1 byte,8 bit)作為一個數據單元,數據流中最小的數據單元是字節。

2,字符流:以 16 位(即 1 char,2 byte,16 bit)作為一個數據單元,數據流中最小的數據單元是字符, Java 中的字符是 Unicode 編碼,一個字符佔用兩個字節。

IO 的分類


Java 基礎之詳解 Java IO


流式部分和非流式部分

Java 的 IO 主要包含兩個部分:

1.流式部分:是 IO 的主體部分,也是本文介紹的重點, 流式部分根據流向分為輸入流(InputStream/Reader)和輸出流(OutputStream/Writer), 根據數據不同的操作單元,分為字節流(InputStream/OutputStream)和字符流(Reader/Writer),依據字節流和字符流,Java 定義了用來操作數據的抽象基類InputStream/OutputStream 和 Reader/Writer,再根據不同應用場景(或功能),在這兩種抽象基類上基於數據載體或功能派上出很多子類,用來滿足文件,網絡,管道等不同場景的 IO 需求,從而形成了 Java 的基本 IO 體系。

下面是 Java IO 體系中常用的流類:

Java 基礎之詳解 Java IO


2.非流式部分:主要包含一些輔助流式部分的類,如: SerializablePermission 類、File 類、RandomAccessFile 類和 FileDescriptor 等;

節點流和處理流

Java io 分類方式有很多,根據是否直接處理數據,Java io又分為節點流和處理流,節點流是真正直接處理數據的;處理流是裝飾加工節點流的。

節點流

  • 文件流:FileInputStream,FileOutputStrean,FileReader,FileWriter,它們都會直接操作文件,直接與 OS 底層交互。因此他們被稱為節點流 ,注意:使用這幾個流的對象之後,需要關閉流對象,因為 java 垃圾回收器不會主動回收。不過在 Java7 之後,可以在 try() 括號中打開流,最後程序會自動關閉流對象,不再需要顯示地 close。
  • 數組流:ByteArrayInputStream,ByteArrayOutputStream,CharArrayReader,CharArrayWriter,對數組進行處理的節點流。
  • 字符串流:StringReader,StringWriter,其中 StringReader 能從 String 中讀取數據並保存到 char 數組。
  • 管道流:PipedInputStream,PipedOutputStream,PipedReader,PipedWrite,對管道進行處理的節點流。

處理流

處理流是對一個已存在的流的連接和封裝,通過所封裝的流的功能調用實現數據讀寫。如 BufferedReader。

處理流的構造方法總是要帶一個其他的流對象做參數。

常用處理流(通過關閉處理流裡面的節點流來關閉處理流)

  • 緩衝流 :BufferedImputStrean,BufferedOutputStream,BufferedReader ,BufferedWriter,需要父類作為參數構造,增加緩衝功能,避免頻繁讀寫硬盤,可以初始化緩衝數據的大小,由於帶了緩衝功能,所以就寫數據的時候需要使用 flush 方法,另外,BufferedReader 提供一個 readLine( ) 方法可以讀取一行,而 FileInputStream 和 FileReader 只能讀取一個字節或者一個字符,因此 BufferedReader 也被稱為行讀取器
  • 轉換流:InputStreamReader,OutputStreamWriter,要 inputStream 或 OutputStream 作為參數,實現從字節流到字符流的轉換,我們經常在讀取鍵盤輸入(System.in)或網絡通信的時候,需要使用這兩個類。
  • 數據流:DataInputStream,DataOutputStream,提供將基礎數據類型寫入到文件中,或者讀取出來。

字節流

字節輸入流

下面是 IO 中輸入字節流的繼承關係。

InputStream

  • ByteArrayInputStream
  • FileInputStream
  • FilterInputStream
  • PushbackInputStream
  • DataInputStream
  • BufferedInputStream
  • LineNumberInputStream
  • ObjectInputStream
  • PipedInputStream
  • SequenceInputStream
  • StringBufferInputStream

總結:

  1. InputStream 是所有的輸入字節流的父類,它是一個抽象類。
  2. PushbackInputStream、DataInputStream 和 BufferedInput Stream都是處理流,他們的的父類是 FilterInputStream。
  3. ByteArrayInputStream、StringBufferInputStream、FileInputStream 是三種基本的介質流,它們分別從 Byte 數組、StringBuffer、和本地文件中讀取數據。PipedInputStream 是從與其它線程共用的管道中讀取數據。

InputStream 中的三個基本的讀方法

  • abstract int read() :讀取一個字節數據,並返回讀到的數據,如果返回 -1,表示讀到了輸入流的末尾。
  • int read(byte[] b) :將數據讀入一個字節數組,同時返回實際讀取的字節數。如果返回-1,表示讀到了輸入流的末尾。
  • int read(byte[] b, int off, int len) :將數據讀入一個字節數組,同時返回實際讀取的字節數。如果返回 -1,表示讀到了輸入流的末尾。off 指定在數組 b 中存放數據的起始偏移位置;len 指定讀取的最大字節數。

字節輸出流

下面是 IO 中輸出字節流的繼承關係。

OutputStream

  • ByteArrayOutputStream
  • FileOutputStream
  • FilterOutputStream
  • BufferedOutputStream
  • DataOutputStream
  • PrintStream
  • ObjectOutputStream
  • PipedOutputStream

總結:

  1. OutputStream 是所有的輸出字節流的父類,它是一個抽象類。
  2. ByteArrayOutputStream、FileOutputStream 是兩種基本的介質流,它們分別向 Byte 數組、和本地文件中寫入數據。
  3. PipedOutputStream 是向與其它線程共用的管道中寫入數據。
  4. BufferedOutputStream、DataOutputStream 和 PrintStream 都是處理流,他們的的父類是 FilterOutputStream。

outputStream中的三個基本的寫方法

  • abstract void write(int b):往輸出流中寫入一個字節。
  • void write(byte[] b) :往輸出流中寫入數組b中的所有字節。
  • void write(byte[] b, int?off, int?len) :往輸出流中寫入數組 b 中從偏移量 off 開始的 len 個字節的數據。

其它重要方法:

  • void flush() :刷新輸出流,強制緩衝區中的輸出字節被寫出。
  • void close() :關閉輸出流,釋放和這個流相關的系統資源。

字節流的輸入與輸出的對應

java io 的輸入和輸出是高度對應的,下圖表示字節流的輸入與輸出的對應關係。

上圖中藍色的為主要的對應部分,紅色的部分是不對應部分。紫色的虛線部分代表這些流一般要搭配使用。

我們主要看看這些字節流中不對稱的幾個類:

  1. PushbackInputStream 為另一個輸入流添加性能,即 “ 推回(push back)” 或 “ 取消讀取(unread)” 一個字節的能力。
  2. SequenceInputStream 可以認為是一個工具類,將兩個或者多個輸入流當成一個輸入流依次讀取。完全可以從 IO 包中去除,還完全不影響 IO 包的結構。
  3. PrintStream 也可以認為是一個輔助工具。主要可以向其他輸出流,或者 FileInputStream 寫入數據,本身內部實現還是帶緩衝的。本質上是對其它流的綜合運用的一個工具而已。一樣可以從 IO 包中去除!System.io 和 System.out 就是 PrintStream 的實例!
  4. StringBufferInputStream 和 StringBufferInputStream 已經過時,還允許它存在只是為了保持版本的向下兼容而已。

搭配使用的三對類: ObjectInputStream / ObjectOutputStream 和 DataInputStream / DataOutputStream 主要是要求寫對象/數據和讀對象 / 數據的次序要保持一致,否則可能不能得到正確的數據,甚至拋出異常(一般會如此);PipedInputStream / PipedOutputStream 在創建時一般就一起創建,調用它們的讀寫方法時會檢查對方是否存在,或者關閉!

字符流

字符輸入流 Reader

下面是 IO 中輸入字符流的繼承關係。

Reader

  • BufferedReader
  • LineNumberReader
  • CharArrayReader
  • FilterReader
  • PushbackReader
  • InputStreamReader
  • FileReader
  • PipedReader
  • StringReader

總結:

  1. Reader 是所有的輸入字符流的父類,它是一個抽象類。
  2. CharReader、StringReader 是兩種基本的介質流,它們分別將 Char 數組、String 中讀取數據。PipedReader 是從與其它線程共用的管道中讀取數據。
  3. BufferedReader 很明顯就是一個裝飾器,它和其子類負責裝飾其它 Reader 對象。
  4. FilterReader 是所有自定義具體裝飾流的父類,其子類 PushbackReader 對 Reader 對象進行裝飾,會增加一個行號。
  5. InputStreamReader 是一個連接字節流和字符流的橋樑,它將字節流轉變為字符流。

Reader 基本的三個讀方法(和字節流對應):

(1) public int read() throws IOException; 讀取一個字符,返回值為讀取的字符。

(2) public int read(char cbuf[]) throws IOException; 讀取一系列字符到數組 cbuf[]中,返回值為實際讀取的字符的數量。

(3) public abstract int read(char cbuf[],int off,int len) throws IOException; 讀取 len 個字符,從數組 cbuf[] 的下標 off 處開始存放,返回值為實際讀取的字符數量,該方法必須由子類實現。

字符輸出流 Writer

下面是 IO 中輸出字符流的繼承關係。

Writer

  • BufferedWriter
  • CharArrayWriter
  • FilterWriter
  • OutputStreamWriter
  • FileWriter
  • PipedWriter
  • PrintWriter
  • StringWriter

總結(和字節輸出流對應):

  1. Writer 是所有的輸出字符流的父類,它是一個抽象類。
  2. CharArrayWriter、StringWriter 是兩種基本的介質流,它們分別向 Char 數組、String 中寫入數據。PipedWriter 是向與其它線程共用的管道中寫入數據。
  3. BufferedWriter 是一個裝飾器為 Writer 提供緩衝功能。
  4. PrintWriter 和 PrintStream 極其類似,功能和使用也非常相似。
  5. OutputStreamWriter 是 OutputStream 到 Writer 轉換的橋樑,它的子類 FileWriter 其實就是一個實現此功能的具體類。

writer 的主要寫方法:

  1. public void write(int c) throws IOException; //寫單個字符
  2. public void write(char cbuf[]) throws IOException; //將字符數組 cbuf[] 寫到輸出流 。
  3. public abstract void write(char cbuf[],int off,int len) throws IOException; //將字符數組cbuf[]中的從索引為off的位置處開始的len個字符寫入輸出流 。
  4. public void write(String str) throws IOException; //將字符串str中的字符寫入輸出流 。
  5. public void write(String str,int off,int len) throws IOException; //將字符串 str 中從索引 off 開始處的 len 個字符寫入輸出流 。

字符流的輸入與輸出的對應

可參照(字節流的輸入與輸出的對應)記憶

Java 基礎之詳解 Java IO


Java IO 常見用法

1、讀取鍵盤輸入,打印到控制檯 在刷題網站刷算法題的時候,在程序開頭都需要和鍵盤進行交互,常常用到行奪取器 BufferedReader 和轉換流 InputStreamReader。

public static void keyInAndPrintConsole() throws IOException {
PrintWriter out = null;
BufferedReader br = null;
try{
System.out.println("請輸入:");
out = new PrintWriter(System.out, true);
br = new BufferedReader(new InputStreamReader(System.in));
String line = null;
while ((line = br.readLine()) != null) {
if (line.equals("exit")) {
System.exit(1);
}
out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}finally{
out.close();
br.close();
}
}

運行結果:

Java 基礎之詳解 Java IO


2 、用字節流讀寫文件

因為是用字節流來讀媒介,所以對應的流是 InputStream 和 OutputStream,並且媒介對象是文件,所以用到子類是 FileInputStream 和 FileOutputStream,這裡還可以通過 BufferedInputStream 用緩衝流來讀取文件。

 public static void readAndWriteByteToFile() throws IOException {
InputStream is =null;
OutputStream os = null;
try {
// 在try()中打開文件會在結尾自動關閉
is = new FileInputStream("D:/FileInputStreamTest.txt");
os = new FileOutputStream("D:/FileOutputStreamTest.txt");
byte[] buf = new byte[4];
int hasRead = 0;
while ((hasRead = is.read(buf)) > 0) {
os.write(buf, 0, hasRead);
}
System.out.println("write success");
} catch (Exception e) {
e.printStackTrace();
}finally{
os.close();
is.close();
}
}

運行結果:

Java 基礎之詳解 Java IO


Java 基礎之詳解 Java IO


3、用字符流進行讀寫操作

(1)FileReader 和 FileWriter

 // 在try() 中打開的文件, JVM會自動關閉
public static void readAndWriteCharToFile() throws IOException{
Reader reader = null;
Writer writer =null;
try {
File readFile = new File("d:/FileInputStreamTest.txt");

reader = new FileReader(readFile);
File writeFile = new File("d:/FileOutputStreamTest.txt");
writer = new FileWriter(writeFile);
char[] byteArray = new char[(int) readFile.length()];
int size = reader.read(byteArray);
System.out.println("大小:" + size + "個字符;內容:" + new String(byteArray));
writer.write(byteArray);
} catch (Exception e) {
e.printStackTrace();
}finally{
reader.close();
writer.close();
}
}

運行結果:

Java 基礎之詳解 Java IO


Java 基礎之詳解 Java IO


(2)StringReader 和 StringWriter

public static void stringNode() throws IOException {
StringReader sr =null;
StringWriter sw =null;
try {
String str = "學習不刻苦\n" + "不如賣紅薯;\n";
char[] buf = new char[32];
int hasRead = 0;
// StringReader將以String字符串為節點讀取數據
sr = new StringReader(str);
while ((hasRead = sr.read(buf)) > 0) {
System.out.print(new String(buf, 0, hasRead));
}

// 由於String是一個不可變類,因此創建StringWriter時,實際上是以一個StringBuffer作為輸出節點
sw = new StringWriter();
sw.write("黑夜給了我黑色的眼睛\n");
sw.write("我卻用它尋找光明\n");
// toString()返回sw節點內的數據
System.out.println(sw.toString());
} catch (Exception e) {
e.printStackTrace();
}finally{
sw.close();
sr.close();
}
}

運行結果:

Java 基礎之詳解 Java IO


4、字節流轉換為字符流

在例 3 中用字符流讀文件時,打印到控制檯的中文會亂碼,使用轉換流可以解決這一問題。

public static void convertByteToChar() throws IOException {
InputStream is =null;
Reader reader = null;
try {
File file = new File("d:/FileInputStreamTest.txt");
is = new FileInputStream(file);
reader = new InputStreamReader(is,"gbk");
char[] byteArray = new char[(int) file.length()];
int size = reader.read(byteArray);
System.out.println("大小:" + size + ";內容:" + new String(byteArray));
} catch (Exception e) {
e.printStackTrace();
}finally{
reader.close();
is.close();
}
}

運行結果:

Java 基礎之詳解 Java IO


5、隨機讀寫文件 使用 RandomAccessFile 可以實現對文件的隨機讀取,主要是通過 seek() 方法實現指針偏移。

public static void randomAccessFileReadAndWrite() throws IOException {
RandomAccessFile randomAccessFile =null;
try {
// 創建一個RandomAccessFile對象
randomAccessFile = new RandomAccessFile("d:/File.txt", "rw");
// 通過seek方法來移動指針
randomAccessFile.seek(10);
// 獲取當前指針
long pointerBegin = randomAccessFile.getFilePointer();
// 從當前指針開始讀
byte[] contents = new byte[10];
randomAccessFile.read(contents);
long pointerEnd = randomAccessFile.getFilePointer();
System.out.println("pointerBegin:" + pointerBegin + "\n" + "pointerEnd:" + pointerEnd + "\n" + new String(contents));
randomAccessFile.seek(20);
// 獲取當前指針
long begin = randomAccessFile.getFilePointer();
randomAccessFile.write(contents);
long end = randomAccessFile.getFilePointer();
System.out.println("begin:" + begin + "\n" + "end:" + end + "\n");
} catch (Exception e) {
e.printStackTrace();
}finally{
randomAccessFile.close();
}
}

運行結果:

操作前文件內容如下圖:

Java 基礎之詳解 Java IO


操作後控制檯打印信息:

Java 基礎之詳解 Java IO


操作後的文件內容:

Java 基礎之詳解 Java IO


6、讀寫管道

管道流要成對使用

public static void piped() throws IOException {
final PipedOutputStream output = new PipedOutputStream();
final PipedInputStream input = new PipedInputStream(output);
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
try {
output.write("Hello world, pipe!".getBytes());
} catch (IOException e) {
}
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
try {
int data = input.read();
while (data != -1) {
System.out.print((char) data);
data = input.read();
}
} catch (IOException e) {
} finally {
try {
input.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
});
thread1.start();
thread2.start();
}

運行結果:

Java 基礎之詳解 Java IO


7、將多個輸入流當成一個輸入流依次讀取

public static void sequeue() throws IOException {
FileInputStream fistream1 =null;
FileInputStream fistream2 =null;
SequenceInputStream sistream =null;
FileOutputStream fostream =null;
try {
fistream1 = new FileInputStream("d:/A.txt");
fistream2 = new FileInputStream("d:/B.txt");
sistream = new SequenceInputStream(fistream1, fistream2);
fostream = new FileOutputStream("d:/C.txt");
int temp;
while( ( temp = sistream.read() ) != -1) {
System.out.print( (char) temp );
fostream.write(temp);
}
} catch (Exception e) {
e.printStackTrace();
}finally{
fostream.close();
sistream.close();
fistream1.close();
fistream2.close();
}
}

運行結果:

Java 基礎之詳解 Java IO


Java 基礎之詳解 Java IO


8、推回輸入流使用實例

public static void pushback() throws FileNotFoundException, IOException {
try (PushbackReader pr = new PushbackReader(new FileReader("D:/A.txt"), 64)) {
char[] buf = new char[32];
String lastContent = "";
int hasRead = 0;
while ((hasRead = pr.read(buf)) > 0) {
String content = new String(buf, 0, hasRead);
int targetIndex = 0;
if ((targetIndex = (lastContent + content).indexOf("A")) > 0) {
System.out.println(targetIndex);

pr.unread((lastContent + content).toCharArray());
if (targetIndex > 32) {
buf = new char[targetIndex];
}
pr.read(buf, 0, targetIndex);
System.out.println(new String(buf, 0, targetIndex));
System.out.println(new String(buf, targetIndex, buf.length-targetIndex));
System.exit(0);
} else {
System.out.println(lastContent);
lastContent = content;
}
}
} catch (IOException e) {
e.printStackTrace();
}
}

運行結果:

Java 基礎之詳解 Java IO


Java 基礎之詳解 Java IO


Java IO 常見面試題

1、字節流和字符流的區別?

(1)讀寫單位不同:字節流以字節(8 bit)為單位,字符流以字符為單位,根據碼錶映射字符,一次可能讀多個字節。

(2)處理對象不同:字節流能處理所有類型的數據(如圖片、avi 等),而字符流只能處理字符類型的數據。

(3)字節流沒有緩衝區,是直接輸出的,而字符流是輸出到緩衝區的。因此在輸出時,字節流不調用 colse() 方法時,信息已經輸出了,而字符流只有在調用 close() 方法關閉緩衝區時,信息才輸出。要想字符流在未關閉時輸出信息,則需要手動調用 flush() 方法。

2、什麼是節點流,什麼是處理流,它們各有什麼用處,處理流的創建有什麼特徵?

注意:處理流的構造器必須要 傳入節點流的子類

3、什麼叫對象序列化,什麼是反序列化,實現對象序列化需要做哪些工作?

  • 對象序列化:將對象以二進制的形式保存在硬盤上;
  • 反序列化:將二進制的文件轉化為對象讀取;
  • 實現 serializable 接口可以實現對象序列化,其中沒有需要實現的方法,implements Serializable 只是為了標註該對象是可被序列化的。

例如,在 web 開發中,如果對象被保存在了 Session 中,tomcat 在重啟時要把 Session 對象序列化到硬盤,這個對象就必須實現 Serializable 接口。如果對象要經過分佈式系統進行網絡傳輸,被傳輸的對象就必須實現 Serializable 接口。

4、什麼是 Filter 流有哪些?

FilterStream 是一種 IO 流,主要作用是用來對存在的流增加一些額外的功能,像給目標文件增加源文件中不存在的行數,或者增加拷貝的性能等。在 java.io 包中主要由 4 個可用的 filter Stream。兩個字節 filter stream,兩個字符 filter stream.

分別是:FilterInputStream,FilterOutputStream,FilterReader and FilterWriter. 這些類是抽象類,不能被實例化的。

FilterInputStream 流的子類:

  • DataInputStream 可以把包括基本類型在內的數據和字符串按順序從數據源讀入,它有一些特殊的方法如 readInt(),readDouble() 和 readLine() 等可以讀取一個 int,double 和一個 string。
  • BufferedInputStream 增加性能。
  • PushbackInputStream 推送要求的字節到系統中。
  • 注:其它子類見 Java io 分類圖。

6、說說 RandomAccessFile?

它在 java.io 包中是一個特殊的類,既不是輸入流也不是輸出流,它兩者都可以做到。他是 Object 的直接子類。通常來說,一個流只有一個功能,要麼讀,要麼寫。但是 RandomAccessFile 既可以讀文件,也可以寫文件。

而且 RandomAccessFile 支持對文件的隨機訪問,實例可見上文:例 5,隨機讀寫文件。

總結

很多初學者剛剛學習 java 的 IO 時會比較茫然,確實 IO 類很多,不容易記憶,不過我們可以嘗試對其進行總結記憶,把流式部分概括為:兩對應一橋樑一隨機

兩個對應指:

  1. 字節流(Byte Stream)和字符流(Char Stream)的對應;
  2. 輸入和輸出的對應。
  3. 一個橋樑指:從字節流到字符流的橋樑。對應於輸入和輸出為InputStreamReader和OutputStreamWriter;
  4. 一個隨機是:RandomAccessFile。可以隨機讀取文件。

總結記憶會使 Java io 清晰好記許多,大家也可以嘗試按照自己的方式進行總結,印象會更加深

Java 基礎之詳解 Java IO


分享到:


相關文章: