Java基礎(chǔ)(四)-IO / NIO

在Java程序中,以“流”(stream)的方式對數(shù)據(jù)進行I/O操作。

一、流分類

大方向分:

類型
按數(shù)據(jù)流方向不同分 輸入流 輸出流
按功能不同分 節(jié)點流 處理流
按處理單位不同分 字節(jié)流 字符流

具體分:


注:直接套在數(shù)據(jù)源上獲取數(shù)據(jù)的管道是節(jié)點流,節(jié)點流上套的其他的增強管道是處理流。

下面看一張完整的劃分:

二、字節(jié)流

API:
InputStream 類

方法 解釋
public abstract int read() 一個字節(jié)一個字節(jié)地讀數(shù)據(jù)。費磁盤
public int read(byte b[]) 以byte 數(shù)組位位單位來讀取字節(jié)數(shù)據(jù)。相當于拿個桶舀水。
public int read(byte b[], int off, int len) 從第 off 位置讀取 len 長度字節(jié)的數(shù)據(jù)放到 byte 數(shù)組中,以 -1 來判斷是否讀取結(jié)束。
public long skip(long n) 跳過指定個數(shù)的字節(jié)。
public int available() 返回可讀的字節(jié)數(shù)量。
public void close() 讀取完關(guān)閉流,釋放資源。
public synchronized void mark(int readlimit) 標記讀取位置,下次還可以從這里開始讀?。ㄊ褂们耙串斍傲魇欠裰С?,可以使用 markSupport() 方法判斷)。
public synchronized void reset() 重置讀取位置為上次 mark 標記的位置。
public boolean markSupported() 判斷當前流是否支持標記流,和上面兩個方法配套使用。

OutputStream 類

方法 解釋
public abstract void write(int b) 寫入一個字節(jié),參數(shù)是一個 int 類型,對應(yīng)上面的讀方法,int 類型的 32 位,只有低 8 位才寫入,高 24 位將舍棄。
public void write(byte b[]) 以byte 數(shù)組為單位來寫入字節(jié)數(shù)據(jù)。
public void write(byte b[], int off, int len) 將 byte 數(shù)組從 off 位置開始,len 長度的字節(jié)寫入
public void flush() 強制刷新,將緩沖中的數(shù)據(jù)寫入
public void close() 關(guān)閉輸出流,流被關(guān)閉后就不能再輸出數(shù)據(jù)了

問題1:為什么要使用flash?

flash把緩沖區(qū)的數(shù)據(jù)強行輸出,主要用在IO中,即清空緩沖區(qū)數(shù)據(jù),一般在讀寫流(stream)的時候,數(shù)據(jù)是先被讀到了內(nèi)存中,再把數(shù)據(jù)寫到文件中,當你數(shù)據(jù)讀完的時候不代表你的數(shù)據(jù)已經(jīng)寫完了,因為還有一部分有可能會留在內(nèi)存這個緩沖區(qū)中。這時候如果你調(diào)用了close()方法關(guān)閉了讀寫流,那么這部分數(shù)據(jù)就會丟失,所以應(yīng)該在關(guān)閉讀寫流之前先flush()。

問題2:為什么InputStream.read()讀取一個byte卻返回一個int呢?

從先從源碼來看,read方法在java層經(jīng)過IoBridge和LibCore,最終會JNI到native來實現(xiàn),而native是c++實現(xiàn)的,返回的是unsigned byte,取值范圍為[0~255],在java中沒有對應(yīng)的類型,在java中byte范圍是[-128-127],無符號范圍是[0-127],所以[128-255]只能由int來接收。
另外,在讀取byte數(shù)據(jù)時,我們知道127+1 = -128(0111 1111 -> 1000 0000,首位是1表示負數(shù),負數(shù)轉(zhuǎn)int:先對各位取反,將其轉(zhuǎn)換為十進制數(shù),加上負號,再減去1即-128),那么會有一種情況是連續(xù)8個1:1111 1111,按前面的計算公式,最終會轉(zhuǎn)為-1,而-1表示讀取結(jié)束,顯然此時并沒有讀完,為了避免這種情況,需要將byte提升為int,即向前補0來避免此問題發(fā)生。

因此:
FileInputStream的read方法實際是在做類型提升(將byte提升為int),避免java byte無法對應(yīng)[128-255]范圍問題,避免-1問題。
FileOutputStream的write的方法實際在做類型強轉(zhuǎn)(將int強轉(zhuǎn)為byte),只有低 8 位才寫入,高 24 位將舍棄。

