ByteArrayOutputStream的研究 原

這是一段對輸入流進行讀取和輸出的代碼

  Resource classPathResource = new ClassPathResource("1.txt");
  InputStream inputStream = pathResource.getInputStream();
  ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
  int i;
  while ((i=inputStream.read())!=-1){
    byteArrayOutputStream.write(i);
  }
  System.out.println(byteArrayOutputStream.toString());

輸出數據如下:

以前一直對這樣的while循環理解不足,今天突然再度寫到這樣的代碼,就想着深入研究一下。

inputStream.read()

這個方法的官方定義如下

    /**
     * Reads the next byte of data from the input stream. The value byte is
     * returned as an <code>int</code> in the range <code>0</code> to
     * <code>255</code>. If no byte is available because the end of the stream
     * has been reached, the value <code>-1</code> is returned. This method
     * blocks until input data is available, the end of the stream is detected,
     * or an exception is thrown.
     *
     * <p> A subclass must provide an implementation of this method.
     *
     * @return     the next byte of data, or <code>-1</code> if the end of the
     *             stream is reached.
     * @exception  IOException  if an I/O error occurs.
     */
    public abstract int read() throws IOException;

就是說,從輸入流中讀取數據的下一個byte字節,並返回int類型的字節值,這個字節的值範圍在0~255之間。如果輸入流已經被讀完,就返回-1。

這裏還是先講一下byte的知識。

byte

bit:位。位是電子計算機中最小的數據單位。每一位的狀態只能是0或1。比爾蓋茨曾經說過,計算機世界都是由0和1組成的。計算機也只能讀懂0和1這種二進制數據。

byte:字節。8個二進制位構成1個"字節(Byte)",它是存儲空間的基本計量單位。即:1byte=8bit。

編碼:編碼的知識量太大,這裏只是簡單提一下開發中常用到的utf-8編碼。在utf-8編碼中,一個英文字符等於一個字節,一箇中文(含繁體)等於三個字節。無論是中文還是英文,都叫字符。中文標點佔三個字節,英文標點佔一個字節。也就是說一個“千”字,它要被計算機識別,首先轉化成對應它的三個byte字節(對應規則在相關的映射表中),再轉化成3*8=24(bit)。‭最終結果是11100101 10001101 10000011‬ ,這個二進制數字才能被計算機讀懂。而英文字母“Q”的轉化簡單些,它只需要一個字節去表示 ‭10000001‬。

這裏分享個在線轉換utf-8編碼的地址http://www.mytju.com/classcode/tools/encode_utf8.asp

java中的流傳輸

我在開發過程中,遇到的流傳輸、數據傳輸,基本上都是用byte數組,所以這裏也主要講這個。從文章的第一段代碼中,不難看出,inputStream.read()將讀取到的文件1.txt的二進制數據賦予了變量i,ByteArrayOutputStream負責將i的值通過write()方法記錄起來,然後使用toString()方法將記錄的數據重新編碼輸出。這裏看了一下ByteArrayOutputStream的底層源碼,把重要的方法放上來,並註上中文註釋:

public class ByteArrayOutputStream extends OutputStream {

    /**
     *  byte數組,用於存儲讀取到的byte字節。
     */
    protected byte buf[];

    /**
     * 有效字節數
     */
    protected int count;

    /**
     * 初始化byte[] 數組,32長度
     */
    public ByteArrayOutputStream() {
        this(32);
    }

    public ByteArrayOutputStream(int size) {
        if (size < 0) {
            throw new IllegalArgumentException("Negative initial size: "
                                               + size);
        }
        buf = new byte[size];
    }

    /**
     * byte數組擴容,這是爲了防止傳入的數據量大於byte[]初始容量
     * */
    private void ensureCapacity(int minCapacity) {
        // overflow-conscious code
        if (minCapacity - buf.length > 0)
            grow(minCapacity);
    }

    /**
     * 定義byte數組最大容量
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;


    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = buf.length;
        int newCapacity = oldCapacity << 1;
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        buf = Arrays.copyOf(buf, newCapacity);
    }

    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

    /**
     * 把讀取的一個字節放入byte[]
     */
    public synchronized void write(int b) {
        ensureCapacity(count + 1);
        buf[count] = (byte) b;
        count += 1;
    }


    /**
     * 將byte[]數組中存儲的字節以平臺默認編碼(這裏是utf-8)重新編碼,並返回String類型的數據,也就是文本的內容。
     */
    public synchronized String toString() {
        return new String(buf, 0, count);
    }

源碼思路:定義一個byte[]變量,存儲所有讀到的字節,作爲一個緩衝域。這個緩衝域隨着對象不斷的讀入數據而不斷增長,當數據量大於緩衝域的空間,就會對緩衝域進行擴容處理。當數據全部讀入緩衝區中,裏面的內容大概是這樣的,buf[]={117,115,101,114...}。最後通過調用toString()方法將緩衝區的數據以特定編碼再次輸出。

講一下源碼中幾個關鍵的點:

  1.  int newCapacity = oldCapacity << 1; 這裏的意思是數組空間擴容1倍。oldCapacity 左移一位,即左邊高位去掉,右邊地位補0。例:十進制8的二進制表示成 0000 1000 左移1位,8<<1,變成0001 0000 。再轉成十進制,即變成16。這就完成了擴容。
  2. new String(buf, 0, count) 表示 將字符數組buf 從第一個字符(java以0開始,也可以說是第0個字符)開始,長度爲count的的所有字符構建成一個字符串,也就是String了。這個方法幫我們完成了由字節轉化到字符的操作。

 

這樣來看,這個ByteArrayOutputStream 其實很簡單。我這裏自己也寫了個根據輸入流輸出文本數據的工具類,結果發現代碼真的差不多,就稍微封裝了一下。

/**
 *  千里明月
 */
public class OutputStreamUtil {
        private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
        private static int count=0;
        private static byte[] bytes=new byte[10];

        public static String write(InputStream inputStream) throws IOException {
            int i =0 ;
            while ((i=inputStream.read())!=-1){
                write(i);
            }
            return encode();
        }

        public static void write(int i){
            if (bytes.length<=count){
                grow(count+1);
            }
            bytes[count]= (byte) i;
            System.out.print(bytes[count]);
            count++;
        }

         private static void grow(int capacity) {
            int oldcapacity= bytes.length;
            int newcapacity= oldcapacity<<1;
             if (newcapacity<capacity){
                 newcapacity=capacity;
             }
             if (newcapacity<MAX_ARRAY_SIZE){
                 bytes = Arrays.copyOf(bytes, newcapacity);
             }
         }

        public static String encode(){
            return new String(bytes,0,count);
        }

}

測試類:

public class TestResource {

    public static void main(String[] args) throws IOException {

        Resource classPathResource = new ClassPathResource("1.txt");
        InputStream inputStream = classPathResource.getInputStream();
        String write = OutputStreamUtil.write(inputStream);
        System.out.println(write);
        inputStream.close();
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章