Java知識探究一:關於IO類庫

通過組織考察,令我突然發覺本身在最經常使用的Java中也有不少不明白的地方,實爲平身一大憾事,今天特地抽時間將這些點滴記錄下來,與你們一塊兒分享java

第一批想整理的知識點以下:算法

  1. Java的IO探究,IO的整個結構與發展,順帶附上公司某小工寫的斷點續傳代碼學習。
  2. Java的異常機制,關於編譯時異常和運行時異常的探究。
  3. JavaCommon包的理解,尤爲是collection包的一些小見解,其實容器嘛,什麼樣的Utils也逃不出一些基本的範疇,好比存、取、排序、安全性、校驗等等等。

閒話很少說,先開始今天的主題,研究一下IO的整個結構vim

從體系結構上劃分,IO系統總共分爲兩大模塊, IO和NIO(非阻塞),IO誕生於JDK1.4以前,JDK1.4時,產生了NIO,而且借用NIO重構了部分IO的代碼,好比FileInputStream中增長了對NIO進行支持的getChannel()方法,再好比Reader和FileReader基本用nio所有重寫了。windows

1、Think in IO數組

IO從實現上,大體分爲字節流和字符流兩種:緩存

  1. 字節流。對文件的讀寫操縱以字節爲單位,說的直白一點,就是操做byte,byte數組。對應無符號整數的話,就是read方法的正常返回值範圍在[0,255]之間,範圍有限的返回值有不少優勢,比較有表明性的一個就是能夠流來作一個簡單的zip實現,算法的話,採用huffman樹。固然,一個一個字節操做的話,效率不高,利用Buffer則效率提升很多。可是字節流有個問題,那就是在操做文本文件的時候,對於編碼會有不少多餘的代碼,例子以下
    FileInputStream is = new FileInputStream("F:\\books\\base\\vim經常使用指令.txt");
            byte[] buff = new byte[BUFFER_SIZE];
            int readSize = 0;
            while ((readSize = is.read(buff)) != -1)
            {
                System.out.println(readSize);
                if(readSize<1024){
                    byte[] tmp = new byte[readSize];
                    System.arraycopy(buff, 0, tmp, 0, readSize);
                    System.out.print(new String(tmp, "GBK"));
                }else{
                    System.out.print(new String(buff, "GBK"));
                }
            }
  2. 字符流。以字符做爲單元進行操做,Reader內部實現其實就是以char或者char數組做爲緩存容器的。操做文本文件時候方便許多。編碼採用系統默認的編碼格式。找了很久才找到代碼的說+_+,代碼隱藏的很深,從Reader找到ImputStreamReader,再到StreamDecoder再到nio包中的Charset,最終是優先獲取系統中的環境變量,System.getProperties()也能夠獲取,windows7中文版的話,獲取到的是「 file.encoding=GB18030」
    /**
         * Returns the default charset of this Java virtual machine.
         *
         * <p> The default charset is determined during virtual-machine startup and
         * typically depends upon the locale and charset of the underlying
         * operating system.
         *
         * @return  A charset object for the default charset
         *
         * @since 1.5
         */
        public static Charset defaultCharset() {
            if (defaultCharset == null) {
            synchronized (Charset.class) {
            java.security.PrivilegedAction pa =
                new GetPropertyAction("file.encoding");
            String csn = (String)AccessController.doPrivileged(pa);
            Charset cs = lookup(csn);
            if (cs != null)
                defaultCharset = cs;
                    else 
                defaultCharset = forName("UTF-8");
                }
        }
        return defaultCharset;
        }

下面詳細敘述一下字節流安全

    1、InputStream 和 OutputStream 是兩個 abstact 類,對於字節爲導向的 stream 都擴展這兩個雞肋(基類 ^_^ ) ; app

