爲了方便理解與闡述,先引入兩張圖:java
a、Java IO中經常使用的類數組
在整個Java.io包中最重要的就是5個類和一個接口。5個類指的是File、OutputStream、InputStream、Writer、Reader;一個接口指的是Serializable.掌握了這些IO的核心操做那麼對於Java中的IO體系也就有了一個初步的認識了安全
Java I/O主要包括以下幾個層次,包含三個部分:服務器
1.流式部分――IO的主體部分;網絡
2.非流式部分――主要包含一些輔助流式部分的類,如:File類、RandomAccessFile類和FileDescriptor等類;多線程
3.其餘類--文件讀取部分的與安全相關的類,如:SerializablePermission類,以及與本地操做系統相關的文件系統的類,如:FileSystem類和Win32FileSystem類和WinNTFileSystem類。app
主要的類以下:dom
1. File(文件特徵與管理):用於文件或者目錄的描述信息,例如生成新目錄,修改文件名,刪除文件,判斷文件所在路徑等。socket
2. InputStream(二進制格式操做):抽象類,基於字節的輸入操做,是全部輸入流的父類。定義了全部輸入流都具備的共同特徵。函數
3. OutputStream(二進制格式操做):抽象類。基於字節的輸出操做。是全部輸出流的父類。定義了全部輸出流都具備的共同特徵。
4.Reader(文件格式操做):抽象類,基於字符的輸入操做。
5. Writer(文件格式操做):抽象類,基於字符的輸出操做。
6. RandomAccessFile(隨機文件操做):一個獨立的類,直接繼承至Object.它的功能豐富,能夠從文件的任意位置進行存取(輸入輸出)操做。
Java中IO流的體系結構如圖:
b、Java流類的類結構圖:
一、流的概念和做用
流:表明任何有能力產出數據的數據源對象或者是有能力接受數據的接收端對象<Thinking in Java>
流的本質:數據傳輸,根據數據傳輸特性將流抽象爲各類類,方便更直觀的進行數據操做。
流的做用:爲數據源和目的地創建一個輸送通道。
Java中將輸入輸出抽象稱爲流,就好像水管,將兩個容器鏈接起來。流是一組有順序的,有起點和終點的字節集合,是對數據傳輸的總稱或抽象。即數據在兩設備間的傳輸稱爲流.
二、Java IO所採用的模型
Java的IO模型設計很是優秀,它使用Decorator(裝飾者)模式,按功能劃分Stream,您能夠動態裝配這些Stream,以便得到您須要的功能。
例如,您須要一個具備緩衝的文件輸入流,則應當組合使用FileInputStream和BufferedInputStream。
三、IO流的分類
· 根據處理數據類型的不一樣分爲:字符流和字節流
· 根據數據流向不一樣分爲:輸入流和輸出流
· 按數據來源(去向)分類:
一、File(文件): FileInputStream, FileOutputStream, FileReader, FileWriter
二、byte[]:ByteArrayInputStream, ByteArrayOutputStream
三、Char[]: CharArrayReader,CharArrayWriter
四、String:StringBufferInputStream, StringReader, StringWriter
五、網絡數據流:InputStream,OutputStream, Reader, Writer
字符流和字節流
流序列中的數據既能夠是未經加工的原始二進制數據,也能夠是經必定編碼處理後符合某種格式規定的特定數據。所以Java中的流分爲兩種:
1) 字節流:數據流中最小的數據單元是字節
2) 字符流:數據流中最小的數據單元是字符, Java中的字符是Unicode編碼,一個字符佔用兩個字節。
字符流的由來: Java中字符是採用Unicode標準,一個字符是16位,即一個字符使用兩個字節來表示。爲此,JAVA中引入了處理字符的流。由於數據編碼的不一樣,而有了對字符進行高效操做的流對象。本質其實就是基於字節流讀取時,去查了指定的碼錶。
輸入流和輸出流
根據數據的輸入、輸出方向的不一樣對而將流分爲輸入流和輸出流。
1) 輸入流
程序從輸入流讀取數據源。數據源包括外界(鍵盤、文件、網絡…),便是將數據源讀入到程序的通訊通道
2) 輸出流
程序向輸出流寫入數據。將程序中的數據輸出到外界(顯示器、打印機、文件、網絡…)的通訊通道。
採用數據流的目的就是使得輸出輸入獨立於設備。
輸入流( Input Stream )不關心數據源來自何種設備(鍵盤,文件,網絡)。
輸出流( Output Stream )不關心數據的目的是何種設備(鍵盤,文件,網絡)。
3)特性
相對於程序來講,輸出流是往存儲介質或數據通道寫入數據,而輸入流是從存儲介質或數據通道中讀取數據,通常來講關於流的特性有下面幾點:
一、先進先出,最早寫入輸出流的數據最早被輸入流讀取到。
二、順序存取,能夠一個接一個地往流中寫入一串字節,讀出時也將按寫入順序讀取一串字節,不能隨機訪問中間的數據。(RandomAccessFile能夠從文件的任意位置進行存取(輸入輸出)操做)
三、只讀或只寫,每一個流只能是輸入流或輸出流的一種,不能同時具有兩個功能,輸入流只能進行讀操做,對輸出流只能進行寫操做。在一個數據傳輸通道中,若是既要寫入數據,又要讀取數據,則要分別提供兩個流。
四、Java IO流對象
1.輸入字節流InputStream
IO 中輸入字節流的繼承圖可見上圖,能夠看出:
1. InputStream是全部的輸入字節流的父類,它是一個抽象類。
2. ByteArrayInputStream、StringBufferInputStream(上圖的StreamBufferInputStream)、FileInputStream是三種基本的介質流,它們分別從Byte數組、StringBuffer、和本地文件中讀取數據。
3. PipedInputStream是從與其它線程共用的管道中讀取數據.
4. ObjectInputStream和全部FilterInputStream的子類都是裝飾流(裝飾器模式的主角)。
InputStream中的三個基本的讀方法
abstract int read() :讀取一個字節數據,並返回讀到的數據,若是返回-1,表示讀到了輸入流的末尾。
intread(byte[]?b) :將數據讀入一個字節數組,同時返回實際讀取的字節數。若是返回-1,表示讀到了輸入流的末尾。
intread(byte[]?b, int?off, int?len) :將數據讀入一個字節數組,同時返回實際讀取的字節數。若是返回-1,表示讀到了輸入流的末尾。off指定在數組b中存放數據的起始偏移位置;len指定讀取的最大字節數。
流結束的判斷:方法read()的返回值爲-1時;readLine()的返回值爲null時。
其它方法
long skip(long?n):在輸入流中跳過n個字節,並返回實際跳過的字節數。
int available() :返回在不發生阻塞的狀況下,可讀取的字節數。
void close() :關閉輸入流,釋放和這個流相關的系統資源。
voidmark(int?readlimit) :在輸入流的當前位置放置一個標記,若是讀取的字節數多於readlimit設置的值,則流忽略這個標記。
void reset() :返回到上一個標記。
booleanmarkSupported() :測試當前流是否支持mark和reset方法。若是支持,返回true,不然返回false。
2.輸出字節流OutputStream
IO 中輸出字節流的繼承圖可見上圖,能夠看出:
1. OutputStream是全部的輸出字節流的父類,它是一個抽象類。
2. ByteArrayOutputStream、FileOutputStream是兩種基本的介質流,它們分別向Byte數組、和本地文件中寫入數據。PipedOutputStream是向與其它線程共用的管道中寫入數據。
3. ObjectOutputStream和全部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() :關閉輸出流,釋放和這個流相關的系統資源。
3.字符輸入流Reader
在上面的繼承關係圖中能夠看出:
1. Reader是全部的輸入字符流的父類,它是一個抽象類。
2. CharReader、StringReader是兩種基本的介質流,它們分別將Char數組、String中讀取數據。PipedReader是從與其它線程共用的管道中讀取數據。
3. BufferedReader很明顯就是一個裝飾器,它和其子類負責裝飾其它Reader對象。
4. FilterReader是全部自定義具體裝飾流的父類,其子類PushbackReader對Reader對象進行裝飾,會增長一個行號。
5. InputStreamReader是一個鏈接字節流和字符流的橋樑,它將字節流轉變爲字符流。FileReader能夠說是一個達到此功能、經常使用的工具類,在其源代碼中明顯使用了將FileInputStream轉變爲Reader的方法。咱們能夠從這個類中獲得必定的技巧。Reader中各個類的用途和使用方法基本和InputStream中的類使用一致。後面會有Reader與InputStream的對應關係。
主要方法:
(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處開始存放,返回值爲實際讀取的字符數量,該方法必須由子類實現*/
4.字符輸出流Writer
在上面的關係圖中能夠看出:
1. Writer是全部的輸出字符流的父類,它是一個抽象類。
2. CharArrayWriter、StringWriter是兩種基本的介質流,它們分別向Char數組、String中寫入數據。PipedWriter是向與其它線程共用的管道中寫入數據,
3. BufferedWriter是一個裝飾器爲Writer提供緩衝功能。
4. PrintWriter和PrintStream極其相似,功能和使用也很是類似。
5. OutputStreamWriter是OutputStream到Writer轉換的橋樑,它的子類FileWriter其實就是一個實現此功能的具體類(具體能夠研究一SourceCode)。功能和使用和OutputStream極其相似.
主要方法:
(1) public void write(int c) throws IOException; //將整型值c的低16位寫入輸出流
(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個字符寫入輸出流
5.字節流的輸入與輸出的對應
圖中藍色的爲主要的對應部分,紅色的部分就是不對應部分。從上面的圖中能夠看出JavaIO中的字節流是極其對稱的。「存在及合理」咱們看看這些字節流中不太對稱的幾個類吧!
1. LineNumberInputStream主要完成從流中讀取數據時,會獲得相應的行號,至於何時分行、在哪裏分行是由改類主動肯定的,並非在原始中有這樣一個行號。在輸出部分沒有對應的部分,咱們徹底能夠本身創建一個LineNumberOutputStream,在最初寫入時會有一個基準的行號,之後每次遇到換行時會在下一行添加一個行號,看起來也是能夠的。好像更不入流了。
2. PushbackInputStream的功能是查看最後一個字節,不滿意就放入緩衝區。主要用在編譯器的語法、詞法分析部分。輸出部分的BufferedOutputStream幾乎實現相近的功能。
3. StringBufferInputStream已經被Deprecated,自己就不該該出如今InputStream部分,主要由於String應該屬於字符流的範圍。已經被廢棄了,固然輸出部分也沒有必要須要它了!還容許它存在只是爲了保持版本的向下兼容而已。
4. SequenceInputStream能夠認爲是一個工具類,將兩個或者多個輸入流當成一個輸入流依次讀取。徹底能夠從IO包中去除,還徹底不影響IO包的結構,卻讓其更「純潔」――純潔的Decorator模式。
5. PrintStream也能夠認爲是一個輔助工具。主要能夠向其餘輸出流,或者FileInputStream寫入數據,自己內部實現仍是帶緩衝的。本質上是對其它流的綜合運用的一個工具而已。同樣能夠踢出IO包!System.out和System.out就是PrintStream的實例!
字符流的輸入與輸出的對應
6.字符流與字節流轉換
轉換流的特色:
1. 其是字符流和字節流之間的橋樑
2. 可對讀取到的字節數據通過指定編碼轉換成字符
3. 可對讀取到的字符數據通過指定編碼轉換成字節
什麼時候使用轉換流?
1. 當字節和字符之間有轉換動做時;
2. 流操做的數據須要編碼或解碼時。
具體的對象體現:
轉換流:在IO中還存在一類是轉換流,將字節流轉換爲字符流,同時能夠將字符流轉化爲字節流。
1. InputStreamReader:字節到字符的橋樑
2. OutputStreamWriter:字符到字節的橋樑
OutputStreamWriter(OutStreamout):將字節流以字符流輸出。
InputStreamReader(InputStream in):將字節流以字符流輸入。
這兩個流對象是字符體系中的成員,它們有轉換做用,自己又是字符流,因此在構造的時候須要傳入字節流對象進來。
7.字節流和字符流的區別(重點)
字節流和字符流的區別:(詳細能夠參見http://blog.csdn.net/qq_25184739/article/details/51203733)
節流沒有緩衝區,是直接輸出的,而字符流是輸出到緩衝區的。所以在輸出時,字節流不調用colse()方法時,信息已經輸出了,而字符流只有在調用close()方法關閉緩衝區時,信息才輸出。要想字符流在未關閉時輸出信息,則須要手動調用flush()方法。
· 讀寫單位不一樣:字節流以字節(8bit)爲單位,字符流以字符爲單位,根據碼錶映射字符,一次可能讀多個字節。
· 處理對象不一樣:字節流能處理全部類型的數據(如圖片、avi等),而字符流只能處理字符類型的數據。
結論:只要是處理純文本數據,就優先考慮使用字符流。除此以外都使用字節流。
8.非流式文件類--File類
從定義看,File類是Object的直接子類,同時它繼承了Comparable接口能夠進行數組的排序。
File類的操做包括文件的建立、刪除、重命名、獲得路徑、建立時間等,如下是文件操做經常使用的函數。
File類是對文件系統中文件以及文件夾進行封裝的對象,能夠經過對象的思想來操做文件和文件夾。File類保存文件或目錄的各類元數據信息,包括文件名、文件長度、最後修改時間、是否可讀、獲取當前文件的路徑名,判斷指定文件是否存在、得到當前目錄中的文件列表,建立、刪除文件和目錄等方法。
File類共提供了三個不一樣的構造函數,以不一樣的參數形式靈活地接收文件和目錄名信息。
構造函數:
1)File (String pathname)
例:File f1=new File("FileTest1.txt"); //建立文件對象f1,f1所指的文件是在當前目錄下建立的FileTest1.txt
2)File (String parent , String child)
例:File f2=new File(「D:\dir1","FileTest2.txt") ;// 注意:D:\dir1目錄事先必須存在,不然異常
3)File (File parent , String child)
例:File f4=new File("\dir3");
File f5=new File(f4,"FileTest5.txt"); //在若是 \dir3目錄不存在使用f4.mkdir()先建立
一個對應於某磁盤文件或目錄的File對象一經建立, 就能夠經過調用它的方法來得到文件或目錄的屬性。
1)public boolean exists( ) 判斷文件或目錄是否存在
2)public boolean isFile( ) 判斷是文件仍是目錄
3)public boolean isDirectory( ) 判斷是文件仍是目錄
4)public String getName( ) 返回文件名或目錄名
5)public String getPath( ) 返回文件或目錄的路徑。
6)public long length( ) 獲取文件的長度
7)public String[ ] list ( ) 將目錄中全部文件名保存在字符串數組中返回。
File類中還定義了一些對文件或目錄進行管理、操做的方法,經常使用的方法有:
1) public boolean renameTo( File newFile ); 重命名文件
2) public void delete( ); 刪除文件
3) public boolean mkdir( ); 建立目錄
例子:
public class FileDemo1 {
public static void main(String[] args) {
File file = new File("D:" + File.separator + "test.txt");
if (file.exists()) {
file.delete();
} else {
try {
file.createNewFile();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
9.RandomAccessFile類
該對象並非流體系中的一員,其封裝了字節流,同時還封裝了一個緩衝區(字符數組),經過內部的指針來操做字符數組中的數據。該對象特色:
1. 該對象只能操做文件,因此構造函數接收兩種類型的參數:a.字符串文件路徑;b.File對象。
2. 該對象既能夠對文件進行讀操做,也能進行寫操做,在進行對象實例化時可指定操做模式(r,rw)
注意:該對象在實例化時,若是要操做的文件不存在,會自動建立;若是文件存在,寫數據未指定位置,會從頭開始寫,即覆蓋原有的內容。 能夠用於多線程下載或多個線程同時寫數據到文件。
十、System類對IO的支持
針對一些頻繁的設備交互,Java語言系統預約了3個能夠直接使用的流對象,分別是:
· System.in(標準輸入),一般表明鍵盤輸入。
· System.out(標準輸出):一般寫往顯示器。
· System.err(標準錯誤輸出):一般寫往顯示器。
標準I/O
Java程序可經過命令行參數與外界進行簡短的信息交換,同時,也規定了與標準輸入、輸出設備,如鍵盤、顯示器進行信息交換的方式。而經過文件能夠與外界進行任意數據形式的信息交換。
1. 命令行參數
public class TestArgs { public static void main(String[] args) { for (int i = 0; i < args.length; i++) { System.out.println("args[" + i + "] is <" + args[i] + ">"); } } }
運行命令:java Java C VB
運行結果:
2. 標準輸入,輸出數據流
java系統自帶的標準數據流:java.lang.System:
public final class System extends Object{ static PrintStream err;//標準錯誤流(輸出) static InputStream in;//標準輸入(鍵盤輸入流) static PrintStream out;//標準輸出流(顯示器輸出流) }
注意:
(1)System類不能建立對象,只能直接使用它的三個靜態成員。
(2)每當main方法被執行時,就自動生成上述三個對象。
1) 標準輸出流 System.out
System.out向標準輸出設備輸出數據,其數據類型爲PrintStream。方法:
Void print(參數)
Void println(參數)
2)標準輸入流 System.in
System.in讀取標準輸入設備數據(從標準輸入獲取數據,通常是鍵盤),其數 據類型爲InputStream。方法:
int read() //返回ASCII碼。若,返回值=-1,說明沒有讀取到任何字節讀取工做結束。
int read(byte[] b)//讀入多個字節到緩衝區b中返回值是讀入的字節數
import java.io.*; public class StandardInputOutput { public static void main(String args[]) { int b; try { System.out.println("please Input:"); while ((b = System.in.read()) != -1) { System.out.print((char) b); } } catch (IOException e) { System.out.println(e.toString()); } } }
等待鍵盤輸入,鍵盤輸入什麼,就打印出什麼:
3)標準錯誤流
System.err輸出標準錯誤,其數據類型爲PrintStream。可查閱API得到詳細說明。
標準輸出經過System.out調用println方法輸出參數並換行,而print方法輸出參數但不換行。println或print方法都通 太重載實現了輸出基本數據類型的多個方法,包括輸出參數類型爲boolean、char、int、long、float和double。同時,也重載實現 了輸出參數類型爲char[]、String和Object的方法。其中,print(Object)和println(Object)方法在運行時將調 用參數Object的toString方法。
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; public class StandardInputOutput { public static void main(String args[]) { String s; // 建立緩衝區閱讀器從鍵盤逐行讀入數據 InputStreamReader ir = new InputStreamReader(System.in); BufferedReader in = new BufferedReader(ir); System.out.println("Unix系統: ctrl-d 或 ctrl-c 退出" + " Windows系統: ctrl-z 退出"); try { // 讀一行數據,並標準輸出至顯示器 s = in.readLine(); // readLine()方法運行時若發生I/O錯誤,將拋出IOException異常 while (s != null) { System.out.println("Read: " + s); s = in.readLine(); } // 關閉緩衝閱讀器 in.close(); } catch (IOException e) { // Catch any IO exceptions. e.printStackTrace(); } } }
在Java語言中使用字節流和字符流的步驟基本相同,以輸入流爲例,首先建立一個與數據源相關的流對象,而後利用流對象的方法從流輸入數據,最後執行close()方法關閉流。
如下是基於NIO的socket通訊的簡單實現:
抽象一個socketWrapper類,避免server 和 client 裏建立過多的讀寫流 以下:
package me.ele.data.nio; import java.io.*; import java.net.Socket; /** * Description * <p> * </p> * DATE 2019/3/26. * * @author caichengzhang. */ public class SocketWrapper { private Socket socket; private InputStream inputStream; private BufferedReader bufferedReader; private BufferedWriter bufferedWriter; public SocketWrapper(Socket socket) throws IOException { this.socket = socket; this.inputStream = socket.getInputStream(); this.bufferedReader = new BufferedReader(new InputStreamReader(inputStream,"GBK")); this.bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(),"GBK")); } public String readLine() throws IOException{ return bufferedReader.readLine(); } public void writeLine(String line) throws IOException{ bufferedWriter.write(line+' '); bufferedWriter.flush(); } public void close(){ try { this.socket.close(); } catch (IOException e) { e.printStackTrace(); } } }
socketServer類以下:
package me.ele.data.nio; import java.io.IOException; import java.net.ServerSocket; /** * Description * <p> * </p> * DATE 2019/3/26. * * @author caichengzhang. */ public class NioSocketServer { public static void main(String[] args) throws IOException { int port = 8888; ServerSocket serverSocket = new ServerSocket(port); SocketWrapper socketWrapper = null; try{ socketWrapper = new SocketWrapper(serverSocket.accept()); String line = socketWrapper.readLine(); while(!"bye".equals(line)){ System.out.println("接收到客戶端消息:"+line); socketWrapper.writeLine("我已收到你的消息:"+line); line = socketWrapper.readLine(); } socketWrapper.writeLine("close"); }catch (Exception e){ e.printStackTrace(); }finally { if(socketWrapper!=null){ socketWrapper.close(); } } } }
socketClient類代碼以下:
package me.ele.data.nio; import java.io.IOException; import java.net.Socket; import java.util.Scanner; /** * Description * <p> * </p> * DATE 2019/3/26. * * @author caichengzhang. */ public class NioSocketClient { public static void main(String[] args) throws IOException { Scanner scanner = new Scanner(System.in); String host = "localhost"; int port = 8888; SocketWrapper socketWrapper = new SocketWrapper(new Socket(host,port)); try{ String msg = scanner.nextLine(); socketWrapper.writeLine(msg); String reciveMsg = socketWrapper.readLine(); while(!"close".equals(reciveMsg)){ System.out.println("服務器返回:"+ reciveMsg); msg = scanner.nextLine(); socketWrapper.writeLine(msg); reciveMsg = socketWrapper.readLine(); } }finally { if(socketWrapper != null){ socketWrapper.close(); } } } }