Java輸入/輸出流基礎(IO流)

一、流的分類

按輸入輸出分類

  • 輸入流:將外設中的數據讀取到內存中。
  • 輸出流:將內存中的數據寫到外設中。

按字節,字符分類

  • 字節流:傳輸過程中,傳輸數據的最基本單位是字節的流。
  • 字符流:傳輸過程中,傳輸數據的最基本單位是字符的流。

二、字符流

由來

字節流讀取字節數據後,先不操作數據,而是去查找指定的編碼表,然後再對文字進行操作。簡單來說,字符流=字節流+編碼表。

操作文本的字符流

向文件中寫數據

FileWriter

主要構造方法:

  • new FileWriter(String path):如果此文件不存在,則會自動創建此文件;如果,此文件已經存在,則會覆蓋此文件;下同。
  • new FileWriter(String path,boolean append):如果append爲true,那麼表示可以追加數據(也就是多次使用write方法,不會把以前寫入文本的數據覆蓋掉,可以在文本後面追加數據),下同。
  • new FileWriter(File file)
  • new FileWriter(File file,boolean append)

實用方法:

  • write(String str):把字符串str寫入到緩衝區中
  • write(char []ch):把字符數組ch寫入到緩衝區中
  • write(char ch[],int start,int offset):把字符數組ch中從start開始的長度爲offset的字符寫入到緩衝區中
  • write(int c):把字符c寫入緩衝區中
  • flush():把緩衝區中的數據刷新到磁盤中
  • close():關閉流,關閉資源。在關閉之前要先刷新數據(close會自動調用flush方法)

換行:

  • 因爲在不同的操作系統中,換行符是不一樣的,所以需要適配操作系統。Java中已經爲我們解決了該問題。
  • 換行符的獲取:final String line=System.getProperty(“line.separator”);

IO異常的處理:

  • 標準寫法:
FileWriter fw = null;
        try {
            fw = new FileWriter("G:/xxx.txt", true);
            fw.write("窗外的麻雀,在電線杆上多嘴,你說這一句,很有夏天的感覺");
        } catch (IOException e) {
            System.out.println(e.getMessage());
        } finally {
            // 只有當fw不爲空的時候,才能關閉資源,否則會有空指針異常
            if (fw != null) {
                try {
                    fw.close();
                } catch (IOException e) {
                    throw new RuntimeException("老鐵,關閉資源失敗!");
                }
            }
        }

讀取文件中的數據

FileReader

構造方法:

  • new FileReader(String path)
  • new FileReader(File file)

實用方法:

  • int read():每次讀取一個字符,返回讀到的字符(int類型);如果返回的是-1,那麼就表示已經讀到了流的末尾。
  • int read(char ch[]):每次返回讀取到的字符數,即讀到了幾個字符

該方法原理圖:

圖1

  • int read(char ch[],int start,int len)
  • close():關閉資源。

小練習(複製並粘貼一個文本文件)

  • 思路:使用FileReader不斷地把文本從源文件中讀取出來,並把讀到的字符(或字符數組)用FileWriter寫入到另一個文件。
  • 注意點:複製的過程中可能會產生中文亂碼,這是因爲編碼方式不一致導致的:FileReader讀取字符以及FileWriter寫字符時的編碼方式是根據操作系統的編碼方式來的,而txt文本的編碼方式可能會與上述編碼方式不一致,所以會產生亂碼。
  • 文本文件複製原理圖:

圖2

  • 實現代碼:
public void test01() {
        FileReader fr = null;
        FileWriter fw = null;
        try {
            fr = new FileReader("G:\\系統屬性.txt");
            fw = new FileWriter("F:\\系統屬性.txt", true);
            char ch[] = new char[1024];
            int len = -1;
            while ((len = fr.read(ch)) != -1) {
                fw.write(ch, 0, len);
                fw.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fr != null) {
                try {
                    fr.close();
                } catch (IOException e) {
                    throw new RuntimeException("異常......");
                }
            }
            if (fw != null) {
                try {
                    fw.close();
                } catch (IOException e) {
                    throw new RuntimeException("異常......");
                }
            }
        }
    }

緩衝區

BufferedWriter

