JAVA IO 位元組流與字元流

来源:http://www.cnblogs.com/TingyunAPM/archive/2016/04/01/5343497.html
-Advertisement-
Play Games

文章出自:聽雲博客 題主將以三個章節的篇幅來講解JAVA IO的內容 。 第一節JAVA IO包的框架體系和源碼分析,第二節,序列化反序列化和IO的設計模塊,第三節非同步IO。 本文是第一節。 IO框架 從上圖我們可以看出IO可以分為兩大塊 位元組流和字元流 位元組流是 InputStream 和 Out ...


      文章出自:聽雲博客

      題主將以三個章節的篇幅來講解JAVA IO的內容 。

      第一節JAVA IO包的框架體系和源碼分析,第二節,序列化反序列化和IO的設計模塊,第三節非同步IO。

      本文是第一節。

      IO框架

23231.jpg 

       從上圖我們可以看出IO可以分為兩大塊 位元組流和字元流

       位元組流是 InputStream 和 OutputStream 分別對應輸入與輸出

       字元流是Reader和Writer分別對應輸入與輸出

       ByteArrayInputStream 

       它位元組數組輸入流。繼承於InputStream。它包含一個數組實現的緩衝區ByteArrayInputStream也是數組實現的,提供read()來讀取數據,內部有一個計數器用來確定下一個要讀取的位元組。

       分析源碼

       56565.png 

       //pos是下一個會被讀取位元組的索引

       //count位元組流的長度

      //pos為0就是從0開始讀取

      //讀取下一個位元組, &0xff的意思是將高8位全部置0

      1115.png 

      // 將“位元組流的數據寫入到位元組數組b中” 

      // off是“位元組數組b的偏移地址”,表示從數組b的off開始寫入數據

      // len是“寫入的位元組長度” 

      1116.png 

        ByteArrayOutputStream

        它是位元組數組輸出流。繼承於OutputStream。ByteArrayOutputStream 實際也是數組實現的,它維護一個位元組數組緩衝。緩衝區會自動擴容。

         源碼分析

        1119.png

        1117.png 

       //我們看到不帶參的構造方法預設值是32 數組大小必須大於0否則會報 Negative initial size錯誤 ByteArrayOutputStream本質是一個byte數組

       //是將位元組數組buffer寫入到輸出流中,offset是從buffer中讀取數據的起始下標,len是寫入的長度。

       //ensureCapacity方法是判斷數組是否需要擴容

       //System.arraycopy是寫入的實現

       11120.png 

       //數組如果已經寫滿則grow

       1121.png 

       //int Capacity=oldCapacity<<1,簡單粗暴容量X2

       112212.png 

       Piped(管道) 

       多線程可以通過管道實現線程中的通訊,在使用管道時必須PipedInputStream,PipedOutputStream配套缺一不可

       PipedInputStream 

       12345.png 

       //初始化管道

       9898.png 

       //鏈接管道

       778878.png 

       8877.png

       //將“管道輸入流”和“管道輸出流”綁定。

      //調用的是PipedOutputStream的connect方法

       2221.png 

       2222.png 

       PipedOutputStream 

       //指定配對的PedpedInputStream

       2223.png 

       2224.png

       2225.png 

示例

