Java IO 基本概念java
Java IO:即 Java 輸入 / 輸出系統。web
區分 Java 的輸入和輸出:把本身當成程序, 當你從外邊讀數據到本身這裏就用輸入(InputStream/Reader), 向外邊寫數據就用輸出(OutputStream/Writer)。面試
Stream:Java 中將數據的輸入輸出抽象爲流,流是一組有順序的,單向的,有起點和終點的數據集合,就像水流。按照流中的最小數據單元又分爲字節流和字符流。算法
1,字節流:以 8 位(即 1 byte,8 bit)做爲一個數據單元,數據流中最小的數據單元是字節。數組
2,字符流:以 16 位(即 1 char,2 byte,16 bit)做爲一個數據單元,數據流中最小的數據單元是字符, Java 中的字符是 Unicode 編碼,一個字符佔用兩個字節。tomcat
IO 的分類網絡
流式部分和非流式部分dom
Java 的 IO 主要包含兩個部分:分佈式
1.流式部分:是 IO 的主體部分,也是本文介紹的重點, 流式部分根據流向分爲輸入流(InputStream/Reader)和輸出流(OutputStream/Writer), 根據數據不一樣的操做單元,分爲字節流(InputStream/OutputStream)和字符流(Reader/Writer),依據字節流和字符流,Java 定義了用來操做數據的抽象基類InputStream/OutputStream 和 Reader/Writer,再根據不一樣應用場景(或功能),在這兩種抽象基類上基於數據載體或功能派上出不少子類,用來知足文件,網絡,管道等不一樣場景的 IO 需求,從而造成了 Java 的基本 IO 體系。ide
下面是 Java IO 體系中經常使用的流類:
2.非流式部分:主要包含一些輔助流式部分的類,如: SerializablePermission 類、File 類、RandomAccessFile 類和 FileDescriptor 等;
節點流和處理流
Java io 分類方式有不少,根據是否直接處理數據,Java io又分爲節點流和處理流,節點流是真正直接處理數據的;處理流是裝飾加工節點流的。
節點流
處理流
處理流是對一個已存在的流的鏈接和封裝,經過所封裝的流的功能調用實現數據讀寫。如 BufferedReader。
處理流的構造方法老是要帶一個其餘的流對象作參數。
經常使用處理流(經過關閉處理流裏面的節點流來關閉處理流)
字節流
字節輸入流
下面是 IO 中輸入字節流的繼承關係。
InputStream
總結:
InputStream 中的三個基本的讀方法
字節輸出流
下面是 IO 中輸出字節流的繼承關係。
OutputStream
總結:
outputStream中的三個基本的寫方法
其它重要方法:
字節流的輸入與輸出的對應
java io 的輸入和輸出是高度對應的,下圖表示字節流的輸入與輸出的對應關係。
上圖中藍色的爲主要的對應部分,紅色的部分是不對應部分。紫色的虛線部分表明這些流通常要搭配使用。
咱們主要看看這些字節流中不對稱的幾個類:
搭配使用的三對類: ObjectInputStream / ObjectOutputStream 和 DataInputStream / DataOutputStream 主要是要求寫對象/數據和讀對象 / 數據的次序要保持一致,不然可能不能獲得正確的數據,甚至拋出異常(通常會如此);PipedInputStream / PipedOutputStream 在建立時通常就一塊兒建立,調用它們的讀寫方法時會檢查對方是否存在,或者關閉!
字符流
字符輸入流 Reader
下面是 IO 中輸入字符流的繼承關係。
Reader
總結:
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
總結(和字節輸出流對應):
writer 的主要寫方法:
字符流的輸入與輸出的對應
可參照(字節流的輸入與輸出的對應)記憶
Java IO 常見用法
一、讀取鍵盤輸入,打印到控制檯 在刷題網站刷算法題的時候,在程序開頭都須要和鍵盤進行交互,經常用到行奪取器 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();
}
}
運行結果:
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();
}
}
運行結果:
三、用字符流進行讀寫操做
(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();
}
}
運行結果:
(2)StringReader 和 StringWriter
public static void stringNode() throws IOException {
StringReader sr =null;
StringWriter sw =null;
try {
String str = "學習不刻苦 " + "不如賣紅薯; ";
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("黑夜給了我黑色的眼睛 ");
sw.write("我卻用它尋找光明 ");
// toString()返回sw節點內的數據
System.out.println(sw.toString());
} catch (Exception e) {
e.printStackTrace();
}finally{
sw.close();
sr.close();
}
}
運行結果:
四、字節流轉換爲字符流
在例 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();
}
}
運行結果:
五、隨機讀寫文件 使用 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 + " " + "pointerEnd:" + pointerEnd + " " + new String(contents));
randomAccessFile.seek(20);
// 獲取當前指針
long begin = randomAccessFile.getFilePointer();
randomAccessFile.write(contents);
long end = randomAccessFile.getFilePointer();
System.out.println("begin:" + begin + " " + "end:" + end + " ");
} catch (Exception e) {
e.printStackTrace();
}finally{
randomAccessFile.close();
}
}
運行結果:
操做前文件內容以下圖:
操做後控制檯打印信息:
操做後的文件內容:
六、讀寫管道
管道流要成對使用
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();
}
運行結果:
七、將多個輸入流當成一個輸入流依次讀取
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();
}
}
運行結果:
八、推回輸入流使用實例
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 IO 常見面試題
一、字節流和字符流的區別?
(1)讀寫單位不一樣:字節流以字節(8 bit)爲單位,字符流以字符爲單位,根據碼錶映射字符,一次可能讀多個字節。
(2)處理對象不一樣:字節流能處理全部類型的數據(如圖片、avi 等),而字符流只能處理字符類型的數據。
(3)字節流沒有緩衝區,是直接輸出的,而字符流是輸出到緩衝區的。所以在輸出時,字節流不調用 colse() 方法時,信息已經輸出了,而字符流只有在調用 close() 方法關閉緩衝區時,信息才輸出。要想字符流在未關閉時輸出信息,則須要手動調用 flush() 方法。
二、什麼是節點流,什麼是處理流,它們各有什麼用處,處理流的建立有什麼特徵?
見上文:節點流和處理流;
注意:處理流的構造器必需要 傳入節點流的子類
三、什麼叫對象序列化,什麼是反序列化,實現對象序列化須要作哪些工做?
例如,在 web 開發中,若是對象被保存在了 Session 中,tomcat 在重啓時要把 Session 對象序列化到硬盤,這個對象就必須實現 Serializable 接口。若是對象要通過分佈式系統進行網絡傳輸,被傳輸的對象就必須實現 Serializable 接口。
四、什麼是 Filter 流有哪些?
FilterStream 是一種 IO 流,主要做用是用來對存在的流增長一些額外的功能,像給目標文件增長源文件中不存在的行數,或者增長拷貝的性能等。在 java.io 包中主要由 4 個可用的 filter Stream。兩個字節 filter stream,兩個字符 filter stream.
分別是:FilterInputStream,FilterOutputStream,FilterReader and FilterWriter. 這些類是抽象類,不能被實例化的。
FilterInputStream 流的子類:
六、說說 RandomAccessFile?
它在 java.io 包中是一個特殊的類,既不是輸入流也不是輸出流,它二者均可以作到。他是 Object 的直接子類。一般來講,一個流只有一個功能,要麼讀,要麼寫。可是 RandomAccessFile 既能夠讀文件,也能夠寫文件。
並且 RandomAccessFile 支持對文件的隨機訪問,實例可見上文:例 5,隨機讀寫文件。
總結
不少初學者剛剛學習 java 的 IO 時會比較茫然,確實 IO 類不少,不容易記憶,不過咱們能夠嘗試對其進行總結記憶,把流式部分歸納爲:兩對應一橋樑一隨機。
兩個對應指:
總結記憶會使 Java io 清晰好記許多,你們也能夠嘗試按照本身的方式進行總結,印象會更加深