inputstream

  1. FileInputStream,打開本地文件的流,經常使用,有3個構造方法
    public FileInputStream(File file)
    public FileInputStream(String name)
    public FileInputStream(FileDescriptor fdObj) 值得強調,這個構造是不能直接用的,FileDescriptor 至關於打開文件的句柄,能夠用一個文件流建立另外一個,這樣建立的流至關因而一個。一個流關閉的話, 另外一個也不能讀取。
  2. PipedInputStream,必須與PipedOutputStream一塊兒使用,必須是兩個或者多個線程中使用,相似生產者消費者模型, PipedOutputStream將數據寫到共享的buffer數組中,通知PipedInputStream讀取。

    有兩點注意事項:async

    a) 使用PipedInputStream的read方法時候要注意,若是緩衝區沒有數據的話,會阻塞當前線程,在主線程中運行的話,會卡住不動。ide

    b)PipedOutputStream所在的線程若是中止,那麼PipedOutputStream所使用的資源也會回收,會形成pipe 的「broken」,PipedInputStream的read方法也會報錯。

    「A pipe is said to be broken if a thread that was providing data bytes to the connected piped output stream is no longer alive. 」

  3. FilterInputStream,自己是不能被實例化的,是BufferedInputStream等的父類,其實不建立這個類也能夠實現它的子類,這個類內部的方法幾乎所有都是複用父類的方法。其實它存在的意義更可能是表明一個抽象,意思是在InputStream的基礎之上對返回數據進行了從新包裝或者處理,處理緣由可能各不相同,因而又了各不相同的子類。

  4. LineNumberInputStream,這個類是字節流和字符流轉換中的失敗產物,已經肯定爲被廢棄,廢棄的理由是在字節流中強制的判斷讀取換行,不考慮編碼方面的問題。先無論功能能不能實現,首先從抽象層次上面就有欠缺。挪到字符流裏面就皆大歡喜。對應的有LineNumberReader這個類可使用。具體參見LineNumberReader詳解。

  5. DataInputStream,直接讀取目標文件的byte,拼接或轉化byte爲其餘基本類型,好比下面方法

    public final int readInt() throws IOException {
            int ch1 = in.read();
            int ch2 = in.read();
            int ch3 = in.read();
            int ch4 = in.read();
            if ((ch1 | ch2 | ch3 | ch4) < 0)
                throw new EOFException();
            return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0));
        }
    對於基本類型能夠這樣轉化,可是對於float和double,各自用了Float類和Double類中的native方法進行轉化,想來與操做系統底層有關係。
    public final double readDouble() throws IOException {
        return Double.longBitsToDouble(readLong());
        }
    惟一實現的比較複雜的是readUTF方法,須要讀取所有數據,必須是符合格式的,須要用DataOutputStream的writeUTF進行對應的寫。DataInputStream在實際運用中,仍是應該與DataOutputStream一塊兒使用,否則的話,意義不是十分大。
  6. BufferedInputStream,初始化一個8192大小的緩存,提升效率用,調用API上面沒有任何不一樣,只是減小了直接讀取系統數據的次數。內部持有一個普通的inputStream,只有緩衝區空了之後,才真正調用inputStream的read去寫滿緩衝區,因此直接用BufferedInputStream的read方法能夠提升效率。
    有點意思的是這個類裏面用了一個AtomicReferenceFieldUpdater對象來進行對volatile類型緩衝byte數組的更新和替換,這個類的compareAndSet方法帶有原子性質的比較和更新。
    /**
         * Atomic updater to provide compareAndSet for buf. This is
         * necessary because closes can be asynchronous. We use nullness
         * of buf[] as primary indicator that this stream is closed. (The
         * "in" field is also nulled out on close.)
         */
        private static final 
            AtomicReferenceFieldUpdater<BufferedInputStream, byte[]> bufUpdater = 
            AtomicReferenceFieldUpdater.newUpdater
            (BufferedInputStream.class,  byte[].class, "buf");// 建立原子更新器
    ...
    /**
         * Fills the buffer with more data, taking into account
         * shuffling and other tricks for dealing with marks.
         * Assumes that it is being called by a synchronized method.
         * This method also assumes that all data has already been read in,
         * hence pos > count.
         */
        private void fill() throws IOException {
            byte[] buffer = getBufIfOpen();
        if (markpos < 0)
            pos = 0;        /* no mark: throw away the buffer */
        else if (pos >= buffer.length)    /* no room left in buffer */
            if (markpos > 0) {    /* can throw away early part of the buffer */
            int sz = pos - markpos;
            System.arraycopy(buffer, markpos, buffer, 0, sz);
            pos = sz;
            markpos = 0;
            } else if (buffer.length >= marklimit) {
            markpos = -1;    /* buffer got too big, invalidate mark */
            pos = 0;    /* drop buffer contents */
            } else {        /* grow buffer */
            int nsz = pos * 2;
            if (nsz > marklimit)
                nsz = marklimit;
            byte nbuf[] = new byte[nsz];
            System.arraycopy(buffer, 0, nbuf, 0, pos);
                    if (!bufUpdater.compareAndSet(this, buffer, nbuf)) {//進行更新比較, 若是buf對象和buffer相同, 那麼進行更新,不一樣的話,不更新
                        // Can't replace buf if there was an async close.
                        // Note: This would need to be changed if fill()
                        // is ever made accessible to multiple threads.
                        // But for now, the only way CAS can fail is via close.
                        // assert buf == null;
                        throw new IOException("Stream closed");
                    }
                    buffer = nbuf;
            }
            count = pos;
        int n = getInIfOpen().read(buffer, pos, buffer.length - pos);
            if (n > 0)
                count = n + pos;
        }
  7. PushBackInputStream,特色是unread()方法,做用是在讀取流的過程當中自行添加入字節或者字節數組,進行從新讀取,小說中隨機插入的廣告url卻是能夠用這個實現,冷不丁的在讀取過程當中插入一個urlbyte數組,倒也方便。
  8. ByteArrayInputStream,特色是內存操做,讀取的數據所有都在緩存數組中,構造方法以下
    public ByteArrayInputStream(byte buf[])
    public ByteArrayInputStream(byte buf[], int offset, int length)
  9. SequenceInputStream,構造時候能見多個流進行拼接,依次進行read, 其中包含的流會自動進行關閉,在調用時候進行關閉
    public int read() throws IOException {
        if (in == null) {
            return -1;
        }
        int c = in.read();
        if (c == -1) {
            nextStream();// 讀完一個流之後, 自動變動下一個,可是這個方法不是線程安全的, 兩個一塊兒調,後果十分嚴重
            return read();
        }
        return c;
        }
    
    /**
         *  Continues reading in the next stream if an EOF is reached.
         */
        final void nextStream() throws IOException {
        if (in != null) {
            in.close();
        }
    
            if (e.hasMoreElements()) {
                in = (InputStream) e.nextElement();
                if (in == null)
                    throw new NullPointerException();
            }
            else in = null;
    
        }
  10. StringBufferInputStream,這個類已經被廢棄,緣由是錯誤的對字節流進行向字符流的轉化,忽略了編碼問題。值得一提的是, 這個類裏基本全部部分方法都是線程安全的。swing的某個類中還引用了這個方法。
  11. ObjectInputStream,這個類能夠說的比較多
      1. 實現了兩個接口,ObjectInut:定義了能夠read到的類型,ObjectStreamConstants:定義了讀取文件類型的常量,使用readObject時候,區分讀取到的對象是什麼類型,從序列化的對象進行讀取時候,須要經過標誌位來判斷讀取到的是什麼對象,這個常量裏面定義了這些值, 都是short的。
      2. 擁有一個內部類BlockDataInputStream,這個類的做用是讀取基本類型數據時候進行緩存,以提升效率,可是也產生了問題,http://www.tuicool.com/articles/v6RNNr 反序列化和序列化必定注意,建議使用read(byte[],start,end) 替代簡單的read(byte[]),使用後者的話, 可能出現讀取亂碼,內容錯誤等問題,尤爲是音視頻, 可能出現雜音,由於ObjectInputStream是根據單個字節來判斷數據類型的,因此必定要準確。

2、OutputStream, 基本每一個InputStream都有一個對應的OutputStream,來實現對應的功能,基本全都是抽象方法。

  1. FileOutputStream,FileDescriptor至關於句柄, 既然是句柄, 就會有多個流可能使用之, 因此FileDescriptor有incrementAndGetUseCount方法, 用來線程安全的進行引用計數器+1的操做。另外值得注意的是,FileOutputStream還有追加寫入的構造方法
    public FileOutputStream(File file, boolean append)
            throws FileNotFoundException
        {
            String name = (file != null ? file.getPath() : null);
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkWrite(name);
        }
            if (name == null) {
                throw new NullPointerException();
            }
        fd = new FileDescriptor();
            fd.incrementAndGetUseCount();
            this.append = append;
        if (append) {
            openAppend(name);
        } else {
            open(name);
        }
        }
  2. PipedOutputStream,須要與InputStream進行配合使用,不在贅述
相關文章
相關標籤/搜索