標籤:
1.流的概念
一個對輸入輸出裝置的抽象概念,JAVA中檔案的操作都是以“流”的方式進行。可以把流理解成“管道”,資料通過管道得以從一端傳輸到另一端。
流具有方向性:一般而言這個方向是以程式作為參照,所以,輸入資料流是指輸向程式的流,輸出資料流是指從程式輸出的流;也可以理解成,程式從輸入資料流擷取資料,向輸出資料流寫資料。
流式輸入輸出的特點為:資料的擷取和發送沿資料序列的順序進行,即每一個資料都必須等待排在它前面的資料,等前面的資料讀入或送出後才能被讀寫。因此,流和隊列一樣,只能以“先進先出”的方式對其中的資料進行讀寫,而不能隨意選擇讀寫位置。
2.JAVA IO庫的設計原則
兩個原則
1.輸入輸出對稱性。如:InputStream和OutputStream各自佔據位元組流的輸入和輸出的兩個平行等級結構的根部,Reader和Writer則對應字元流輸入和輸出的兩個平行等級結構的根部。
2.byte和char的對稱性。如:InputSteam和Reader,OutputSteam和Writer。
兩種設計模式
1.裝飾者模式(Decorator)。裝飾者又稱為封裝器,其作用類似子類實現父類並自訂更多更豐富的方法,但是裝飾者模式比子類更靈活。
以InputStream為例,描述了裝飾者模式的實現案例。下面說一下個人對的簡單理解:InputStream作為抽象component根節點,FileInputStream、StringBufferInputStream、ByteArrayInputStream三者作為被裝飾對象的具體實現,而FilterInputStream及其子類則是裝飾者。由於所有元素都是component根節點的子孫類,所以他們在方法調用上是一致的,裝飾者通過在構建過程中調用被裝飾對象,從而在實現具體方法的時候,可以在被裝飾者具體方法前加入自己的邏輯,這樣就實現了對原有方法基礎上的自訂。(具體可檢索"裝飾者模式")
2.適配器模式(Adapter)。以FileInputStream為例。
public class FileInputStream extends InputStream{ /* File Descriptor - handle to the open file */ private FileDescriptor fd;...... public FileInputStream(FileDescriptor fdObj) { SecurityManager security = System.getSecurityManager(); if (fdObj == null) { throw new NullPointerException(); } if (security != null) { security.checkRead(fdObj); } fd = fdObj; }......
先理解下Java磁碟IO實現機制:檔案是作業系統與磁碟機互動的最小單元,程式也只能通過檔案來操作磁碟上的資料。在Java中,檔案操作通過File類完成,但是一個File類其實並不代表一個真實存在的檔案對象,當new一個File類的時候,返回的只是跟所傳遞的路徑描述符相關聯的虛擬對象,而真正要操作這個檔案對象,則是通過File類中引用的FileSystem屬性來進行底層系統互動來實現的(當然,這也與Java本身不能對作業系統底層進行訪問,只能通過JNI介面調用其他語言來實現對底層訪問有關)。
通過上面分析可知,同理,FileInputStream在調用系統檔案方面,其實現適配器模式可以這樣理解:通過引用FileDescriptor類,並將其適配成InputSteam類型的對象,實現檔案操作輸入資料流。
3.流的分類
按處理的資料單位分:位元組流、字元流
按資料方向(相對於程式本身)分:輸入資料流、輸出資料流
按功能分:節點流、處理流
其中第三點的解釋為:節點流是指從某個特定資料來源讀寫資料的流;而處理流是基於已存在的流(節點流或處理流)之上,為資料處理提供更強大功能的流,又稱為“過濾流”,它是對原有流的封裝。
四種基本抽象類別:輸入資料流——InputStream、Reader;輸出資料流——OutputStream、Writer
實作類別,深色為節點流,淺色為處理流:
4.read和write方法
- InputStream:Java中,InputStream類定義了三個read方法:
- public abstract int read() throws IOException;從輸入資料流中讀取資料的下一個位元組,返回讀到的位元組值。這個傳回值是0-255的int值,如果到達流的末尾,則返回-1。
- public int read(byte b[]) throws IOException;從輸入資料流中讀取b.length個位元組的資料,並儲存在緩衝區資料b中,返回的是實際讀取到的位元組數。
- public int read(byte b[], int off, int len) throws IOException;從輸入資料流中讀取len個位元組的資料,並從b的off位置開始寫入到b中。
- OutputStream:同樣是三個write方法:
- public abstract void write(int b) throws IOException;將指定位元組(b)寫入此輸出資料流,其實這裡寫入的b的8個低階位元,它的24個高階位元將被忽略。
- public void write(byte b[]) throws IOException;將b.length個位元組從指定的byte數組(b)寫入此輸出資料流。
- public void write(byte b[], int off, int len) throws IOException;將指定數組b中從off開始的len個位元組寫入此輸出資料流。
5.位元組流和字元流的實現
①基於位元組的輸入資料流:
值得注意的地方:1、節點流(Level2)直接與資料來源互動,所以其大多會指明資料來源的形式:如ByteArray、File、Piped;2、過濾流(Level3)是基於其他流的封裝(都整合自FilterInputStream),為其提供更豐富的功能,所以過濾流則多是指明功能:如Buffered、LineNumber。
Level2:
- ByteArrayInputStream:從記憶體中讀取一個位元組的資料,在建立其執行個體的時候,程式內部會建立一個byte數群組類型的緩衝區,將讀取的資料儲存在該緩衝區中,並用一個計數器記錄從資料來源中讀取的位元組數目。需要注意的是,在源碼中,ByteArrayInputStream的close()方法是空的,沒有任何實際作用,這是因為它是從記憶體讀取資料,不存在關閉串連的說法。
- FileInputStream:從檔案中讀取一個位元組的資料。FileInputStream將檔案操作類FileDescriptor適配成InputStream,從而實現了對檔案的讀取。
- PipedInputStream:管道輸入資料流可以與管道輸出資料流相串連,它從管道輸出資料流中讀取一個位元組的資料,主要用於多線程情況。輸入資料流和輸出資料流分成兩個線程,避免線程的死結。
- SequenceInputStream:把多個輸入資料流按順序合并成一個輸入資料流。
Level3:
- DataInputStream:資料流用於操作基本類型的資料,將擷取到的資料流轉換成未經處理資料類型。
- BufferedInputStream:緩衝流提供了讀取資料的內部緩衝區,這樣在讀資料的時候,可以一次讀取更多資料存放在緩衝區,然後再返回。通過內部緩衝區,BufferedInputStream提供了對資料讀取的更最佳化方式,減少了IO操作。此外,BufferedInputStream還提供了mark(標記方法)、reset(重設標記方法)、skip(跳過指定位元組)、available(預估可用位元組數方法)。
②基於位元組的輸出資料流:
Level2:
- PrintStream:位元組列印流,提供了一系列print和println方法,可以將到底層的輸出資料流輸出成被轉換成字串的資料。列印流不會拋出IO異常,出現異常會將其trouble屬性設為true,使用者可以調用checkError方法檢查是否有異常出現。並且,PrintStream自備自動flush功能,在它的輸出方法被調用後,會自動執行flush清空緩衝。
- DataOutputSteam:與DataInputStream類似,它可以將記憶體中的基本類型資料直接寫入底層輸出資料流。
- BufferedOutputStream:為輸出資料流提供緩衝功能,所有的輸出請求會先緩衝在BufferedOutputStream的緩衝區,待到合適時機再統一寫出。所以其write方法可能會先快取資料,如果需要立即寫入,可調用flush()方法。
Level3:
- ByteArrayOutputStream:該輸出資料流用於將資料輸出到記憶體中,在輸出前會將資料緩衝在自身的緩衝區裡 ,其close方法也是空的,因為它是往記憶體寫資料,不存在關閉連結的說法。
- FileOutputStream:用於往檔案中輸出資料位元組。有些系統下,同一檔案會只允許開啟一個輸出資料流。
③基於字元的輸入資料流:
需要注意的地方:實際上所有對檔案的IO操作都是通過位元組的形式實現的,所謂的字元流只不過邏輯上的一種說法。
上述類不做過多介紹,基本與位元組輸入資料流對應,其中StringReader是從一個字串中讀取內容,LineNumberReader是一個可以跟蹤讀入的“行資料”的字元輸入資料流,它內建一個指標用於跟蹤讀入資料的行數。
④基於字元的輸出資料流:
⑤位元組流和字元流之間的轉換:
InputStreamReader和OutputStreamWriter分別提供了位元組輸入資料流到字元輸入資料流和字元輸出資料流到位元組輸出資料流的轉換,轉換可指定編碼。
6.RandomAccessFile
RandomAccessFile是獨立實現的JavaIO操作類,它並不在上面所說的輸入輸出資料流結構中。作為擴充了IO架構的隨機檔案流,它可以在檔案的任意位置讀取或寫入資料。其在檔案系統中建立一個檔案指標,用於標記將要進行讀寫操作的下一位元組的位置,seek方法可以將指標移動到檔案內部的任意位置,從而實現檔案的隨機讀取。
7.執行個體代碼
代碼1:將網路上的某個頁面(資源)複製到本地(頁面靜態化)
public static void main(String[] args) throws Exception { InputStream in = new BufferedInputStream(new URL("http://www.jd.com").openStream()); File bdFile = new File("D:\\jd.html"); if (!bdFile.exists()) { bdFile.createNewFile(); } OutputStream ou = new BufferedOutputStream(new FileOutputStream(bdFile)); byte[] b = new byte[1024]; int len; while((len = in.read(b, 0, b.length)) != -1){ ou.write(b, 0, len); }}
代碼2:將某個網路資源通過多線程下載到本地(使用RandomAccessFile類)
public class Main { /** * @param args */ public static void main(String[] args) throws Exception{ String path = "xxx";//網路資源URL String filePath = "D:\\";//本地磁碟位置 new Main().threadDownload(path, 5, filePath); } public void threadDownload(String path, int threadSize, String filePath) throws Exception{ URL url = new URL(path); HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection(); httpURLConnection.setRequestMethod("GET"); if (httpURLConnection.getResponseCode() != 200) { throw new Exception("響應異常"); } int dataLength = httpURLConnection.getContentLength(); String fileName = Main.getFileName(httpURLConnection); httpURLConnection.disconnect(); threadSize = threadSize == 0 ? 5 : threadSize; int blockSize = dataLength/threadSize; int left = dataLength % threadSize; for (int i = 0; i < threadSize; i++) { if (i == threadSize) { blockSize += left; } new Thread(new DownloadThread(path, new RandomAccessFile(new File(filePath + fileName), "rw"), blockSize * i, blockSize, i + 1)).start(); } } private static String getFileName(HttpURLConnection httpURLConnection) throws Exception{ Map<String, List<String>> headerFields = httpURLConnection.getHeaderFields(); Set<Entry<String, List<String>>> set = headerFields.entrySet(); for (Entry<String, List<String>> entry : set) { if (entry.toString().contains("filename")) { List<String> list = entry.getValue(); for (String string : list) { if (string.contains("filename")) { String fileNameResult = new String(string.getBytes(), "UTF-8"); return fileNameResult.substring(fileNameResult.indexOf("\"") + 1, fileNameResult.lastIndexOf("\"")); } } } } return null; }}public class DownloadThread implements Runnable{ private String path; private RandomAccessFile randomAccessFile; private int startSize; private int blockSize; private int threadName; public DownloadThread(String path, RandomAccessFile randomAccessFile, int startSize, int blockSize, int threadName) { this.path = path; this.randomAccessFile = randomAccessFile; this.startSize = startSize; this.blockSize = blockSize; this.threadName = threadName; } @Override public void run() { HttpURLConnection httpURLConnection = null; InputStream inputStream = null; try { System.out.println("線程"+threadName+"開始下載"); URL url = new URL(path); httpURLConnection = (HttpURLConnection) url.openConnection(); httpURLConnection.setRequestMethod("GET"); httpURLConnection.setReadTimeout(10 * 1000); httpURLConnection.setRequestProperty("Range", "bytes=" + startSize + "-"); if (httpURLConnection.getResponseCode() != 206) {//206 Partial Content 客戶發送了一個帶有Range頭的GET請求,伺服器完成了它(HTTP 1.1新) System.out.println("線程" + threadName + "響應異常,代碼為" + httpURLConnection.getResponseCode()); throw new Exception("響應異常"); } inputStream = httpURLConnection.getInputStream(); byte[] b = new byte[1024]; int len = -1; int length = 0; randomAccessFile.seek(startSize); while(((len = inputStream.read(b)) != -1) && length < blockSize){ randomAccessFile.write(b, 0, len); length += len; } randomAccessFile.close(); inputStream.close(); httpURLConnection.disconnect(); System.out.println("線程"+threadName+"下載完成"); } catch (Exception e) { e.printStackTrace(); } finally { randomAccessFile = null; inputStream = null; httpURLConnection = null; } }}
1.JAVA基礎——IO