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ù)廷雅。相當(dāng)于拿個桶舀水。
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) 標記讀取位置诚些,下次還可以從這里開始讀取(使用前要看當(dāng)前流是否支持皇型,可以使用 markSupport() 方法判斷)诬烹。
public synchronized void reset() 重置讀取位置為上次 mark 標記的位置。
public boolean markSupported() 判斷當(dāng)前流是否支持標記流弃鸦,和上面兩個方法配套使用绞吁。

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ù)寫到文件中,當(dāng)你數(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() 判斷當(dāng)前流是否支持標記流
public void mark(int readAheadLimit) 標記讀取位置客燕,下次還可以從這里開始讀取鸳劳,使用前要看當(dāng)前流是否支持,可以使用 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學(xué)習(xí):http://www.reibang.com/p/d30893c4d6bb

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者梁只。
  • 序言:七十年代末缚柳,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子搪锣,更是在濱河造成了極大的恐慌秋忙,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件构舟,死亡現(xiàn)場離奇詭異笙什,居然都是意外死亡,警方通過查閱死者的電腦和手機慕嚷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門侈贷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人抡谐,你說我怎么就攤上這事裁奇。” “怎么了麦撵?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵刽肠,是天一觀的道長。 經(jīng)常有香客問我免胃,道長音五,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任羔沙,我火速辦了婚禮躺涝,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘扼雏。我一直安慰自己坚嗜,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布诗充。 她就那樣靜靜地躺著苍蔬,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蝴蜓。 梳的紋絲不亂的頭發(fā)上碟绑,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天俺猿,我揣著相機與錄音,去河邊找鬼格仲。 笑死押袍,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的抓狭。 我是一名探鬼主播伯病,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼否过!你這毒婦竟也來了午笛?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤苗桂,失蹤者是張志新(化名)和其女友劉穎药磺,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體煤伟,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡癌佩,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了便锨。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片围辙。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖放案,靈堂內(nèi)的尸體忽然破棺而出姚建,到底是詐尸還是另有隱情,我是刑警寧澤吱殉,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布掸冤,位于F島的核電站,受9級特大地震影響友雳,放射性物質(zhì)發(fā)生泄漏稿湿。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一押赊、第九天 我趴在偏房一處隱蔽的房頂上張望饺藤。 院中可真熱鬧,春花似錦流礁、人聲如沸涕俗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽咽袜。三九已至丸卷,卻和暖如春枕稀,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工萎坷, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留凹联,地道東北人。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓哆档,卻偏偏與公主長得像蔽挠,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子瓜浸,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,976評論 2 355