程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> JAVA IO 字節流與字符流,javaio

JAVA IO 字節流與字符流,javaio

編輯:JAVA綜合教程

JAVA IO 字節流與字符流,javaio


      文章出自:聽雲博客

      題主將以三個章節的篇幅來講解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

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved