I/O 問題是任何編程語言都沒法迴避的問題,能夠說 I/O 問題是整我的機交互的核心問題,由於 I/O 是機器獲取和交換信息的主要渠道。在當今這個數據大爆炸時代,I/O 問題尤爲突出,很容易成爲一個性能瓶頸。html
I/O ? 或者輸入/輸出 ? 指的是計算機與外部世界或者一個程序與計算機的其他部分的之間的接口。它對於任何計算機系統都很是關鍵,於是全部 I/O 的主體其實是內置在操做系統中的。單獨的程序通常是讓系統爲它們完成大部分的工做。java
I
:就是從硬盤將內容讀取到內存中O
:就是從內存將內容讀取到硬盤中其中有些狀況下
I/O
是沒有和硬盤進行交互的,例如管道流,涉及到兩個線程之間的通訊。管道自己是一個分配在內存中的循環緩衝區,只能被鏈接它的兩個線程使用。程序員
Java中的I/O操做類在包java.io
下面,大概將近有80多個類,可是這些類能夠分爲三組sql
InputStream
和OutputStream
Writer
和 Reader
File
而後在各個接口下還有其各自的包裝類,其運用到了裝飾模式,爲其增長一些功能,而Java的I/O複雜也在這,不一樣的裝飾模式建立類的代碼也不一樣。編程
InputStream
的做用是用來表示那些從不一樣數據源產生輸入的類,這些數據源包括數組
Internet
中的Socket
鏈接InputStream
的類圖,OutputStream
類圖和這個相似緩存
類 | 功能 | 構造器參數 | 如何使用 |
---|---|---|---|
ByteArrayInputStream |
容許將內存的緩衝區當作InputStream 使用 |
緩衝區,字節將其從中取出 | 做爲數據源:將其與FilterInputStream 對象相連以提供有用的接口 |
StringBufferInputStream |
將String轉換成InputStream |
字符串,底層實現實際使用StringBuffer |
做爲數據源:將其與FilterInputStream 對象相連提供有用接口 |
FileInputStream |
用於從文件中讀取信息 | 字符串,表示文件名,文件或者FileDescriptor 對象 |
做爲一種數據源,將其與FilterInputStream 對象相連提供有用接口 |
PipedInputStream |
產生用於寫入相關PipedOutputStream 的額數據,實現管道化的概念 |
PipedOutputStream |
做爲多線程的數據源:將其與FilterInputStream 對象相連提供有用接口 |
FilterInputStream |
抽象類,做爲裝飾器的接口,爲其餘的InputStream 提供有用的功能 |
Java的I/O類庫須要多種不一樣功能的組合,這正是裝飾模式的理由所在。而這也是java的I/O類庫中存在Filter(過濾器)類的緣由所在,Filter做爲全部裝飾類的基類。bash
類 | 功能 |
---|---|
BufferedInputStream |
使用它能夠防止每次讀取都進行與磁盤的交互,使用緩衝區進行一次性讀取固定值的之後再向磁盤中執行寫操做,減小了與磁盤的交互次數。提升速度 |
DataInputStream |
容許應用程序以與機器無關方式從底層輸入流中讀取基本 Java 數據類型 |
舉個簡單使用過濾器進行讀取一個文件的內容並輸出,例子以下:網絡
public static void main(String[] args) throws IOException {
InputStream inputStream = new BufferedInputStream(new FileInputStream("/Users/hupengfei/Downloads/a.sql"));
byte[] buffer = new byte[1024];
while ( inputStream.read(buffer)!=-1){
System.out.println(new String(buffer));
}
inputStream.close();
}
複製代碼
複製一個文件的例子:數據結構
public static void main(String[] args) throws IOException {
InputStream inputStream =new BufferedInputStream(new FileInputStream("/Users/hupengfei/Downloads/leijicheng.png"));
OutputStream outputStream =new BufferedOutputStream(new FileOutputStream("/Users/hupengfei/Downloads/fuzhi.png"));
byte [] buffer = new byte[1024];
while (inputStream.read(buffer)!=-1){
outputStream.write(buffer);
}
outputStream.flush();
inputStream.close();
outputStream.close();
}
複製代碼
若是要使用
BufferedOutputStream
進行在文件中寫入的話,那麼在緩衝區寫完以後要記得調用flush()
清空緩衝區。強行將緩衝區中的數據寫出。不然可能沒法寫出數據。
不論是磁盤仍是網絡傳輸,最小的存儲單元都是字節,而不是字符,因此 I/O 操做的都是字節而不是字符,可是爲啥有操做字符的 I/O 接口呢?這是由於咱們的程序中一般操做的數據都是以字符形式,爲了操做方便固然要提供一個直接寫字符的 I/O 接口。
仍是老規矩,咱們先來看一下關於Reader
的類圖,對應的字節流是InputStream
其中的InputStreamReader
是能夠將InputStream
轉換爲Reader
即將字節翻譯爲字符。其中爲何要設計Reader
和Writer
,主要是爲了國際化,以前的字節流僅僅支持8位的字節流,不能很好的處理16位的Unicode字符,因爲Unicode用於字符國際化,因此添加了Reader
和Writer
是爲了在全部的I/O操做中都支持Unicode。
在某些場合,面向字節流InputStream
和OutputStream
纔是正確的解決方案,特別是在java.util.zip
類庫就是面向字節流而不是面向字符的。所以,最明智的作法就是儘可能優先使用Reader
和Writer
,一旦程序沒法編譯,那麼咱們就會發現本身不得不使用面向字節類庫。
仍是寫一個相關的讀取文件的簡單例子
public static void main(String[] args) throws IOException {
BufferedReader bufferedReader = new BufferedReader(new FileReader("/Users/hupengfei/Downloads/a.sql"));
String date;
StringBuilder stringBuilder = new StringBuilder();
while ((date = bufferedReader.readLine()) != null){
stringBuilder.append(date +"\n");
}
bufferedReader.close();
System.out.println(stringBuilder.toString());
}
複製代碼
調用
readLine()
方法時要添加換行符,由於readLine()
自動將換行符給刪除了
在JDK1.4
中添加了NIO類,咱們也能夠稱之爲新I/O。NIO 的建立目的是爲了讓 Java 程序員能夠實現高速 I/O 而無需編寫自定義的本機代碼。NIO 將最耗時的 I/O 操做(即填充和提取緩衝區)轉移回操做系統,於是能夠極大地提升速度。
速度的提升來自於所使用的結構更接近於操做系統執行I/O的方式:通道(Channel)和緩衝器(Buffer)
通道和緩衝器是NIO中的核心對象,幾乎每個I/O操做中都會使用它們。通道是對原I/O包中的流的模擬。到任何地方(來自任何地方)的數據都得必須經過一個Channel對象。一個Buffer實質上是一個容器對象。發送給一個通道的全部對象都必須首先放到Buffer緩衝器中。
咱們能夠將它們想象成一個煤礦,通道就是一個包含煤礦(數據)的礦藏,而緩衝器就是派送到礦藏中的礦車,礦車載滿煤炭而歸,咱們再從礦車上獲取煤炭。也就是說,咱們並無直接和通道交互,咱們只是和緩衝器進行交互。
Buffer是一個對象,它包含着一些須要讀取的數據或者是要傳輸的數據。在NIO中加入了Buffer對象,體現了和以前的I/O的一個重要的區別。在面向流的I/O中咱們直接經過流對象直接和數據進行交互的,可是在NIO中咱們和數據的交互必須經過Buffer了。
緩衝器實質上是一個數組。一般它是一個字節的數組,可是也可使用其餘種類的數組。可是一個緩衝器不只僅是一個數組,緩衝器提供了對數據結構化的訪問,並且還能夠跟蹤系統的讀寫進程。
接下來咱們能夠看一下Buffer
相關的實現類
每個 Buffer
類都是 Buffer
接口的一個實例。 除了 ByteBuffer
,每個 Buffer
類都有徹底同樣的操做,只是它們所處理的數據類型不同。由於大多數標準 I/O 操做都使用 ByteBuffer
,因此它具備全部共享的緩衝區操做以及一些特有的操做。
ByteBuffer
是惟一一個直接與通道交互的緩衝器——也就說,能夠存儲未加工字節的緩衝器。當咱們查看ByteBuffer
源碼時會發現其經過告知分配多少存儲空間來建立一個ByteBuffer
對象,而且還有一個方法選擇集,用於以原始的字節形式或者基本數據類型輸出和讀取數據。可是,也沒辦法輸出或者讀取對象,便是是字符串的對象也不行。這種處理方式雖然很低級,可是正好,由於這是大多數操做系統中更有效的映射方式。
Channel
是一個對象,緩衝器能夠經過它進行讀取和寫入數據。和原來的I/O作個比較,通道就像個流。正如前面所提到的,Channel
是不和數據進行交互。可是它和流有一點不一樣,就是通道是雙向的,而流只能是單向的(只能是InputStream或者OutputStream),可是通道能夠用於讀、寫或者是同時用於讀寫。
在以前的I/O中有三個類被修改,能夠用來產生FileChannel
對象。這三個類是FileInputStream
、FileOutputStream
以及既用於讀也用於寫的RandomAccessFile
。
下面就舉個建立FileChannel
的例子。
FileChannel in = new FileInputStream("fileName").getChannel();
複製代碼
我會舉一個簡單的例子來演示如何使用NIO對文件進行復制的操做。仍是上面所說的,NIO中對數據操做的是緩衝器,和緩衝器交互的通道,因此如今須要咱們有兩個對象一個是Buffer
和Channel
。
public static void main(String[] args) throws IOException {
//獲取讀通道
FileChannel in = new FileInputStream("/Users/hupengfei/Downloads/hu.sql").getChannel();
//獲取寫通道
FileChannel out = new FileOutputStream("/Users/hupengfei/Downloads/a.sql").getChannel();
//爲緩衝器進行初始化大小
ByteBuffer byteBuffer =ByteBuffer.allocate(1024);
while (in.read(byteBuffer)!=-1){
//作好讓人讀的準備
byteBuffer.flip();
out.write(byteBuffer);
//清除數據
byteBuffer.clear();
}
}
複製代碼
一旦要用從緩衝器中讀取數據的話,那麼就要調用緩衝器的flip()
方法,讓它作好讓別人讀取字節的準備。那麼寫完數據之後就要調用緩存器的clear()
方法對全部的內部的指針從新安排,以便緩衝器在另外一個read()
操做期間可以作好接受數據的準備。而後數據就會從源文件中源源不斷的讀到了目標文件中。
clear()
方法在源碼中有介紹,此方法不會實際的清除在緩衝器的數據。
固然上面的方法也能夠簡便,直接將兩個通道進行相連只須要調用transferTo()
方法,這個也是複製文件的效果。
public static void main(String[] args) throws IOException {
FileChannel in = new FileInputStream("/Users/hupengfei/Downloads/hu.sql").getChannel();
FileChannel out = new FileOutputStream("/Users/hupengfei/Downloads/a.sql").getChannel();
in.transferTo(0,in.size(),out);
}
複製代碼