三、字符流

與字節(jié)流相比,字符流是按一個個字符來讀寫。適合文本文件的讀寫。
Reader 類

方法 解釋
public int read(java.nio.CharBuffer target) 讀取字節(jié)到字符緩存中
public int read() 讀取單個字符
public int read(char cbuf[]) 讀取字符到指定的 char 數(shù)組中
abstract public int read(char cbuf[], int off, int len) 從 off 位置讀取 len 長度的字符到 char 數(shù)組中
public long skip(long n) 跳過指定長度的字符數(shù)量
public boolean ready() 和上面的 available() 方法類似
public boolean markSupported() 判斷當前流是否支持標記流
public void mark(int readAheadLimit) 標記讀取位置,下次還可以從這里開始讀取,使用前要看當前流是否支持,可以使用 markSupport() 方法判斷
public void reset() 重置讀取位置為上次 mark 標記的位置
abstract public void close() 關(guān)閉流釋放相關(guān)資源

Writer 類

方法 解釋
public void write(int c) 寫入一個字符
public void write(char cbuf[]) 以字符數(shù)組為單位寫入
abstract public void write(char cbuf[], int off, int len) 從字符數(shù)組的 off 位置寫入 len 數(shù)量的字符
public void write(String str) 寫入一個字符串
public void write(String str, int off, int len) 從字符串的 off 位置寫入 len 數(shù)量的字符
public Writer append(CharSequence csq) 追加寫入一個字符序列
public Writer append(CharSequence csq, int start, int end) 追加寫入一個字符序列的一部分,從 start 位置開始,end 位置結(jié)束
public Writer append(char c) 追加寫入一個 16 位的字符
abstract public void flush() 強制刷新,將緩沖中的數(shù)據(jù)寫入
abstract public void close() 關(guān)閉輸出流,流被關(guān)閉后就不能再輸出數(shù)據(jù)了

問題:一個字符是幾個字節(jié)?

這個不同的編碼格式有差別,索性就打印一下看:

漢字:
try {
   String str = "你";
   System.out.println("UTF-16:"+str.getBytes("UTF-16").length);
   System.out.println("UTF-8:"+str.getBytes("UTF-8").length);
   System.out.println("GBK:"+str.getBytes("GBK").length);
} catch (UnsupportedEncodingException e) {
    e.printStackTrace();
}
結(jié)果:
UTF-16:4
UTF-8:3
GBK:2

英文字母:
try {
    String str = "A";
   System.out.println("UTF-16:"+str.getBytes("UTF-16").length);
   System.out.println("UTF-8:"+str.getBytes("UTF-8").length);
   System.out.println("GBK:"+str.getBytes("GBK").length);
} catch (UnsupportedEncodingException e) {
    e.printStackTrace();
}
結(jié)果:
UTF-16:4
UTF-8:1
GBK:1
四、處理流

簡單總結(jié)幾個常用的處理流:

字節(jié):

  • 字節(jié)流轉(zhuǎn)換為字符流:
    InputStreamReader & OutputStreamWriter

  • 字節(jié)管道加buff:
    BufferedInputStream & BufferedOutputStream

字符:

  • 字符管道加buff:
    BufferedRead & BufferedWriter

  • PrintWriter
    字符包裝寫PrintWriter 與 BufferedWriter區(qū)別:

    • PrintWriter 可以接受更多類型的參數(shù),在循環(huán)讀的過程中,方便拓展寫的內(nèi)容,而BufferedWriter的write方法只能接受字符、字符數(shù)組和字符串;
    • PrintWriter的println方法自動添加換行,BufferedWriter需要顯示調(diào)用newLine方法;
    • PrintWriter的方法不會拋異常,若關(guān)心異常,需要調(diào)用checkError方法看是否有異常發(fā)生;
    • PrintWriter構(gòu)造方法可指定參數(shù),實現(xiàn)自動刷新緩存(autoflush);
    • BufferedWriter可以任意設(shè)定緩沖大小

個人感覺,PrintWriter比BufferedWriter更靈活,推薦使用PrintWriter。