構造方法:

  • new BufferedWriter(Writer writer):創建BufferedWriter時必須把字符輸出流傳入,這相當於把資源傳入。緩衝區大小爲默認。
  • new BufferedWriter(Writer writer,int size):指定緩衝區大小爲size。

實用方法:

  • write(String str)
  • write(int c)
  • write(char ch[])
  • write(char ch[],int start,int len)
  • newLine():換行。
  • flush():刷新,最好每使用緩衝流寫一次數據就刷新一次。
  • close():關閉資源。注意: 關閉BufferedWriter後,傳入的字符輸出流也已經被關閉了

BufferedReader

構造方法:

  • new BufferedReader(Reader reader):創建BufferedReader,並把Reader傳入。緩衝區大小爲默認。
  • new BufferedReader(Reader reader,int size):指定緩衝區大小爲size。

實用方法:

  • int read()
  • int read(char ch[]):讀取字符,並存入字符數組,讀取規律同Reader。
  • int read(char ch[],int start,int len)。
  • String readlLine():讀取一行文本,返回讀取到的字符串,如果讀取到流的末尾,則返回空。

read()以及readLine()方法原理圖:

圖3

LineNumberReader

介紹:LineNumberReader是BufferedReader的子類,LineNumberReader記錄了當前行號,也可以獲取/設置行號。

構造方法:

  • new LineNumberReader(Reader reader)

實用方法:

  • String readLine()
  • read(char ch[])
  • read(char[],int start,int len)
  • int getLineNumber():獲取讀取的當前行(是第幾行)
  • void setLineNumber():行數默認是0,此時就從第一行開始讀取;如果自己設置行爲n,那麼就從第n+1行開始讀取。

自定義緩衝區

代碼如下:

//自定義BufferedReader
public class MyBufferedReader {
    private FileReader fr;
    private char ch[] = new char[10]; //緩衝區
    private int pos = -1; //緩衝區中指針的位置
    private int count = 0; //緩衝區中元素個數

    //構造方法
    public MyBufferedReader(FileReader fr) {
        this.fr = fr;
    }

    //自定義read方法
    //br中的read方法讀取數據時從緩衝區中讀取
    public int myRead() throws IOException {
        //緩衝區中元素個數爲0
        if (this.count == 0) {
            pos = -1; //角標賦值爲-1
            //從數據源中讀取數據存入緩衝區
            int len = fr.read(this.ch);
            if (len == -1)
                return -1;
            this.count = len;
            //從緩衝區中讀取一個字符
            --this.count; //緩衝區中的字符減少一個
            return (int) ch[++pos];
        } else {
            //緩衝區有數據
            --this.count;
            return (int) ch[++pos];
        }
    }

    //讀取一行文本
    public String myReadLine() throws IOException {
        StringBuffer sb = new StringBuffer();
        int len = 0;
        while ((len = this.myRead()) != -1) {
            char c = (char) len;
            if (c == '\r')
                continue; //遇到'\r'繼續讀取
            if (c == '\n')
                //直接就return,
                // 因爲如果跳出了循環,那麼就沒法分清是讀取到了流末尾出的循環,
                // 還是用break跳出的循環
                return sb.toString();
            sb.append(c);
        }
        return null; //返回null表示上面的循環結束,即爲讀到了流末尾
    }

    public void close() throws IOException {
        fr.close();
    }
}

裝幀設計模式

  • 介紹:裝幀設計模式是爲了使類的功能更加強大。
  • 實現原理:
    -請看如下代碼:
public class Person {

    public void eat() {
        System.out.println("恰飯!!!");
    }
}

public class NewPerson {
    private Person p;

    public NewPerson(Person p) {
        this.p = p;
    }

    public void eat() {
        System.out.println("飯前喝湯");
        p.eat();
        System.out.println("飯後甜點");
    }
}
  • 裝幀設計模式與繼承的區別:
    • 裝飾類比繼承更加精煉

三、字符流

FileInputStream

構造方法:

  • new FileInputStream(String path)
  • new FileInputStream(File file)

實用方法:

  • int read():讀取一個字節的數據。若返回-1表示讀取到了流的末尾。
  • int read(byte b[]):讀取數組b的長度的字節數據進入b數組中,返回讀取到的字節數,返回-1表示讀到了流的末尾。詳細的讀取數據到數組b中的過程同上,此處不再贅述。
  • int read(byte b[],int off,int len)
  • int available():返回文件中可以讀取的數據的估計值(返回字節大小);本方法可以在創建字節數組時定義數組的大小,不過,當文件很大的時候,不能用此方法。

