日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

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

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

?前言

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

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

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

IO的難點在于

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

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

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

    File

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

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

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

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

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

    絕對路徑和相對路徑

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

    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的常用方法

    創建、刪除

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

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

    • 創建多級目錄時,mkdir創建失敗,返回false,mkdirs創建成功,返回true(推薦使用mkdirs)
    • 刪除目錄時,目錄內不為空時,刪除失敗,返回false, 即只能刪除文件或者空目錄
    File?shuiHu?=?new?File("D:/四大名著/水滸傳");
    //?返回false?創建失敗
    boolean?mkdir?=?shuiHu.mkdir();
    //?返回true?創建失敗
    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()文件是否可執行
    long lastModified()返回文件的上次修改時間

    注意的是

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

    文件獲取

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

    注意

    • length() 返回的是文件的字節數,目錄的 長度是0
    • getPath()在用絕對路徑表示的文件時相同,用相對路徑表示的文件時不同
    • listFiles和list方法的調用,必須是實際存在的目錄,否則返回null
    • listFiles和list 可以傳入FilenameFilter的實現類,用于按照文件名稱過濾文件
    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?{
    ???//?參數為目錄和指定過濾名稱
    ????boolean?accept(File?dir,?String?name);
    }

    擴展(由讀者自己實現)

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

    IO流

    上一章節學習了使用File類創建、查找、刪除文件,但是無法讀取、傳輸文件中的內容。

    IO流主要是讀取、傳輸、寫入數據內容的。

    I: input,O:output

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

    IO的分類

    • 本地IO和網絡IO

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

      網絡IO主要是通過網絡發送數據,或者通過網絡上傳、下載文件,我們每天上網無時無刻不在體驗著IO的傳輸

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

    • 按數據類型分:字節流和字符流

    • 按功能分:節點流和處理流

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

    IO流主要的入口是數據源,下面列舉常見的源設備和目的設備

    源設備

  • 硬盤(文件)
  • 內存(字節數組、字符串等)
  • 網絡(Socket)
  • 鍵盤(System.in)
  • 目的設備

  • 硬盤(文件)
  • 內存(字節數組、字符串等)
  • 網絡(Socket)
  • 控制臺(System.out)
  • 本文先探討本地IO的字節流和字符流,先列舉字節流和字符流的公共方法

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

    注意:

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

    字節流

    一切皆為字節

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

    InputStream是字節輸入流的頂層抽象

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

    核心方法如下

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

    OutputStream是字節輸出流的頂層抽象

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

    核心方法如下

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

    文件節點流

    InputStream有很多的實現類,先介紹下文件節點流,即目標設備是文件,輸入流和輸出流對應的是

    FileInputStream和FileOutputStream

    FileInputStream主要從磁盤文件中讀取數據,常用構造方法如下

    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);
    }

    //?輸出結果
    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));
    }

    //?輸出結果
    ab
    cd
    ed

    上述代碼由于最后一次讀取時,只讀取一個字節 e ,數組中還是上次的數據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?為每次讀取的有效的字節個數
    ????System.out.println(new?String(data,?0,?len));
    }

    //?輸出結果
    ab
    cd
    e

    注意:使用數組讀取,每次讀取多個字節,減少了系統間的IO操作次數,從而提高了效率,建議使用

    源碼解析

    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[])的源碼,可見都是調用native的方法,涉及底層的系統調用。

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

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

    FileOutputStream主要是向磁盤文件中寫出數據,常用構造方法如下

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

    注意:

    • 上述構造方法執行后,如果file不存在,會自動創建該文件

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

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

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

    向文件寫數據

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

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

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

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

    應用場景

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

    擴展(由讀者自己實現)

    利用文件節點流實現文件的復制

    內存節點流

    ByteArrayInputStream是從內存的字節數組中讀取數據

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

    注意:不需要close數據源和拋出IOException,因為不涉及底層的系統調用

    ByteArrayOutputStream是向內存字節數組中寫數據,內部維護了一個數組

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

    內存節點流代碼示例

    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()));

    應用場景

  • 內存操作流一般在一些生成臨時信息時會被使用,如果臨時信息保存著文件中,代碼執行完還要刪除文件比較麻煩
  • 結合對象流,可以實現對象和字節數組的互轉
  • 字符流

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

    Reader用于讀取文本字符

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

    核心方法

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

    Writer用于寫出文本字符

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

    核心方法

    方法名方法說明
    void write(int c) throws IOException寫入單個字符到輸出流中
    void write(char[] cbuf) throws IOException寫入字符數組到輸出流中
    void write(char[] cbuf, int off, int len) ?throws IOException寫入字符數組的一部分,偏移量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追加字符到輸出流中

    文件節點流

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

    FileReader主要是向磁盤文件中寫出數據,常用構造方法如下

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

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

  • read()循環讀取文件
  • 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構造方法如下,和FileOutStream構造方法類似,和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?{}

    常用的寫數據進文件的方法

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

    注意:

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

    應用場景

    純文本文件的io操作,配合處理流一起實現。

    內存節點流

    字符流也有對應的內存節點流,常用的有StringWriter和CharArrayWriter

    StringWriter是向內部的StringBuffer對象寫數據。

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

    ????private?StringBuffer?buf;

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

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

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

    CharArrayWriter是向內部的char數組寫數據

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

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

    四種常用節點流的使用總結

    字節流和字符流的共同點

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

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

    close(): 強制刷新緩沖區后關閉資源,關閉后流對象不可以繼續使用

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

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

    處理流

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

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

    字節流的處理流的基類是FilterInputStreamFilterOutputStream

    緩沖流(重點)

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

    分類

    字節緩沖流:BufferedInputStream , BufferedOutputStream

    字符緩沖流:BufferedReader , BufferedWriter

    字節緩沖流

    可見構造方法傳入的是節點流,是對節點流的裝飾

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

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

  • 使用基本的流讀取數據(一次傳輸一個字節)
  • 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);
    //?五分鐘還沒拷好,關閉程序了...
  • 使用基本的流讀取數據(一次傳輸一個8M的字節數組)
  • 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
  • 使用緩沖流讀取數據(一次傳輸一個字節)
  • 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
  • 使用緩沖流讀取數據(一次傳輸一個8M的字節數組)(最常使用)
  • 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

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

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

    字符緩沖流

    對字符節點流的裝飾,下面是字符緩沖流的構造方法

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

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

    字符緩沖流的特有方法

    類方法名方法說明
    BufferedReaderString readLine() throws IOException一行行讀取,讀取到最后一行返回null
    BufferedWritervoid newLine() throws IOException寫一個換行符到文件中,實現換行
    //?創建流對象
    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();
    }
    //?結果
    我乃常山趙子龍
    于萬軍從中,取上將首級

    緩沖流的正確姿勢

    緩沖流是IO流中最重要的知識點,下面通過代碼實現正確用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的文件,實際測試這里傳輸的大小并不影響傳輸的速度
    ????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塊中關閉流,確保資源一定被關閉
    ????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);
    ????????}
    ????}
    }

    轉換流

    字符編碼與字符集

    字符編碼

    計算機存儲的數據都是二進制的,而我們在電腦上看到的數字、英文、漢字等都是二進制轉換的結果

    • 將字符轉換成二進制,為編碼

    • 將二進制轉換為字符,為解碼

      字符編碼 就是 自然語言和二進制的對應規則

    字符集

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

    IDEA中,使用 FileReader 讀取項目中的文本文件。IDEA可以設置為GBK 編碼,當讀取Windows系統中創建的默認的UTF8文本文件時,就會出現亂碼 。

    如下例

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

    運行的代碼及結果

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

    InputStreamReader

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

    構造方法如下

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

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

    //?創建流對象,默認UTF8編碼
    InputStreamReader?isr?=?new?InputStreamReader(new?FileInputStream("D:/三國/utf8.txt"));
    //?創建流對象,指定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);
    }

    //?輸出結果
    你好
    浣犲ソ

    OutputStreamWriter

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

    構造方法如下

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

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

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

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

    對象流

    序列化

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

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

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

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

    該類實現將對象序列化后寫出到外部設備,如硬盤文件

    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);
    //?創建序列化流對象
    ObjectOutputStream?out?=?new?ObjectOutputStream(new?FileOutputStream("D:/三國/馬超.txt"));
    //?寫出對象
    out.writeObject(user);

    注意:

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

    ObjectInputStream

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

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

    常用方法

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

    對象和字節數組的轉換

    利用對象流和字節數組流結合 ,可以實現java對象和byte[]之間的互轉

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

    管道流(了解)

    管道流主要用于兩個線程間的通信,即一個線程通過管道流給另一個線程發數據

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

    使用的類是如下

    • PipedInputStream和PipedOutStream
    • PipedReader和PipedWriter

    這里使用字節流為例

    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代表了系統標準的輸入、輸出設備

    默認輸入設備是鍵盤,默認輸出設備是控制臺

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

    我們開發中經常使用的輸出到控制臺上的內容的方法。

    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{}

    數據流(了解)

    主要方便讀取Java基本類型以及String的數據,有DataInputStream 和 DataOutputStream兩個實現類

    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流總結

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

    字節流的導圖

    字符流的導圖

    按照功能劃分

    輸入、輸出對應關系

    結語

    短期的加更計劃

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

    如果覺得文章對你有幫助,麻煩 點贊、評論、轉發、在看 、關注 走起

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

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

    END

    我知道你 “在看

    總結

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

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。