五、代碼案例
   /**
    * 字節(jié)流文件復(fù)制
    * @param oriFile
    * @param destFile
    */
   public static void byteCopy(File oriFile, File destFile) {
        if(oriFile == null || !oriFile.exists()){
            return;
       }
        FileInputStream fis = null;
       FileOutputStream fos = null;
       try {
            fis = new FileInputStream(oriFile);
           fos = new FileOutputStream(destFile, true);//第二個參數(shù):是否追加寫。
           byte[] bytes = new byte[1024];
           int len;
           StringBuilder sb = new StringBuilder();
           while ((len = fis.read(bytes)) != -1) {
                fos.write(bytes, 0, len);
               fos.flush();
           }
        } catch (Exception e) {
            e.printStackTrace();
       } finally {
            try {
                if (fos != null) {
                    fos.close();
               }
                if (fis != null) {
                    fis.close();
               }
            } catch (IOException e) {
                e.printStackTrace();
           }
    }
    }

    /**
    * 字符流文件復(fù)制
    * @param oriFile
    * @param destFile
    */
   public static void charCopy(File oriFile, File destFile) {
        if(oriFile == null || !oriFile.exists()){
            return;
       }
       FileReader fr = null;
       FileWriter fw = null;
       try {
           fr = new FileReader(oriFile);
           fw = new FileWriter(destFile, true);
           char[] chars = new char[1024];
           int len;
           while ((len = fr.read(chars)) != -1) {
                fw.write(chars, 0, len);
               System.out.println(String.valueOf(chars));
               fw.flush();
           }
        } catch (Exception e) {
            e.printStackTrace();
       } finally {
            try {
                if (fw != null) {
                    fw.close();
               }
                if (fr != null) {
                    fr.close();
               }
            } catch (IOException e) {
                e.printStackTrace();
           }
        }
    }

    /**
    * 字節(jié)流Buffered復(fù)制
    * @param oriFile
    * @param destFile
    */
   public static void bufferedByteCopy(File oriFile, File destFile) {
        if(oriFile == null || !oriFile.exists()){
            return;
       }
       BufferedInputStream bis = null;
       BufferedOutputStream bos = null;
       try {
           bis = new BufferedInputStream(new FileInputStream(oriFile));
           bos = new BufferedOutputStream(new FileOutputStream(destFile));
           int len;
           while ((len = bis.read()) != -1) {
               bos.write(len);
               System.out.print((char) len);
               bos.flush();
           }
        } catch (Exception e) {
            e.printStackTrace();
       } finally {
            try {
                if (bis != null) {
                    bis.close();
               }
                if (bos != null) {
                    bos.close();
               }
            } catch (IOException e) {
                e.printStackTrace();
           }
        }
    }

    /**
    * 字符流Buffered復(fù)制
    * @param oriFile
    * @param destFile
    */
   public static void bufferedCharCopy(File oriFile, File destFile) {
        if(oriFile == null || !oriFile.exists()){
            return;
       }
       BufferedReader br = null;
       BufferedWriter bw = null;
       try {
            br = new BufferedReader(new FileReader(oriFile));
           bw = new BufferedWriter(new FileWriter(destFile));
          //int len = 0;
         // while ((len = br.read()) != -1) {
         //                bw.write(len);
         //                System.out.print((char) len);
         //            }
           //有更高級的讀法:
           String buffer;
           while ((buffer = br.readLine()) != null) { //一行行讀,判斷從-1變?yōu)閚ull了
                bw.write(buffer);
               System.out.print(buffer);
               bw.flush();
           }
        } catch (Exception e) {
            e.printStackTrace();
       } finally {
            try {
                if (br != null) {
                    br.close();
               }
                if (bw != null) {
                    bw.close();
               }
            } catch (IOException e) {
                e.printStackTrace();
           }
        }
    }

    /**
    * 字符流換printWrite實現(xiàn)復(fù)制
    * @param oriFile
    * @param destFile
    */
   public static void printWriteCharCopy(File oriFile, File destFile) {
        if(oriFile == null || !oriFile.exists()){
            return;
       }
        BufferedReader br = null;
       PrintWriter pw = null;
       try {
            br = new BufferedReader(new FileReader(oriFile));
           pw = new PrintWriter(new FileWriter(destFile));
           String buffer;
           while ((buffer = br.readLine()) != null) {
               pw.print(buffer);
               System.out.print(buffer);
               pw.flush();
           }
        } catch (Exception e) {
            e.printStackTrace();
       } finally {
            try {
                if (br != null) {
                    br.close();
               }
                if (pw != null) {
                    pw.close();
               }
            } catch (IOException e) {
                e.printStackTrace();
           }
        }
    }

    /**
    * 寫了一個相對比較萬能的文件復(fù)制方法
    * @param oriFile
    * @param destFile
    * @param byByte
    * @param isAppend
    */
   public static void fileCopy(File oriFile, File destFile, boolean byByte, boolean isAppend) {
        if(oriFile == null || !oriFile.exists()){
            return;
       }
        try {
           FileInputStream fis = new FileInputStream(oriFile);
           FileOutputStream fos = new FileOutputStream(destFile, isAppend);//isAppend 追加
           if (byByte) {
                BufferedInputStream bis = new BufferedInputStream(fis);
               BufferedOutputStream bos = new BufferedOutputStream(fos);
               byte[] bytes = new byte[1024];
               int len;
               while ((len = bis.read(bytes)) != -1) {
                    bos.write(bytes, 0, len);
                   bos.flush();
               }
               bos.close();
               bis.close();
           } else {
                BufferedReader br = new BufferedReader(new InputStreamReader(fis));
               //PrintWriter 構(gòu)造方法第二個參數(shù):autoFlash
               PrintWriter pw = new PrintWriter(new OutputStreamWriter(fos), true);
               String buffer;
               while ((buffer = br.readLine()) != null) {
                    pw.println(buffer);
               }
               pw.close();
               br.close();
           }
        } catch (Exception e) {
            e.printStackTrace();
       }
    }