FileOutputStream

構造方法:

  • new FileOutputStream(String path)
  • new FileOutputStream(File file)
  • new FileOutputStream(String path,boolean flag)
  • new FileOutputStream(File file,boolean flag)

實用方法

  • void write(byte b[])
  • void write(byte b[],int off,int len)
  • void write(int b):寫入一個字符(char),只不過是以int來表示。

BufferedInputStream

構造方法:

  • new BufferedInputStream(InputStream fis)
  • new BufferedInputStream(InputStream fis, int size)

實用方法:

  • int read()
  • int read(byte []b)
  • int read(byte []b,int off,int len)
  • close()

BufferedOutputStream

構造方法:

  • new BufferedOutputStream(OutputStream fos)
  • new BufferedOutputStream(OutputStream fos,int size)

實用方法:

  • void write(byte []b)
  • void write(byte []b,int off,int len)
  • void write(int ch)

四、轉換流

解釋

  • 看不懂:磁盤中的存儲的數據都是“看不懂”的,也就是以字節形式存儲的。
  • 看得懂:內存中存儲的數據都是“看得懂”的,也就是以字符形式存儲的。

InputStreamReader

  • new InputStreamReader(InputStream in):
  • new InputStreamReader(InputStream in,String charsetName):需要傳入一個InputStream對象,採用當前平臺(操作系統)的解碼方式來解碼。

OutputStreamWriter:使用指定的解碼方式來解碼。

  • 把“看得懂”的數據轉換成"看不懂"的數據,即把字符流編碼成字節流。
  • new OutputStreamWriter(OutputStream out):需要傳入一個OutputStream對象,使用當前平臺(操作系統)的編碼方式來編碼。
  • new OutputStreamWriter(OutputStream out,String charsetName):使用指定的編碼方式來編碼。

轉換流原理圖

以指定編碼方式來讀取某文本

遇到這種需求時,就需要用到轉換流,因爲只有轉換流才能指定編解碼格式。GBK把一個漢字解析成兩個字節,UTF-8把一個漢字解析成三個字節。所以,如果寫入文本用的編碼格式與讀取文本用的解碼格式不一樣的話,就會讀取到亂碼(也可能是問號?)。
代碼如下:

@Test
    //使用指定編碼方式寫入一個字符串到文本文件中
    public void test03() throws IOException {
        OutputStreamWriter outputStreamWriter =
                new OutputStreamWriter(new FileOutputStream("G:\\轉換流.txt"), "GBK");
        outputStreamWriter.write("你好,大兄弟!!");
        outputStreamWriter.flush();
    }

    @Test
    //使用指定的解碼格式讀取文本文件中的字符串
    public void test04() throws IOException {
        InputStreamReader inputStreamReader =
                new InputStreamReader(new FileInputStream("G:\\轉換流.txt"), "UTF-8");
        char[] ch = new char[10];
        int len = -1;
        while ((len = inputStreamReader.read(ch)) != -1) {
            System.out.println(new String(ch, 0, len));
        }
    }

五、File

構造方法:

  • new File(String path):根據路徑path把該文件(夾)封裝成一個對象。
  • new File(String parentPath,String childPath):根據兩個字符串的路徑,把該文件(夾)封裝成對象。這樣做的好處是可以靈活填寫路徑。
  • new File(File f,String path):根據文件對象f以及路徑path把對應的文件(夾)封裝成對象。

File裏面的字段(final類型):

  • 與系統有關的默認名稱分隔符
    • File.separator:String類型。Windows下是"",Linux下是"/"。
    • File.separatorChar:char類型。
  • 與系統有關的路徑分隔符
    • File.pathSeparator: 此字符用於分隔以路徑列表形式給定的文件序列中的文件名,在 UNIX 系統上此字段爲:,在 Microsoft Windows 系統上,它爲;。
    • File.pathSeparatorChar:char類型。

