日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

javaio流_万字长文+思维导图帮你梳理 Java IO 流,还学不会你来打我(值得收藏)...

發(fā)布時間:2025/3/19 java 54 豆豆
生活随笔 收集整理的這篇文章主要介紹了 javaio流_万字长文+思维导图帮你梳理 Java IO 流,还学不会你来打我(值得收藏)... 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

?前言

在上一篇的文章獲取不錯的瀏覽量后,繼續(xù)加更的念頭一直徘徊在心中,本來是想花段時間深入學習tomcat的,可是tomcat的源碼中就有至關(guān)重要的NIO,于是得先整一下NIO,但是NIO的基礎(chǔ)是BIO,于是這篇文章就先寫IO流吧。

學習NIO(非阻塞IO),千萬不能被IO阻塞住哇!

IO流在java中其實是很重要的一塊知識點,難度還好,但是容易被忽視,因為工作中真正寫IO的代碼少之又少。

IO的難點在于

  • IO流api很多,各種基礎(chǔ)的流,包裝的流嵌套使用很難記憶
  • 基本每個方法都要拋出非運行時異常
  • 導致很多開發(fā)學過io流一段時間后,寫不出一段正確的io流代碼。

    本文將從理論+代碼的方式由淺入深的帶大家學習IO流,通過圖解的方式來記憶常用的IO流。

    文末有IO總結(jié)的思維導圖,很多博文采用的都是上來一張圖,我覺得對于閱讀者來說很容易陷進去,所以建議理清各個流后再去看思維導圖。

    File

    在正式的介紹IO流之前,我覺得應(yīng)該介紹一下File類,該類主要是對文件和目錄的抽象表示,因為學習io流第一反應(yīng)就是文件,該類提供了對文件的創(chuàng)建、刪除、查找等操作。主要有以下特點

  • java的世界萬物皆對象,文件和目錄就可抽象為File對象
  • 對于File而言,封裝的并不是真正的文件,封裝的僅僅是一個路徑名,磁盤文件本身可以存在,也可以不存在
  • 文件的內(nèi)容不能用File讀取,而是通過流來讀取,File對象可以作為流的來源地和目的地
  • File類的常用構(gòu)造方法

    構(gòu)造方法方法說明
    File(String pathname)將路徑字符串抽象為File實例,路徑字符串可以是相對路徑,也可以為絕對路徑
    File(String parent, String child)從父路徑名和子路徑名來構(gòu)建File實例
    File(File parent, String child)根據(jù)父File實例和子路徑名來構(gòu)建File實例

    如下示例,表示這幾種文件和目錄的代碼

    //?pathname
    File?liuBei?=?new?File("D:/三國/劉備.jpg");
    //?String?parent,?String?child
    File?guanYu?=?new?File("D:/三國",?"關(guān)羽.jpg");
    //?目錄
    File?sanGuo?=?new?File("D:/三國");
    //?File?parent,?String?child
    File?zhangFei?=?new?File(sanGuo,?"張飛.txt");
    //?可以聲明不存在的文件
    File?zhuGeLiang?=?new?File(sanGuo,?"諸葛亮.txt");

    絕對路徑和相對路徑

    絕對路徑:從盤符開始的路徑,表示一個完整的路徑。(經(jīng)常使用) 相對路徑:相對于當前項目目錄的路徑

    File?f?=?new?File("D:/bbb.java");
    //?D:\bbb.java
    System.out.println(f.getAbsolutePath());
    File?f2?=?new?File("bbb.java");
    //?F:\code\ad\bbb.java
    System.out.println(f2.getAbsolutePath());

    路徑分隔符和換行符

    路徑分隔符

  • windows的路徑分隔符:?\
  • linux的路徑分隔符:?/
  • java有常量separator表示路徑分隔符

    public?static?final?String?separator?=?""?+?separatorChar;

    換行符

  • windows的換行符:?\r\n
  • linux的換行符 ?\n
  • File的常用方法

    創(chuàng)建、刪除

    方法名方法說明
    boolean createNewFile() throws IOException當該名稱的文件不存在時,創(chuàng)建一個由該抽象路徑名的空文件并返回true,當文件存在時,返回false
    boolean mkdir()創(chuàng)建由此抽象路徑名命名的目錄
    boolean mkdirs()創(chuàng)建由此抽象路徑名命名的目錄,包括任何必需但不存在的父目錄。級聯(lián)創(chuàng)建目錄
    boolean delete()刪除由此抽象路徑名表示的文件或目錄

    上述方法比較簡單,其中需要注意的是

    • 創(chuàng)建多級目錄時,mkdir創(chuàng)建失敗,返回false,mkdirs創(chuàng)建成功,返回true(推薦使用mkdirs)
    • 刪除目錄時,目錄內(nèi)不為空時,刪除失敗,返回false, 即只能刪除文件或者空目錄
    File?shuiHu?=?new?File("D:/四大名著/水滸傳");
    //?返回false?創(chuàng)建失敗
    boolean?mkdir?=?shuiHu.mkdir();
    //?返回true?創(chuàng)建失敗
    boolean?mkdirs?=?shuiHu.mkdirs();

    File?four?=?new?File("D:/四大名著");
    //?返回false?刪除目錄時必須目錄為空才能刪除成功
    boolean?delete?=?four.delete();

    File?shuiHu?=?new?File("D:/四大名著/水滸傳");
    //?true?正確刪除了水滸傳目錄
    boolean?delete1?=?shuiHu.delete();

    File?liuBei?=?new?File("D:/三國/劉備.jpg");
    //?返回true?正確刪除了劉備.jpg文件
    boolean?delete2?=?liuBei.delete();

    文件檢測

    方法名方法說明
    boolean isDirectory()判斷是否是目錄
    boolean isFile()判斷是否是文件
    boolean exists()判斷文件或目錄是否存在
    boolean canWrite()文件是否可寫
    boolean canRead()文件是否可讀
    boolean canExecute()文件是否可執(zhí)行
    long lastModified()返回文件的上次修改時間

    注意的是

    • 文件或目錄不存在時, isDirectory() 或 isFile() ?返回false
    • 可讀、可寫、可執(zhí)行是對操作系統(tǒng)給文件賦予的權(quán)限
    File?xiYou?=?new?File("D:/西游記");
    //?文件或目錄不存在時?返回false
    System.out.println(xiYou.isDirectory());

    文件獲取

    方法名方法說明
    String getAbsolutePath()返回File對象的絕對路徑字符串
    String getPath()將此抽象路徑名轉(zhuǎn)換為路徑名字符串
    String getName()返回文件或目錄的名稱
    long length()返回由此File表示的文件的字節(jié)數(shù)
    String[] list()返回目錄中的文件和目錄的名稱字符串數(shù)組
    File[] listFiles()返回目錄中的文件和目錄的File對象數(shù)組

    注意

    • length() 返回的是文件的字節(jié)數(shù),目錄的 長度是0
    • getPath()在用絕對路徑表示的文件時相同,用相對路徑表示的文件時不同
    • listFiles和list方法的調(diào)用,必須是實際存在的目錄,否則返回null
    • listFiles和list 可以傳入FilenameFilter的實現(xiàn)類,用于按照文件名稱過濾文件
    File?shuiHu?=?new?File("D:/水滸傳");
    //?0
    System.out.println(shuiHu.length());
    File?liuBei?=?new?File("D:/三國/劉備.jpg");
    //?24591
    System.out.println(liuBei.length());

    File?f?=?new?File("D:/bbb.java");
    ?//?D:\bbb.java
    System.out.println(f.getPath());

    File?f2?=?new?File("bbb.java");
    //?bbb.java
    System.out.println(f2.getPath());

    File?sanGuo2?=?new?File("D:/三國2");
    //?該目錄不存在,返回null
    String[]?list?=?sanGuo2.list();

    過濾文件的接口

    @FunctionalInterface
    public?interface?FilenameFilter?{
    ???//?參數(shù)為目錄和指定過濾名稱
    ????boolean?accept(File?dir,?String?name);
    }

    擴展(由讀者自己實現(xiàn))

    讀取目錄下所有的文件以及目錄,包括子目錄下所有的文件及目錄

    IO流

    上一章節(jié)學習了使用File類創(chuàng)建、查找、刪除文件,但是無法讀取、傳輸文件中的內(nèi)容。

    IO流主要是讀取、傳輸、寫入數(shù)據(jù)內(nèi)容的。

    I: input,O:output

    這里的主體說的都是程序(即內(nèi)存),從外部設(shè)備中讀取數(shù)據(jù)到程序中 即為輸入流,從程序中寫出到外部程序中即為輸出流

    IO的分類

    • 本地IO和網(wǎng)絡(luò)IO

      本地IO主要是操作本地文件,例如在windows上復制粘貼操作文件,都可以使用java的io來操作

      網(wǎng)絡(luò)IO主要是通過網(wǎng)絡(luò)發(fā)送數(shù)據(jù),或者通過網(wǎng)絡(luò)上傳、下載文件,我們每天上網(wǎng)無時無刻不在體驗著IO的傳輸

    • 按流向分,輸入流和輸出流

    • 按數(shù)據(jù)類型分:字節(jié)流和字符流

    • 按功能分:節(jié)點流和處理流

      • 程序直接操作目標設(shè)備的類稱為節(jié)點流
      • 對節(jié)點流進行裝飾,功能、性能進行增強,稱為處理流

    IO流主要的入口是數(shù)據(jù)源,下面列舉常見的源設(shè)備和目的設(shè)備

    源設(shè)備

  • 硬盤(文件)
  • 內(nèi)存(字節(jié)數(shù)組、字符串等)
  • 網(wǎng)絡(luò)(Socket)
  • 鍵盤(System.in)
  • 目的設(shè)備

  • 硬盤(文件)
  • 內(nèi)存(字節(jié)數(shù)組、字符串等)
  • 網(wǎng)絡(luò)(Socket)
  • 控制臺(System.out)
  • 本文先探討本地IO的字節(jié)流和字符流,先列舉字節(jié)流和字符流的公共方法

    方法名方法說明
    void close() throws IOException流操作完畢后,必須釋放系統(tǒng)資源,調(diào)用close方法,一般放在finally塊中保證一定被執(zhí)行!

    注意:

    • 程序中打開的IO資源不屬于內(nèi)存資源,垃圾回收機制無法回收該資源,需要顯式的關(guān)閉文件資源
    • 下面的代碼示例中就不顯示的調(diào)用close方法,也不會處理IOException,只是為了代碼的簡潔,方便閱讀

    字節(jié)流

    一切皆為字節(jié)

    一切文件數(shù)據(jù)(文本、圖片、視頻等)在存儲時,都是以二進制的形式保存,都可以通過使用字節(jié)流傳輸。

    InputStream是字節(jié)輸入流的頂層抽象

    //?Closeable有close()方法
    public?abstract?class?InputStream?implements?Closeable?{}

    核心方法如下

    方法名方法說明
    int read() throws IOException;每次讀取一個字節(jié)的數(shù)據(jù),提升為int類型,讀取到文件末尾時返回 -1
    int read(byte b[])throws IOException每次讀取到字節(jié)數(shù)組中,返回讀取到的有效字節(jié)個數(shù),讀取到末尾時返回 -1(常用)
    int read(byte b[], int off, int len)每次讀取到字節(jié)數(shù)組中,從偏移量off開始,長度為len,返回讀取到的有效字節(jié)個數(shù),讀取到末尾時返回 -1

    OutputStream是字節(jié)輸出流的頂層抽象

    //?Flushable里面有flush()方法
    public?abstract?class?OutputStream?implements?Closeable,?Flushable?{}

    核心方法如下

    方法名方法說明
    void write(int b) throws IOException;將int值寫入到輸出流中
    void write(byte[] b) throws IOException;將字節(jié)數(shù)組寫入到輸出流中
    void write(byte b[], int off, int len) throws IOException將字節(jié)數(shù)組從偏移量off開始,寫入len個長度到輸出流中
    void flush() throws IOException刷新輸出流并強制緩沖的字節(jié)被寫出

    文件節(jié)點流

    InputStream有很多的實現(xiàn)類,先介紹下文件節(jié)點流,即目標設(shè)備是文件,輸入流和輸出流對應(yīng)的是

    FileInputStream和FileOutputStream

    FileInputStream主要從磁盤文件中讀取數(shù)據(jù),常用構(gòu)造方法如下

    public?FileInputStream(File?file)?throws?FileNotFoundException{}
    public?FileInputStream(String?name)?throws?FileNotFoundException{};

    當傳入的文件不存在時,運行時會拋出FileNotFoundException異常

  • read()方法讀取
  • File?file?=?new?File("D:/三國/諸葛亮.txt");
    FileInputStream?fileInputStream?=?new?FileInputStream(file);

    //?核心代碼
    int?b;
    while?((b?=?fileInputStream.read())?!=?-1?){
    ????System.out.print((char)?b);
    }

    //?輸出結(jié)果
    abcde
  • read(byte[])讀取
  • File?file?=?new?File("D:/三國/諸葛亮.txt");
    FileInputStream?fileInputStream?=?new?FileInputStream(file);

    //?核心代碼
    byte[]?data?=?new?byte[2];
    while?(fileInputStream.read(data)?!=?-1)?{
    ????System.out.println(new?String(data));
    }

    //?輸出結(jié)果
    ab
    cd
    ed

    上述代碼由于最后一次讀取時,只讀取一個字節(jié) e ,數(shù)組中還是上次的數(shù)據(jù)cd,只替換了e,所以最后輸出了ed

    下面是使用FileInputStream讀取的正確姿勢

    File?file?=?new?File("D:/三國/諸葛亮.txt");
    FileInputStream?fileInputStream?=?new?FileInputStream(file);

    //?核心代碼
    byte[]?data?=?new?byte[2];
    int?len;
    while?((len?=?fileInputStream.read(data))?!=?-1)?{
    ????//?len?為每次讀取的有效的字節(jié)個數(shù)
    ????System.out.println(new?String(data,?0,?len));
    }

    //?輸出結(jié)果
    ab
    cd
    e

    注意:使用數(shù)組讀取,每次讀取多個字節(jié),減少了系統(tǒng)間的IO操作次數(shù),從而提高了效率,建議使用

    源碼解析

    public?int?read()?throws?IOException?{
    ????return?read0();
    }
    private?native?int?read0()?throws?IOException;

    public?int?read(byte?b[])?throws?IOException?{
    ???return?readBytes(b,?0,?b.length);
    }
    private?native?int?readBytes(byte?b[],?int?off,?int?len)?throws?IOException;

    上面列了read()和read(byte[])的源碼,可見都是調(diào)用native的方法,涉及底層的系統(tǒng)調(diào)用。

    • 如果用read()讀取文件,每讀取一個字節(jié)就要訪問一次硬盤,這種效率較低。

    • 如果用read(byte[])讀取文件,一次讀取多個字節(jié),當文件很大時,也會頻繁訪問硬盤。如果一次讀取超多字節(jié),效率也不會高。

    FileOutputStream主要是向磁盤文件中寫出數(shù)據(jù),常用構(gòu)造方法如下

    構(gòu)造方法名方法說明
    FileOutputStream(File file) throws FileNotFoundException使用一個File對象來構(gòu)建一個FileOutputStream
    FileInputStream(String name) throws FileNotFoundException使用一個文件名來構(gòu)建一個FileOutputStream
    FileOutputStream(File file, boolean append) ? ? throws FileNotFoundExceptionappend傳true時,會對文件進行追加
    FileOutputStream(String name, boolean append) ? ? throws FileNotFoundExceptionappend傳true時,會對文件進行追加

    注意:

    • 上述構(gòu)造方法執(zhí)行后,如果file不存在,會自動創(chuàng)建該文件

    • 如果file存在,append沒有傳或者傳了false,會清空文件的數(shù)據(jù)

    • 如果file存在,append傳了true,不會清空文件的數(shù)據(jù)

    File?file?=?new?File("D:/三國/趙云.txt");
    FileOutputStream?fos?=?new?FileOutputStream(file);
    FileOutputStream?fos1?=?new?FileOutputStream("D:/三國/司馬懿.txt");
    //?上述代碼中執(zhí)行完后,趙云.txt和司馬懿.txt都會自動創(chuàng)建出來

    向文件寫數(shù)據(jù)

    FileOutputStream?fos?=?new?FileOutputStream("D:/三國/司馬懿.txt");
    fos.write(96);
    fos.write(97);
    fos.write(98);
    //?文件內(nèi)容為?abc

    FileOutputStream?fos?=?new?FileOutputStream("D:/三國/趙云.txt");
    fos.write("三國趙云".getBytes());
    //?文件內(nèi)容為?三國趙云

    上述代碼每執(zhí)行一次,文件里的內(nèi)容就會被覆蓋,有時候這不是我們想要的場景,我們一般是想追加文件

    FileOutputStream?fos?=?new?FileOutputStream("D:/三國/趙云.txt",?true);
    fos.write("有萬夫不當之勇".getBytes());
    fos.close();
    //?文件內(nèi)容為?三國趙云有萬夫不當之勇

    應(yīng)用場景

    開發(fā)中涉及文件的上傳、下載、傳輸都是用的這個節(jié)點流,會結(jié)合裝飾后的處理流一起使用,在緩沖流部分有介紹。

    擴展(由讀者自己實現(xiàn))

    利用文件節(jié)點流實現(xiàn)文件的復制

    內(nèi)存節(jié)點流

    ByteArrayInputStream是從內(nèi)存的字節(jié)數(shù)組中讀取數(shù)據(jù)

    public?ByteArrayInputStream(byte?buf[])?{}

    注意:不需要close數(shù)據(jù)源和拋出IOException,因為不涉及底層的系統(tǒng)調(diào)用

    ByteArrayOutputStream是向內(nèi)存字節(jié)數(shù)組中寫數(shù)據(jù),內(nèi)部維護了一個數(shù)組

    public?ByteArrayOutputStream()?{
    ???//?內(nèi)部維護了一個可變的字節(jié)數(shù)組
    ???//?protected?byte?buf[];
    ????this(32);
    }

    內(nèi)存節(jié)點流代碼示例

    ByteArrayInputStream?bis?=?new?ByteArrayInputStream("data".getBytes());
    ByteArrayOutputStream?bos?=?new?ByteArrayOutputStream();

    int?len?=?0;
    while?((len?=?bis.read())?!=?-1){
    ????bos.write(len);
    }
    //?輸出data
    System.out.println(new?String(bos.toByteArray()));

    應(yīng)用場景

  • 內(nèi)存操作流一般在一些生成臨時信息時會被使用,如果臨時信息保存著文件中,代碼執(zhí)行完還要刪除文件比較麻煩
  • 結(jié)合對象流,可以實現(xiàn)對象和字節(jié)數(shù)組的互轉(zhuǎn)
  • 字符流

    字符流封裝了更加適合操作文本字符的方法

    Reader用于讀取文本字符

    public?abstract?class?Reader?implements?Readable,?Closeable?{}

    核心方法

    方法名方法說明
    int read() throws IOException從輸入流中讀取一個字符,讀到文件末尾時返回-1
    int read(char cbuf[]) throws IOException從輸入流中讀取字符到char數(shù)組中

    Writer用于寫出文本字符

    public?abstract?class?Writer?implements?Appendable,?Closeable,?Flushable?{}

    核心方法

    方法名方法說明
    void write(int c) throws IOException寫入單個字符到輸出流中
    void write(char[] cbuf) throws IOException寫入字符數(shù)組到輸出流中
    void write(char[] cbuf, int off, int len) ?throws IOException寫入字符數(shù)組的一部分,偏移量off開始,長度為len到輸出流中
    void write(String str) throws IOException直接寫入字符串到輸出流中(常用)
    void write(String str, int off, int len) throws IOException寫入字符串的一部分,偏移量off開始,長度為len
    Writer append(char c) throws IOException追加字符到輸出流中

    文件節(jié)點流

    字符流操作純文本字符的文件是最合適的,主要有FileReader和FileWriter

    FileReader主要是向磁盤文件中寫出數(shù)據(jù),常用構(gòu)造方法如下

    public?FileReader(String?fileName)?throws?FileNotFoundException{}
    public?FileReader(File?file)?throws?FileNotFoundException?{}

    注意:當讀取的文件不存在時,會拋出FileNotFoundException,這點和FileInputStream一致

  • read()循環(huán)讀取文件
  • FileReader?fileReader?=?new?FileReader("D:/三國/趙云.txt");
    int?b;
    while?((b?=?fileReader.read())?!=?-1)?{
    ????System.out.println((char)?b);
    }
  • read(char[]) 讀取文件
  • FileReader?fileReader?=?new?FileReader("D:/三國/趙云.txt");
    int?len;
    char[]?data?=?new?char[2];
    while?((len?=?fileReader.read(data))?!=?-1)?{
    ????System.out.println(new?String(data,?0,?len));
    }
    //?兩個字符兩個字符依次讀取

    FileWriter構(gòu)造方法如下,和FileOutStream構(gòu)造方法類似,和FileOutputStream類似。

    public?FileWriter(String?fileName)?throws?IOException?{}
    public?FileWriter(String?fileName,?boolean?append)?throws?IOException?{}
    public?FileWriter(File?file)?throws?IOException{}
    public?FileWriter(File?file,?boolean?append)?throws?IOException?{}

    常用的寫數(shù)據(jù)進文件的方法

    FileWriter?fileWriter?=?new?FileWriter("D:/三國/孫權(quán).txt");
    fileWriter.write(97);?
    fileWriter.write('b');?
    fileWriter.write('C');?
    fileWriter.write("權(quán)");?
    fileWriter.append("力");

    注意:

    • 如果不執(zhí)行close()或者flush()方法,數(shù)據(jù)只是保存到緩沖區(qū),不會保存到文件。這點和與FileOutputStream不同,原因見 字節(jié)流和字符流的共同點章節(jié)

    應(yīng)用場景

    純文本文件的io操作,配合處理流一起實現(xiàn)。

    內(nèi)存節(jié)點流

    字符流也有對應(yīng)的內(nèi)存節(jié)點流,常用的有StringWriter和CharArrayWriter

    StringWriter是向內(nèi)部的StringBuffer對象寫數(shù)據(jù)。

    //?定義
    public?class?StringWriter?extends?Writer?{

    ????private?StringBuffer?buf;

    ????public?StringWriter()?{
    ????????buf?=?new?StringBuffer();
    ????????lock?=?buf;
    ????}
    }

    //?應(yīng)用
    StringWriter?sw?=?new?StringWriter();
    sw.write("hello");

    StringBuffer?buffer?=?sw.getBuffer();
    //?輸出hello
    System.out.println(buffer.toString());

    CharArrayWriter是向內(nèi)部的char數(shù)組寫數(shù)據(jù)

    //?定義
    public?class?CharArrayWriter?extends?Writer?{
    ????protected?char?buf[];
    }

    //?應(yīng)用?
    CharArrayWriter?caw?=?new?CharArrayWriter();
    caw.write("hello");
    char[]?chars?=?caw.toCharArray();
    for?(char?c?:?chars)?{
    ?//?輸出了h?e?l?l?o
    ?System.out.println(c);
    }

    四種常用節(jié)點流的使用總結(jié)

    字節(jié)流和字符流的共同點

    注意到,OutputStream、Reader、Writer都實現(xiàn)了Flushable接口,Flushable接口有flush()方法

    flush():強制刷新緩沖區(qū)的數(shù)據(jù)到目的地,刷新后流對象還可以繼續(xù)使用

    close(): 強制刷新緩沖區(qū)后關(guān)閉資源,關(guān)閉后流對象不可以繼續(xù)使用

    緩沖區(qū):可以理解為內(nèi)存區(qū)域,程序頻繁操作資源(如文件)時,性能較低,因為讀寫內(nèi)存較快,利用內(nèi)存緩沖一部分數(shù)據(jù),不要頻繁的訪問系統(tǒng)資源,是提高效率的一種方式

    具體的流只要內(nèi)部有維護了緩沖區(qū),必須要close()或者flush(),不然不會真正的輸出到文件中

    處理流

    上面的章節(jié)介紹了字節(jié)流和字符流的常用節(jié)點流,但是真正開發(fā)中都是使用更為強大的處理流

    處理流是對節(jié)點流在功能上、性能上的增強

    字節(jié)流的處理流的基類是FilterInputStreamFilterOutputStream

    緩沖流(重點)

    前面說了節(jié)點流,都是直接使用操作系統(tǒng)底層方法讀取硬盤中的數(shù)據(jù),緩沖流是處理流的一種實現(xiàn),增強了節(jié)點流的性能,為了提高效率,緩沖流類在初始化對象的時候,內(nèi)部有一個緩沖數(shù)組,一次性從底層流中讀取數(shù)據(jù)到數(shù)組中,程序中執(zhí)行read()或者read(byte[])的時候,就直接從內(nèi)存數(shù)組中讀取數(shù)據(jù)。

    分類

    字節(jié)緩沖流:BufferedInputStream , BufferedOutputStream

    字符緩沖流:BufferedReader , BufferedWriter

    字節(jié)緩沖流

    可見構(gòu)造方法傳入的是節(jié)點流,是對節(jié)點流的裝飾

    //?內(nèi)部默認8192?=8*1024?即8M的緩沖區(qū)
    public?BufferedInputStream(InputStream?in)?{
    ???//?8192????
    ???//?內(nèi)部維護了下面這樣的字節(jié)數(shù)組
    ????//?protected?volatile?byte?buf[];
    ????this(in,?DEFAULT_BUFFER_SIZE);
    }
    public?BufferedOutputStream(OutputStream?out)?{
    ????????this(out,?8192);
    }

    這里使用復制一部1G的電影來感受緩沖流的強大

  • 使用基本的流讀取數(shù)據(jù)(一次傳輸一個字節(jié))
  • long?start?=?System.currentTimeMillis();
    FileInputStream?fis?=?new?FileInputStream("D:/三國/視頻.mp4");
    FileOutputStream?fos?=?new?FileOutputStream("D:/三國/拷貝.mp4");

    int?data;
    while?((data?=?fis.read())?!=?-1)?{
    ????fos.write(data);
    }
    log.info("拷貝電影耗時:{}ms",?System.currentTimeMillis()?-?start);
    //?五分鐘還沒拷好,關(guān)閉程序了...
  • 使用基本的流讀取數(shù)據(jù)(一次傳輸一個8M的字節(jié)數(shù)組)
  • long?start?=?System.currentTimeMillis();
    FileInputStream?fis?=?new?FileInputStream("D:/三國/視頻.mp4");
    FileOutputStream?fos?=?new?FileOutputStream("D:/三國/拷貝.mp4");

    int?len;
    byte[]?data?=?new?byte[1024?*?1024?*?1024];
    while?((len?=?fis.read(data))?!=?-1)?{
    ????fos.write(data,?0,?len);
    }
    log.info("拷貝電影耗時:{}ms",?System.currentTimeMillis()?-?start);
    //?拷貝電影耗時:4651ms
  • 使用緩沖流讀取數(shù)據(jù)(一次傳輸一個字節(jié))
  • long?start?=?System.currentTimeMillis();
    BufferedInputStream?fis?=?new?BufferedInputStream(new?FileInputStream("D:/三國/視頻.mp4"));
    BufferedOutputStream?fos?=?new?BufferedOutputStream(new?FileOutputStream("D:/三國/拷貝.mp4"));

    int?data;
    while?((data?=?fis.read())?!=?-1)?{
    ????fos.write(data);
    }

    log.info("拷貝電影耗時:{}ms",?System.currentTimeMillis()?-?start);
    //?拷貝電影耗時:39033ms
  • 使用緩沖流讀取數(shù)據(jù)(一次傳輸一個8M的字節(jié)數(shù)組)(最常使用)
  • long?start?=?System.currentTimeMillis();
    BufferedInputStream?fis?=?new?BufferedInputStream(new?FileInputStream("D:/三國/視頻.mp4"));
    BufferedOutputStream?fos?=?new?BufferedOutputStream(new?FileOutputStream("D:/三國/拷貝.mp4"));

    int?len;
    byte[]?data?=?new?byte[8?*?1024];
    while?((len?=?fis.read(data))?!=?-1)?{
    ????fos.write(data,?0,?len);
    }

    log.info("拷貝電影耗時:{}ms",?System.currentTimeMillis()?-?start);
    //?拷貝電影耗時:1946ms

    由上述四個例子可以得出結(jié)論,緩沖流讀取數(shù)據(jù)比普通流讀取數(shù)據(jù)快很多!

    注意:采用邊讀邊寫的方式,一次傳輸幾兆的數(shù)據(jù)效率比較高,如果采用先把文件的數(shù)據(jù)都讀入內(nèi)存,在進行寫出,這樣讀寫的次數(shù)是較小,但是占用太大的內(nèi)存空間,一次讀太大的數(shù)據(jù)也嚴重影響效率!

    字符緩沖流

    對字符節(jié)點流的裝飾,下面是字符緩沖流的構(gòu)造方法

    public?BufferedReader(Reader?in)?{
    ?//?private?static?int?defaultCharBufferSize?=?8192;
    ?//?內(nèi)部維護了一個字符數(shù)組
    ????//?private?char?cb[];
    ????this(in,?defaultCharBufferSize);
    }

    public?BufferedWriter(Writer?out)?{
    ????????this(out,?defaultCharBufferSize);
    }

    字符緩沖流的特有方法

    類方法名方法說明
    BufferedReaderString readLine() throws IOException一行行讀取,讀取到最后一行返回null
    BufferedWritervoid newLine() throws IOException寫一個換行符到文件中,實現(xiàn)換行
    //?創(chuàng)建流對象
    BufferedReader?br?=?new?BufferedReader(new?FileReader("D:/三國/趙云.txt"));
    BufferedWriter?bw?=?new?BufferedWriter(new?FileWriter("D:/三國/趙子龍.txt"));
    String?line?=?null;
    while?((line?=?br.readLine())!=null)?{
    ??System.out.println(line);
    ??bw.write(line);
    ??bw.newLine();
    }
    //?結(jié)果
    我乃常山趙子龍
    于萬軍從中,取上將首級

    緩沖流的正確姿勢

    緩沖流是IO流中最重要的知識點,下面通過代碼實現(xiàn)正確用IO流的姿勢

    BufferedInputStream?bis?=?null;
    BufferedOutputStream?bos?=?null;
    try?{
    ????bis?=?new?BufferedInputStream(new?FileInputStream(new?File("D:/三國/視頻.mp4")));
    ????bos?=?new?BufferedOutputStream(new?FileOutputStream(new?File("D:/三國/拷貝.mp4")));
    ????int?len;
    ????//?一次傳輸8M的文件,實際測試這里傳輸?shù)拇笮〔⒉挥绊憘鬏數(shù)乃俣?br />????byte[]?data?=?new?byte[8?*?1024];
    ????while?((len?=?bis.read(data))?!=?-1)?{
    ????????bos.write(data,?0,?len);
    ????}
    }?catch?(IOException?e)?{
    ????log.error("error",?e);
    }?finally?{
    ???//?finally塊中關(guān)閉流,確保資源一定被關(guān)閉
    ????if?(bis?!=?null)?{
    ????????try?{
    ????????????bis.close();
    ????????}?catch?(IOException?e)?{
    ????????????log.error("error",?e);
    ????????}
    ????}
    ????if?(bos?!=?null)?{
    ????????try?{
    ????????????bos.close();
    ????????}?catch?(IOException?e)?{
    ????????????log.error("error",?e);
    ????????}
    ????}
    }

    轉(zhuǎn)換流

    字符編碼與字符集

    字符編碼

    計算機存儲的數(shù)據(jù)都是二進制的,而我們在電腦上看到的數(shù)字、英文、漢字等都是二進制轉(zhuǎn)換的結(jié)果

    • 將字符轉(zhuǎn)換成二進制,為編碼

    • 將二進制轉(zhuǎn)換為字符,為解碼

      字符編碼 就是 自然語言和二進制的對應(yīng)規(guī)則

    字符集

    就是一個編碼表,常見的字符集有ASCII字符集、GBK字符集、Unicode字符集等,具體各個編碼的介紹在這里就不介紹了。

    IDEA中,使用 FileReader 讀取項目中的文本文件。IDEA可以設(shè)置為GBK 編碼,當讀取Windows系統(tǒng)中創(chuàng)建的默認的UTF8文本文件時,就會出現(xiàn)亂碼 。

    如下例

    idea的字符集設(shè)置 默認是UTF-8,這里修改為GBK

    運行的代碼及結(jié)果

    FileReader?fileReader?=?new?FileReader("D:/sanguo/utf8.txt");
    int?read;
    while?((read?=?fileReader.read())?!=?-1)?{
    ????System.out.print((char)read);
    }
    //?浣犲ソ

    InputStreamReader

    Reader的子類,讀取字節(jié),并使用指定的字符集將其解碼為字符。字符集可以自己指定,也可以使用平臺的默認字符集。

    構(gòu)造方法如下

    //?使用平臺默認字符集
    public?InputStreamReader(InputStream?in)?{}
    //?指定字符集
    public?InputStreamReader(InputStream?in,?String?charsetName)throws?UnsupportedEncodingException{}

    讀取文件的“你好",文件默認的字符集是UTF8

    //?創(chuàng)建流對象,默認UTF8編碼
    InputStreamReader?isr?=?new?InputStreamReader(new?FileInputStream("D:/三國/utf8.txt"));
    //?創(chuàng)建流對象,指定GBK編碼
    InputStreamReader?isr2?=?new?InputStreamReader(new?FileInputStream("D:/三國/utf8.txt"),?"GBK");

    int?read;
    while?((read?=?isr.read())?!=?-1)?{
    ????System.out.println((char)?read);
    }

    while?((read?=?isr2.read())?!=?-1)?{
    ????System.out.println((char)?read);
    }

    //?輸出結(jié)果
    你好
    浣犲ソ

    OutputStreamWriter

    Writer的子類,使用指定的字符集將字符編碼為字節(jié)。字符集可以自己指定,也可以使用平臺的默認字符集。

    構(gòu)造方法如下

    //?使用平臺默認字符集
    public?OutputStreamWriter(OutputStream?out)?{}
    //?使用平臺默認字符集
    public?OutputStreamWriter(OutputStream?out,?String?charsetName)throws?UnsupportedEncodingException{}

    如下面的代碼,將你好寫入文件。寫入后兩個文件的字符集不一樣,文件大小也不同

    //?創(chuàng)建流對象,默認UTF8編碼
    OutputStreamWriter?osw?=?new?OutputStreamWriter(new?FileOutputStream("D:/三國/黃忠.txt"));
    osw.write("你好");?//?保存為6個字節(jié)

    //?創(chuàng)建流對象,指定GBK編碼
    OutputStreamWriter?osw2?=?new?OutputStreamWriter(new?FileOutputStream("D:/三國/馬超.txt"),"GBK");
    osw2.write("你好");//?保存為4個字節(jié)

    對象流

    序列化

    jdk提供了對象序列化的方式,該序列化機制將對象轉(zhuǎn)為二進制流,二進制流主要包括對象的數(shù)據(jù)、對象的類型、對象的屬性。可以將java對象轉(zhuǎn)為二進制流寫入文件中。文件會持久保存了對象的信息。

    同理,從文件中讀出對象的信息為反序列化的過程

    對象想序列化,滿足的條件:

  • 該類必須實現(xiàn) java.io.Serializable 接口, Serializable 是一個標記接口(沒有任何抽象方法),不實現(xiàn)此接口的類將不會使任何狀態(tài)序列化或反序列化,會拋出 NotSerializableException 。
  • 該類的所有屬性必須是可序列化的,如果有一個屬性不需要可序列化的,則該屬性使用transient 關(guān)鍵字修飾
  • ObjectOutputStream

    該類實現(xiàn)將對象序列化后寫出到外部設(shè)備,如硬盤文件

    public?ObjectOutputStream(OutputStream?out)?throws?IOException{}

    常用方法

    方法名方法說明
    void writeObject(Object obj) throws IOException將指定的對象寫出

    如下代碼,將User對象寫入文件中

    public?class?User?implements?Serializable?{
    ????private?static?final?long?serialVersionUID?=?8289102797441171947L;

    ????private?String?name;
    ????private?Integer?age;
    }
    //?下面是將對象輸出到文件的核心代碼
    User?user?=?new?User("馬超",20);
    //?創(chuàng)建序列化流對象
    ObjectOutputStream?out?=?new?ObjectOutputStream(new?FileOutputStream("D:/三國/馬超.txt"));
    //?寫出對象
    out.writeObject(user);

    注意:

  • 實現(xiàn)了Serializable的實體一定要加一個serialVersionUID變量,這也是習慣問題,idea可以設(shè)置一下。
  • serialVersionUID生成后不要改變,避免反序列化失敗,改變后會拋出InvalidClassException異常
  • 生成的文件內(nèi)容如下

    ObjectInputStream

    該類將ObjectOutputStream寫出的對象反序列化成java對象

    public?ObjectInputStream(InputStream?in)?throws?IOException?

    常用方法

    方法名方法說明
    Object readObject() ? ? throws IOException, ClassNotFoundException讀取對象
    ObjectInputStream?in?=?new?ObjectInputStream(new?FileInputStream("D:/三國/馬超.txt"));
    //?強轉(zhuǎn)為user
    User?user?=?(User)?in.readObject();
    System.out.println(user);
    //?輸出內(nèi)容
    User(name=馬超,?age=20)

    對象和字節(jié)數(shù)組的轉(zhuǎn)換

    利用對象流和字節(jié)數(shù)組流結(jié)合 ,可以實現(xiàn)java對象和byte[]之間的互轉(zhuǎn)

    //?將對象轉(zhuǎn)為byte[]
    public?static??byte[]?t1(T?t)?{
    ????ByteArrayOutputStream?bos?=?new?ByteArrayOutputStream();
    ????ObjectOutputStream?oos?=?new?ObjectOutputStream(bos);
    ????oos.writeObject(t);return?bos.toByteArray();
    }//?將byte[]轉(zhuǎn)為對象public?static??T?t2(byte[]?data)?throws?IOException,?ClassNotFoundException?{
    ????ByteArrayInputStream?bos?=?new?ByteArrayInputStream(data);
    ????ObjectInputStream?oos?=?new?ObjectInputStream(bos);return?(T)?oos.readObject();
    }

    管道流(了解)

    管道流主要用于兩個線程間的通信,即一個線程通過管道流給另一個線程發(fā)數(shù)據(jù)

    注意:線程的通信一般使用wait()/notify(),使用流也可以達到通信的效果,并且可以傳遞數(shù)據(jù)

    使用的類是如下

    • PipedInputStream和PipedOutStream
    • PipedReader和PipedWriter

    這里使用字節(jié)流為例

    class?Sender?implements?Runnable?{
    ????private?PipedOutputStream?pos;
    ????private?String?msg;

    ????public?Sender(String?msg)?{
    ????????this.pos?=?new?PipedOutputStream();
    ????????this.msg?=?msg;
    ????}

    ????@Override
    ????public?void?run()?{
    ?????? pos.write(msg.getBytes());
    ????}

    ????public?PipedOutputStream?getPos()?{
    ????????return?pos;
    ????}
    }

    class?Receiver?implements?Runnable?{
    ????private?PipedInputStream?pis;

    ????public?Receiver()?{
    ????????this.pis?=?new?PipedInputStream();
    ????}


    ????@Override
    ????public?void?run()?{
    ?????????byte[]?data?=?new?byte[1024];
    ????????????int?len;
    ????????????while?((len?=?pis.read(data))?!=?-1)?{
    ????????????????System.out.println(new?String(data,?0,?len));
    ????????????}
    ????}
    }
    ????
    Sender?sender?=?new?Sender("hello");
    Receiver?receiver?=?new?Receiver();
    receiver.getPis().connect(sender.getPos());
    new?Thread(sender).start();
    new?Thread(receiver).start();
    //?控制臺輸出??hello

    輸入與輸出流(了解)

    System.in和System.out代表了系統(tǒng)標準的輸入、輸出設(shè)備

    默認輸入設(shè)備是鍵盤,默認輸出設(shè)備是控制臺

    可以使用System類的setIn,setOut方法對默認設(shè)備進行改變

    我們開發(fā)中經(jīng)常使用的輸出到控制臺上的內(nèi)容的方法。

    System.out.println("a");
    System.out.print("b");
    class?System{
    ????public?final?static?InputStream?in?=?null;
    ????public?final?static?PrintStream?out?=?null;
    }
    public?PrintStream(String?fileName)?throws?FileNotFoundException{}

    數(shù)據(jù)流(了解)

    主要方便讀取Java基本類型以及String的數(shù)據(jù),有DataInputStream 和 DataOutputStream兩個實現(xiàn)類

    DataOutputStream?dos?=?new?DataOutputStream(new?FileOutputStream("D:/三國/周瑜.txt"));
    dos.writeUTF("周瑜");
    dos.writeBoolean(false);
    dos.writeLong(1234567890L);

    DataInputStream?dis?=?new?DataInputStream(new?FileInputStream("D:/三國/周瑜.txt"));
    String?s?=?dis.readUTF();
    System.out.println(s);
    boolean?b?=?dis.readBoolean();
    System.out.println(b);
    //?輸出
    周瑜
    false

    IO流總結(jié)

    以上各個章節(jié)詳細介紹了各個流,可見流的種類比較多,記憶確實增加了困難。但是可以通過思維導圖的方式整理出來,方便記憶。

    字節(jié)流的導圖

    字符流的導圖

    按照功能劃分

    輸入、輸出對應(yīng)關(guān)系

    結(jié)語

    短期的加更計劃

  • NIO
  • tomcat系列源碼解析
  • 文章篇幅較長,給看到這里的小伙伴點個大大的贊!由于作者水平有限,文章中難免會有錯誤之處,歡迎小伙伴們反饋指正。

    如果覺得文章對你有幫助,麻煩 點贊、評論、轉(zhuǎn)發(fā)、在看 、關(guān)注 走起

    你的支持是我加更最大的動力!!!

    瑣碎時間想看一些技術(shù)文章,可以去公眾號菜單欄翻一翻我分類好的內(nèi)容,應(yīng)該對部分童鞋有幫助。同時看的過程中發(fā)現(xiàn)問題歡迎留言指出,不勝感謝~。另外,有想多了解哪些方面內(nèi)容的可以留言(什么時候,哪篇文章下留言都行),附菜單欄截圖(PS:很多人不知道公眾號菜單欄是什么)

    END

    我知道你 “在看

    總結(jié)

    以上是生活随笔為你收集整理的javaio流_万字长文+思维导图帮你梳理 Java IO 流,还学不会你来打我(值得收藏)...的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。