六、byte[] 、char[] 、String相互轉(zhuǎn)換

順便總結(jié)下byte[] 、char[] 、String相互轉(zhuǎn)換的API。

/**
* String 轉(zhuǎn) char[]
*/
public static char[] stringToCharArray(String str) {
    return str.toCharArray();
}

/**
* char[] 轉(zhuǎn) String
*/
public static String charArrayToString(char[] chars) {
    //return String.valueOf(chars);
   return new String(chars);
}

/**
* String 轉(zhuǎn) byte[]
*/
public static byte[] stringToByteArray(String str) throws Exception {
    return str.getBytes("UTF-8");
}

/**
* byte[] 轉(zhuǎn) String
*/
public static String byteArrayToString(byte[] bytes) {
    return new String(bytes);
}

/**
* byte[] 轉(zhuǎn) char[]
*/
public static char[] byteArrayToCharArray(byte[] bytes) {
   Charset cs = Charset.forName("UTF-8");
   ByteBuffer bb = ByteBuffer.allocate(bytes.length);
   bb.put(bytes);
   bb.flip();
   CharBuffer cb = cs.decode(bb);
   return cb.array();
}

/**
* char[] 轉(zhuǎn) byte[]
*/
public static byte[] charArrayToByteArray(char[] chars) {
   Charset cs = Charset.forName("UTF-8");
   CharBuffer cb = CharBuffer.allocate(chars.length);
   cb.put(chars);
   cb.flip();
   ByteBuffer bb = cs.encode(cb);
   return bb.array();
}
七、NIO

JDK 1.4后,Java提供了一個全新的IO API,即 Java New IO,特點是面向緩沖區(qū),提供多路非阻塞式的IO操作。

核心組件:

  • 通道(Channel)
  • 緩沖區(qū)(Buffer)
  • 選擇器(Selectors)

詳細介紹:

實例:實現(xiàn)文件復(fù)制

       // 設(shè)置輸入源 & 輸出地 = 文件
       String infile = “XXX";
       String outfile = “XXX";
       // 1. 獲取數(shù)據(jù)源 和 目標傳輸?shù)氐妮斎胼敵隽鳎ù颂幰詳?shù)據(jù)源 = 文件為例)
       FileInputStream fin = new FileInputStream(infile);
       FileOutputStream fout = new FileOutputStream(outfile);
       // 2. 獲取數(shù)據(jù)源的輸入輸出通道
       FileChannel fcin = fin.getChannel();
       FileChannel fcout = fout.getChannel();
       // 3. 創(chuàng)建緩沖區(qū)對象
       ByteBuffer buff = ByteBuffer.allocate(1024);
       while (true) {
           // 4. 從通道讀取數(shù)據(jù) & 寫入到緩沖區(qū)
           // 注:若 以讀取到該通道數(shù)據(jù)的末尾,則返回-1 
           int r = fcin.read(buff);
           if (r == -1) {
               break;
           }
           // 5. 傳出數(shù)據(jù)準備:調(diào)用flip()方法 
           buff.flip();
           // 6. 從 Buffer 中讀取數(shù)據(jù) & 傳出數(shù)據(jù)到通道
           fcout.write(buff);
           // 7. 重置緩沖區(qū)
           buff.clear();
         }
       }

與 Java IO區(qū)別:

NIO部分內(nèi)容向Carson_Ho學習:http://www.itdecent.cn/p/d30893c4d6bb

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者。

友情鏈接更多精彩內(nèi)容