常用方法:

  • 獲取
    • 文件(夾)名字:String getName()
    • 文件(夾)路徑
      • 相對路徑:String getPath();是相對於項目所處的路徑,File構造方法裏面傳入什麼路徑,getPath就獲取什麼路徑。因爲路徑裏面不寫磁盤的話,那就一定是相對路徑。寫了磁盤就是絕對路徑。
      • 絕對路徑:String getAbsolutePath()
    • 文件(夾)大小:long length();返回字節大小。
    • 文件(夾)最後修改時間:long lastModified(),返回距離1970年1月1日的毫秒數。
    • 系統目錄及其容量
      • static File [] listRoots():獲取系統目錄(可以理解爲磁盤目錄),返回一個File數組。
      • long getFreeSpace():獲取該目錄(磁盤)的可用空間,返回字節數(long類型)。
      • long getTotalSpace():獲取該目錄(磁盤)的總空間,返回字節數(long類型)。
      • long getUsableSpace():獲取該目錄(磁盤)可以被虛擬機使用的空間,返回字節數(long類型)。
    • 當前文件夾裏面的文件或文件夾名字
      • String [] list()
      • String [] list(FilenameFilter filter):傳入一個過濾器(通過文件名字來過濾),過濾器的解釋請看下文。
      • 注意:File封裝的必須是一個文件夾(目錄),如果封裝的是一個文件,則會拋空指針異常。
    • 當前文件夾裏面的文件(夾)
      • File [] listFiles():獲取某個文件夾裏面的文件(夾),返回File數組,可以用來操作文件,而不僅僅是獲取文件名字。
      • File [] listFiles(FilenameFilter filter):傳入一個過濾器(通過文件名字來過濾)。
      • File [] listFiles(FileFilter filter):傳入一個過濾器(通過文件來過濾)。
    • 過濾器解釋
      • 過濾器分爲兩種,一種是FilenameFilter(通過文件名字來過濾,比如可以過濾掉非.txt後綴的文件),另一種是FileFilter(通過文件來過濾,比如可以過濾掉隱藏文件)。
      • FilenameFilter:FilenameFilter是一個接口,它裏面有一個抽象方法boolean accept(File f,String name),文件的過濾就是通過該方法來過濾的。自定義過濾器實現該接口後,實現accept()方法,如果該方法返回true,那麼就留下當前遍歷到的文件,否則就不留下。
      • FileFilter:FileFilter過濾器的原理與FilenameFilter的原理是一樣的,只不過該接口裏面的抽象方法是boolean accept(File f),方法參數裏面只有一個File對象,並沒有文件名。因爲該過濾器是通過File來過濾的,與文件名無關。
      • 過濾器原理圖
  • 創建與刪除
    • 文件
      • 創建:boolean createNewFile();根據文件的路徑創建一個文件,如果該文件存在,則不創建並返回false;否則創建並返回true。下同,自己體會。(與IO流那邊是不同的,那邊每一次都是直接覆蓋)。
      • 刪除:boolean delete();刪除該文件。刪除成功返回true,刪除失敗返回false。下同,自己體會。如果某個文件裏面有內容,比如File f=new File(“G:/a”),而a文件裏面有其他的文件或文件夾,那麼f.delete()就會失敗。Windows刪除文件是從裏面往外面刪的。
    • 文件
      • 創建:
        • boolean mkdir():創建單個文件夾,例如只創建一個a文件夾。
        • boolean mkdirs():創建多層文件夾,例如創建a/b/c/d/e/f。
      • 刪除:boolean delete();刪除該文件。如果File f=new File(“G:/a/b/c/d/e/f”),那麼此時定位的文件夾是f,故f.delete()刪除的是f文件夾,其餘文件夾不會被刪除。
  • 判斷
    • boolean exists():判斷文件(夾)是否存在,存在則返回true,否則返回false。
    • boolean isFile():判斷是否爲文件。
    • boolean isHidden():判斷文件(夾)是否爲隱藏文件。
    • boolean isDirectory():判斷是否爲文件夾。
    • boolean canRead():判斷該文件(夾)是否可讀。
    • boolean canWrite():判斷該文件(夾)是否可寫。
    • boolean canExecute():判斷該文件(夾)是否可運行。
  • 重命名
    • boolean renameTo(File des):如果源文件src和目標文件des在同一個磁盤下,那麼src.renameTo(des)則會以des的名字來重命名src。如果src和des不在同一個磁盤裏,那麼src.renameTo(des)則會把src文件剪切到des所在的磁盤,並以des的名字重命名src。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章