package ioEx;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.io.IOException;
public class PipedStreamEx {
    public static void main(String[] args) {
        Writer t1 = new Writer();   
        Reader t2 = new Reader();  
        //獲取輸入輸出流
        PipedOutputStream out = t1.getOutputStream();   
        PipedInputStream in = t2.getInputStream();   
        try {   
            //將管道連接 也可以這樣寫 out.connect(in);   
            in.connect(out);   
            t1.start();
            t2.start();
        } catch (IOException e) {
            e.printStackTrace();
                    }
    }
}
class Reader extends Thread {
    private PipedInputStream in = new PipedInputStream();  
    // 獲得“管道輸入流”對象
    public PipedInputStream getInputStream(){   
        return in;   
    }   
    @Override
    public void run(){   
        readOne() ;
        //readWhile() ;
    }
    public void readOne(){
        // 雖然buf的大小是2048個位元組,但最多只會從輸入流中讀取1024個位元組。
        // 輸入流的buffer的預設大小是1024個位元組。
        byte[] buf = new byte[2048];
        try {
            System.out.println(new String(buf,0,in.read(buf)));
            in.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    // 從輸入流中讀取1024個位元組
    public void readWhile() {
        int total=0;
        while(true) {
            byte[] buf = new byte[1024];
            try {
                int len = in.read(buf);
                total += len;
                System.out.println(new String(buf,0,len));
                // 若讀取的位元組總數>1024,則退出迴圈。
                if (total > 1024)
                 break;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        try {
            in.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
class Writer extends Thread {    
    private PipedOutputStream out = new PipedOutputStream();
    public PipedOutputStream getOutputStream(){
        return out;
    }   
     @Override
    public void run(){   
        writeSmall1024();
        //writeBigger1024();
    }   
    // 向輸出流中寫入1024位元組以內的數據 
    private void writeSmall1024() {
        String strInfo = "I < 1024" ;
        try {
            out.write(strInfo.getBytes());
            out.close();   
        } catch (IOException e) {   
            e.printStackTrace();   
        }   
    }
    // 向“管道輸出流”中寫入一則較長的消息
    private void writeBigger1024() {
        StringBuilder sb = new StringBuilder();
        for (int i=0; i<103; i++)
            sb.append("0123456789");
        try {
            // sb的長度是1030 將1030個位元組寫入到輸出流中, 測試一次只能讀取1024個位元組
            out.write( sb.toString().getBytes());
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    }

       ObjectInputStream 和 ObjectOutputStream 

       Object流可以將對象進行序列化操作。ObjectOutputStream可以持久化存儲對象, ObjectInputStream,可以讀出這些這些對象。

       源碼很簡單直接上例子,關於序列化的內容題主將於下一節敘述

package ioEx;

import java.io.FileInputStream;   
import java.io.FileOutputStream;   
import java.io.ObjectInputStream;   
import java.io.ObjectOutputStream;   
import java.io.Serializable;   
import java.util.Map;
import java.util.Map.Entry;
import java.util.HashMap;
import java.util.Iterator;
  
public class ObjectStreamTest { 
    private static final String FILE_NAME= "test.txt";
  
    public static void main(String[] args) {   
        testWrite();
        testRead();
    }
    private static void testWrite() {   
        try {
            ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(FILE_NAME));
             out.writeByte((byte)1);
            out.writeChar('a');
            out.writeInt(20160329);
            out.writeFloat(3.14F);
            out.writeDouble(Math.PI);
            out.writeBoolean(true);
            // 寫入HashMap對象
            Map<String,String> map = new HashMap<String,String>();
            map.put("one", "one");
            map.put("two", "two");
            map.put("three", "three");
            out.writeObject(map);
            // 寫入自定義的Box對象,Box實現了Serializable介面
            Test test = new Test("a", 1, "a");
            out.writeObject(test);

            out.close();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    } 
    private static void testRead() {
        try {
                    ObjectInputStream in = new ObjectInputStream(new FileInputStream(FILE_NAME));
            System.out.println(in.readBoolean());
            System.out.println(in.readByte()&0xff);
            System.out.println(in.readChar());
            System.out.println(in.readInt());
            System.out.println(in.readFloat());
            System.out.println(in.readDouble());
            Map<String,String> map = (HashMap) in.readObject();
            Iterator<Entry<String, String>> iter = map.entrySet().iterator();
            while (iter.hasNext()) {
                Map.Entry<String, String> entry = (Map.Entry<String, String>)iter.next();
                System.out.println(entry.getKey()+":"+ entry.getValue());
            }
            Test test = (Test) in.readObject();
            System.out.println("test: " + test);

            in.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
class Test implements Serializable {
    private String a;   
    private int b; 
      private String c;   

    public Test(String a, int b, String c) {
        this.a = a;
        this.b = b;
        this.c = c;
    }

    @Override
    public String toString() {
        return "a, "+b+", c";
    }
}

       FileInputStream 和 FileOutputStream

       FileInputStream 是文件輸入流,它繼承於InputStream。我們使用FileInputStream從某個文件中獲得輸入位元組。

       FileOutputStream 是文件輸出流,它繼承於OutputStream。我們使用FileOutputStream 將數據寫入 File 或 FileDescriptor 的輸出流。

       File操作十分簡單,這裡就不再展示示例了。

       FilterInputStream 

       它的作用是用來封裝其它的輸入流,併為它們提供額外的功能。它的常用的子類有BufferedInputStream和DataInputStream和PrintStream。。

       BufferedInputStream的作用就是為“輸入流提供緩衝功能,以及mark()和reset()功能”。 

       DataInputStream 是用來裝飾其它輸入流,它允許應用程式以與機器無關方式從底層輸入流中讀取基本 Java 數據類型。應用程式可以使用          DataOutputStream(數據輸出流)寫入由DataInputStream(數據輸入流)讀取的數據

        (01) BufferedOutputStream的作用就是為“輸出流提供緩衝功能”。 

        (02) DataOutputStream 是用來裝飾其它輸出流,將DataOutputStream和DataInputStream輸入流配合使用,“允許應用程式以與機器無關方式從底層輸入流中讀寫基本 Java 數據類型”。 

        (03) PrintStream 是用來裝飾其它輸出流。它能為其他輸出流添加了功能,使它們能夠方便地列印各種數據值表示形式。

       主要瞭解一下Buffered流

       BufferedInputStream 

       它是緩衝輸入流。它繼承於FilterInputStream。它的作用是為另一個輸入流添加一些功能,例如,提供“緩衝功能”以及支持“mark()標記”和“reset()重置方法”。它本質上是通過一個內部緩衝區數組實現的

       源碼分析

       11222.png 

       方法不再一一解讀,重點講兩個方法 read1 和 fill 

       3331.png 

 3332.png

       根據fill()中的判斷條件可以分為五種情況

       情況1:讀取完buffer中的數據,並且buffer沒有被標記

       (01) if (markpos < 0) 它的作用是判斷“輸入流是否被標記”。若被標記,則markpos大於/等於0;否則markpos等於-1。

       (02) 在這種情況下:通過getInIfOpen()獲取輸入流,然後接著從輸入流中讀取buffer.length個位元組到buffer中。

       (03) count = n + pos; 這是根據從輸入流中讀取的實際數據的多少,來更新buffer中數據的實際大小。

       情況2:讀取完buffer中的數據,buffer的標記位置>0,並且buffer中沒有多餘的空間

       這種情況發生的情況是 — — 輸入流中有很長的數據,我們每次從中讀取一部分數據到buffer中進行操作。當我們讀取完buffer中的數據之後,並且此時輸入流存在標記時;那麼,就發生情況2。此時,我們要保留“被標記位置”到“buffer末尾”的數據,然後再從輸入流中讀取下一部分的數據到buffer中。

       其中,判斷是否讀完buffer中的數據,是通過 if (pos >= count) 來判斷的;

                 判斷輸入流有沒有被標記,是通過 if (markpos < 0) 來判斷的。

                 判斷buffer中沒有多餘的空間,是通過 if (pos >= buffer.length) 來判斷的。

       理解這個思想之後,我們再對這種情況下的fill()代碼進行分析,就特別容易理解了。

       (01) int sz = pos - markpos; 作用是“獲取‘被標記位置’到‘buffer末尾’”的數據長度。

       (02) System.arraycopy(buffer, markpos, buffer, 0, sz); 作用是“將buffer中從markpos開始的數據”拷貝到buffer中(從位置0開始填充,填充長度是sz)。接著,將sz賦值給pos,即pos就是“被標記位置”到“buffer末尾”的數據長度。

       (03) int n = getInIfOpen().read(buffer, pos, buffer.length - pos); 從輸入流中讀取出“buffer.length - pos”的數據,然後填充到buffer中。

       (04) 通過第(02)和(03)步組合起來的buffer,就是包含了“原始buffer被標記位置到buffer末尾”的數據,也包含了“從輸入流中新讀取的數據”。 

        情況3:讀取完buffer中的數據,buffer被標記位置=0,buffer中沒有多餘的空間,並且buffer.length>=marklimit

       執行流程如下,

       (01) read() 函數中調用 fill()

       (02) fill() 中的 else if (pos >= buffer.length) ...

      (03) fill() 中的 else if (buffer.length >= marklimit) ... 

      說明:這種情況的處理非常簡單。首先,就是“取消標記”,即 markpos = -1;然後,設置初始化位置為0,即pos=0;最後,再從輸入流中讀取下一部分數據到buffer中。

       情況4:讀取完buffer中的數據,buffer被標記位置=0,buffer中沒有多餘的空間,並且buffer.length<marklimit

       執行流程如下,

       (01) read() 函數中調用 fill()

       (02) fill() 中的 else if (pos >= buffer.length) ...

       (03) fill() 中的 else { int nsz = pos * 2; ... }

       這種情況的處理非常簡單。

       (01) 新建一個位元組數組nbuf。nbuf的大小是“pos*2”和“marklimit”中較小的那個數。

       (02) 接著,將buffer中的數據拷貝到新數組nbuf中。通過System.arraycopy(buffer, 0, nbuf, 0, pos)

       (03) 最後,從輸入流讀取部分新數據到buffer中。通過getInIfOpen().read(buffer, pos, buffer.length - pos);

       註意:在這裡,我們思考一個問題,“為什麼需要marklimit,它的存在到底有什麼意義?”我們結合“情況2”、“情況3”、“情況4”的情況來分析。

       假設,marklimit是無限大的,而且我們設置了markpos。當我們從輸入流中每讀完一部分數據並讀取下一部分數據時,都需要保存markpos所標記的數據;這就意味著,我們需要不斷執行情況4中的操作,要將buffer的容量擴大……隨著讀取次數的增多,buffer會越來越大;這會導致我們占據的記憶體越來越大。所以,我們需要給出一個marklimit;當buffer>=marklimit時,就不再保存markpos的值了。

       情況5:除了上面4種情況之外的情況

       執行流程如下,

       (01) read() 函數中調用 fill()

       (02) fill() 中的 count = pos... 

       這種情況的處理非常簡單。直接從輸入流讀取部分新數據到buffer中。

       BufferedOutputStream 

       BufferedOutputStream 是緩衝輸出流。它繼承於FilterOutputStream。

       BufferedOutputStream 的作用是為另一個輸出流提供“緩衝功能”。 

       代碼很簡單,就不一一分析了 這裡只分析一下write方法

       22332.png 

       字元流和字元流的區別和使用

       字元流的實現與位元組流基本相同,最大的區別是位元組流是通過byte[]實現的,字元流是通過char[]實現的,這裡就不在一一介紹了

       按照以下分類我們就可以很清楚的瞭解在何時使用位元組流或字元流

       一、按數據源分類:

       1 、是文件: FileInputStream, FileOutputStream, ( 位元組流 )FileReader, FileWriter( 字元 ) 

       2 、是 byte[] : ByteArrayInputStream, ByteArrayOutputStream( 位元組流 ) 

       3 、是 Char[]: CharArrayReader, CharArrayWriter( 字元流 ) 

       4 、是 String: StringBufferInputStream, StringBufferOuputStream ( 位元組流 )StringReader, StringWriter( 字元流 ) 

       5 、網路數據流: InputStream, OutputStream,( 位元組流 ) Reader, Writer( 字元流 ) 

       二、按是否格式化輸出分:要格式化輸出: PrintStream, PrintWriter

       三、按是否要緩衝分:要緩衝: BufferedInputStream, BufferedOutputStream,( 位元組流 ) BufferedReader, BufferedWriter( 字元流 ) 

       四、按數據格式分:

       1 、二進位格式(只要不能確定是純文本的) : InputStream, OutputStream 及其所有帶 Stream 結束的子類

       2 、純文本格式(含純英文與漢字或其他編碼方式); Reader, Writer 及其所有帶 Reader, Writer 的子類

       五、按輸入輸出分:

       1 、輸入: Reader, InputStream 類型的子類

       2 、輸出: Writer, OutputStream 類型的子類

       六、特殊需要:

       1 、從 Stream 到 Reader,Writer 的轉換類: InputStreamReader, OutputStreamWriter

       2 、對象輸入輸出: ObjectInputStream, ObjectOutputStream

       3 、進程間通信: PipeInputStream, PipeOutputStream, PipeReader, PipeWriter

       4 、合併輸入: SequenceInputStream

       5 、更特殊的需要: PushbackInputStream, PushbackReader, LineNumberInputStream, LineNumberReader

 

原文鏈接:http://blog.tingyun.com/web/article/detail/351


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Github地址: day0 初始化資料庫: 輸入密碼 Aa123456 (假設你設置的密碼為這個),完成mysql的初始化。 運行程式: 在瀏覽器(Chrome)查看運行效果: 嘗試新增幾個todo看一下效果: 標記為完成: 這樣一個簡單的待辦事項就運行起來了 目前的項目大致結構: 1. appl ...
  • ################################### 方法與觀念的改變 ################################### 錯誤導向: 我在網上會看到人們怎麼優化網站,就是用yslow這樣的工具來看,工具會告訴你要怎麼做,哪方面有問題,比如我寫在文檔裡面的像這樣 ...
  • 查了下tcl wiki,tcl比較成熟的mysql庫是mysqltcl,於是到其官方網站(http://www.xdobry.de/mysqltcl/)下載3.05版本回來,讀了下README沒有什麼特殊事項,慣例性進行linux軟體安裝三部曲: ./configuremakemake instal ...
  • 源代碼:下載 VC開發程式單調的界面相信大家都是深有感觸,提到界面美化編程,人們都會說做界面不要用VC寫,太難了。一句俗語:難者不會,會者不難。VC的美化界面編程並沒有人們想像的那麼難。這篇文章是我寫的一個用戶登錄界面,但界面被我美化了,我將一步一步的來講解它的美化界面的實現步驟。相信有了這篇文章, ...
  • 規範需要平時編碼過程中註意,是一個慢慢養成的好習慣 1.基本原則 強制性原則: 1.字元串的拼加操作,必須使用StringBuilder; 2.try…catch的用法 try{ }catch{Exception e e.printStackTrace(); }finally{ }//在最外層的Ac ...
  • 如果是對話框程式直接在對話框的 初始化時,修改樣式 ...
  • *從”http:localhost:8080”說起 “http://localhost:8080”是一個url.url的組成如下麵部分: *當你在瀏覽器地址欄中輸入”http:www.cdtu.com”按下回車之後, 為什麼出現成都工業學院首頁? 你收到的網頁是從伺服器來的, 呈現在現在瀏覽器中, ... ...
  • 在C中使用指針的原因 避免副本 在函數調用的時候,可以只傳遞數據的引用,而不用傳遞數據 數據共用 兩段代碼可以同時操作同一份數據,而不是兩份獨立的副本 使用指針讀寫數據 船長,向東航行! ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...