Java基礎—IO流(二)

IO流(二)

字節流

這裏寫圖片描述

一、OutputStream類

    1.概述
      OutputStream類可以在硬盤上創建一個文件,並寫入或添加數據。該類的子類還能實現寫入過程中的不同功能。

    2.FileOutputStream類
      FileOutputStream類用於在硬盤上創建文件,並以字節的形式寫入數據。其使用方式和FileWriter類相似,只是以字節的形式操作數據。下面的代碼在指定目錄創建一個文件並寫入自定義數據。

示例代碼:

package com.heisejiuhuche.io;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class FileOutputStreamDemo {
    public static void main(String[] args) {
        FileOutputStream fos = null;
        try {
            /* 創建FileOutputStream對象並關聯文件 */
            fos = new FileOutputStream("C:/Users/jeremy/Documents/javaTmp/fos.txt");

            /* 寫入數據,通過String類的getBytes方法將字符串轉換爲字節數組 */
            fos.write("abcdefg".getBytes());
        } catch(FileNotFoundException e) {
            throw new RuntimeException("文件不存在");
        } catch(IOException e) {
            throw new RuntimeException("操作失敗");
        } finally {
            try {
                if(fos != null)
                    fos.close();
            } catch(IOException e) {
                throw new RuntimeException("輸出流關閉失敗");
            }
        }
    }
}

該程序在指定目錄創建fos.txt並寫入abcdefg

注意:
字節輸出流在直接使用的時候不需要調用flush()方法,與字符流不用。由於字符流底層操作的也是字節,同時用的是字節流的緩衝區;該緩衝區中有個數組,用於臨時存儲數據。要將數組中的數據寫入目的地文件,字符流就需要調用flush()方法進行刷新。而字節輸出流是對最小單位字節直接進行操作,沒有使用具體緩衝區,所以不需要刷新,直接往目的地文件寫入數據。

二、InputStream類

    1.概述
      InputStream類以字節形式讀取硬盤上的文件數據。該類的子類還能實現讀取過程中的不同功能。

    2.FileInputStream類
      FileInputStream類用於以字節形式讀取文件數據。其特有的方法使創建字節數組有了明確的大小。該類的使用方式和FileReader相似。下面的代碼讀取文件中的數據。

示例代碼:

package com.heisejiuhuche.io;

import java.io.FileInputStream;
import java.io.IOException;

public class FileInputStreamDemo {
    public static void main(String[] args) throws IOException {
        read_1();
        System.out.println();
        read_2();
        read_3();

    }

    private static void read_3() throws IOException {
        FileInputStream fis = new FileInputStream("C:/Users/jeremy/Documents/javaTmp/fos.txt");

        /* available方法返回文件的大小 */
        byte[] buf = new byte[fis.available()];

        fis.read(buf);

        System.out.println("read_3: " + new String(buf));
    }

    private static void read_2() throws IOException {
        FileInputStream fis = new FileInputStream("C:/Users/jeremy/Documents/javaTmp/fos.txt");

        byte[] buf = new byte[1024];

        int len = 0;

        while((len = fis.read(buf)) != -1) {
            System.out.println("read_2: " + new String(buf, 0, len));
        }
    }

    private static void read_1() throws IOException {
        FileInputStream fis = new FileInputStream("C:/Users/jeremy/Documents/javaTmp/fos.txt");

        int ch = 0;
        System.out.print("read_1: ");
        while((ch = fis.read()) != -1) {
            System.out.print((char)ch);
        }
    }
}

程序輸出結果:

read_1: abcdefg
read_2: abcdefg
read_3: abcdefg

注意:
如果使用字節輸入流讀取的文件較大,建議使用1024字節整數倍方法讀入數據,而不要使用創建available()方法返回值大小的數組;後者可能造成內存溢出

三、字節流練習

    1.拷貝圖片到指定目錄

示例代碼:

package com.heisejiuhuche.io;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class PicCopy {
    public static void main(String[] args) {
        picCopy();
    }

    private static void picCopy() {
        FileInputStream fis = null;
        FileOutputStream fos = null;

        try {
            /* 創建輸入輸出流並關聯文件 */
            fos = new FileOutputStream("C:/Users/jeremy/Documents/me.jpg");
            fis = new FileInputStream("C:/Users/jeremy/Documents/javaTmp/me.jpg");
            /* 創建緩衝區 */
            byte[] buf = new byte[1024];
            int len = 0;
            /* 複製文件 */
            while((len = fis.read(buf)) != -1) {
                fos.write(buf, 0, len);
            }
        } catch(FileNotFoundException e) {
            throw new RuntimeException("文件不存在");
        } catch(IOException e) {
            throw new RuntimeException("複製失敗");
        } finally {
            try {
                if(fis != null)
                    fis.close();
            } catch(IOException e) {
                throw new RuntimeException("輸入流關閉失敗");
            }
            try {
                if(fos != null)
                    fos.close();
            } catch(IOException e) {
                throw new RuntimeException("輸出流關閉失敗");
            }
        }
    }
}

四、字節流緩衝區

    1.緩衝區對應的類
      字節流緩衝區對應BufferedOuputStream類和BufferedInputStream類。

    2.緩衝區應用
      1)用緩衝區拷貝一個Mp3文件

示例代碼:

package com.heisejiuhuche.io;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class CopyMp3Demo {
    public static void main(String[] args) throws IOException {
        long start = System.currentTimeMillis();
        copy();
        long end = System.currentTimeMillis();
        System.out.println((end - start) + "毫秒");
    }

    private static void copy() throws IOException {
        /* 創建Buffered緩衝區對象並關聯文件 */
        BufferedOutputStream bufos = new BufferedOutputStream(new FileOutputStream(
                "C:/Users/jeremy/Documents/javaTmp/back.mp3"));
        BufferedInputStream bufis = new BufferedInputStream(new FileInputStream(
                "C:\\Users\\jeremy\\Music\\BaiduMusic\\Songs\\Backseat Serenade - All Time Low,Cassadee Pope.mp3"));

        int ch = 0;
        /* 複製文件 */
        while((ch = bufis.read()) != -1) {
            bufos.write(ch);
        }
    }
}

程序輸出結果:136毫秒

      2)自定義緩衝區

這裏寫圖片描述

假設內存中字節數組的大小定義爲1024字節;那麼字節流緩衝區在工作時,首先由FileInputStream從硬盤抓取1024字節的數據存入字節數組,然後由BufferedInputStreamread()方法依次一個字節一個字節讀取。緩衝區中有兩個控制讀取過程的變量,分別是數組的索引指針,和一個計數器。下標用於控制不斷讀取下一個字節,計數器用於控制下一次從硬盤抓數據存入緩衝區數組的時間。read()方法每讀取一個字節,指針右移一位,計數器自減1;當計數器減至0的時候,意味着數組中已經沒有字節可讀,這時再由FileInputStream從硬盤抓取1024個字節存入數組,指針歸零,計數器回到1024,再次進行以上步驟的循環,直至硬盤數據全部被抓取。

要自定義緩衝區,需要定義一個字節數組,兩個變量(指針和計數器)。

示例代碼:

package com.heisejiuhuche.io;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class MyBufferedInputStreamDemo {
    public static void main(String[] args) throws IOException {
        long start = System.currentTimeMillis();
        copy();
        long end = System.currentTimeMillis();
        System.out.println((end - start) + "毫秒");
    }

    private static void copy() throws IOException {
        /* 創建Buffered緩衝區對象並關聯文件 */
        BufferedOutputStream bufos = new BufferedOutputStream(new FileOutputStream(
                "C:/Users/jeremy/Documents/javaTmp/back.mp3"));
        MyBufferedInputStream bufis = new MyBufferedInputStream(new FileInputStream(
                "C:\\Users\\jeremy\\Music\\BaiduMusic\\Songs\\Backseat Serenade - All Time Low,Cassadee Pope.mp3"));

        int ch = 0;
        /* 複製文件 */
        while((ch = bufis.read()) != -1) {
            bufos.write(ch);
        }

        bufos.close();
        bufis.close();
    }
}

class MyBufferedInputStream {
    private FileInputStream fis;
    private int pos;
    private int count;
    private byte ch;
    private byte[] buf = new byte[1024];

    MyBufferedInputStream(FileInputStream fis) {
        this.fis = fis;
    }

    public int read() throws IOException {
        /* 如果count=0了,繼續抓取數據到緩衝區數組 */
        if(count == 0) {
            /* 抓取數據後,count回到1024 */
            count = fis.read(buf);
            /* 讀到文件末尾,fis的read()方法返回-1,必須判斷如果count < 0,說明讀到了最後 */
            if(count < 0) {
                return -1;
            }
            /* 抓取數據後指針歸0 */
            pos = 0;
            /* 取出第一個字節 */
            ch = buf[pos];
            /* 每取一個數據,指針+1,右移 */
            pos++;
            /* 每取一個數據,count-1 */
            count--;
            /* 返回第一個字節 */
            return ch;
        } else if(count > 0) {
            ch = buf[pos];
            pos++;
            count--;
            /* 返回第一個字節後的每一個字節 */
            return ch;
        }
        /* 讀完文件,返回-1 */
        return -1;
    }

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

問題:
上面的代碼運行結果只拷貝了8K到目的文件。

原因:
是因爲媒體文件在硬盤上的數據以二進制形式存在;read()方法在讀取第一個字節的時候,有可能會讀到:11111111;這樣8個1的情況;而8個1的的二進制就是十進制的-1;程序中while循環的跳進等於-1時,循環停止;因此只複製了8K大小。

理解BufferedInputStream的read()方法:
細看BufferedInputStream類的read()方法,其返回的是int類型。而方法中讀到的字節都是byte類型;這樣做的原因,就是爲了解決讀取字節讀到8個1的情況。read()方法在返回byte類型字節數據的時候,將byte類型提升爲int類型,存儲位數由1個8位,變爲4個8位。爲了確保返回的數據與原數據相同而不產生-1的情況,read()方法在類型提升之後,補了3個8位的0在原byte數據前面。過程如下圖:

這裏寫圖片描述

在自定義緩衝區中,雖然方法返回了int類型,進行了數據類型提升,但是沒有進行補0的操作,意味着當讀到8個1組成的byte數據時,返回了一個由32個1組成的int類型數據,結果還是-1。那麼,如果要完成相同功能,只需取32個1的最後8位即可。取最後8位,將原數據與上255

這裏寫圖片描述

將每個return ch;語句改爲return ch & 255;即可。

五、鍵盤錄入

    1.System標準輸出輸入
      System類中對應的成員outin分別是:
        System.out-標準輸出流
        System.in-標準輸入流

      System.in用於讀取鍵盤錄入。

    2.接收鍵盤錄入
      從鍵盤接收輸入,並打印在控制檯。

示例代碼:

package com.heisejiuhuche.io;

import java.io.IOException;
import java.io.InputStream;

public class SysteminDemo {
    public static void main(String[] args) {
        /* 創建標準輸入對象 */
        InputStream in = System.in;
        try {
            /* 錄入一個字符 */
            int ch = in.read();
            /* 打印該字符的ASCII碼 */
            System.out.println(in.read());
        } catch(IOException e) {
            throw new RuntimeException("運行出錯~");
        }
    }
}

程序輸出結果:

a
97

    3.練習
      接收鍵盤錄入,當回車時打印整行內容;當輸入over回車時,結束輸入。

示例代碼:

package com.heisejiuhuche.io;

import java.io.IOException;
import java.io.InputStream;

public class SysteminDemo {
    public static void main(String[] args) {
        InputStream in = System.in;
        StringBuilder sb = new StringBuilder();
        int ch = 0;
        try {
            /* 一直讀入字符,每輸入一個字符,就往StringBuilder添加一個;
             * 如果讀到回車符,繼續讀入;
             * 如果讀到換行符,將StringBuilder中的字符組成字符串
             * 判斷該字符串是否等於over,如果是,結束程序;
             * 如果不是,就打印該字符串,並將StringBuilder容器清空
             */
            while(true) {
                ch = in.read();
                if(ch == 13)
                    continue;
                if(ch == 10) {
                    String str = sb.toString();
                    if(str.equals("over")) {
                        break;
                    }
                    System.out.println(str);
                    sb.delete(0, sb.length());
                } else 
                    sb.append((char)ch);
            }
        } catch(IOException e) {
            throw new RuntimeException("運行出錯~");
        }
    }
}

六、轉換流

    1.InputStreamReader類
      InputStreamReader類用於將字節流轉換爲字符流。該類可以將讀取到的字節數據轉換爲字符數據。其使用的編碼表可以由開發者指定,也可以使用系統默認。利用轉換流將字節流轉換爲字符流,就意味着該字節流可以使用字符流的緩衝區技術,調用其readLine()方法,使鍵盤錄入的讀取過程更加高效便捷。

利用轉換流修改鍵盤錄入並打印的代碼:

package com.heisejiuhuche.io;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

public class InputStreamReaderDemo {
    public static void main(String[] args) {
        /* 創建標準輸入對象 */
        InputStream in = System.in;

        /* 利用轉換流將字節流轉換爲字符流 */
        InputStreamReader isr = new InputStreamReader(in);

        /* 轉換後的字節流,就可以像字符流一樣使用字符流的緩衝技術 */
        BufferedReader bufr = new BufferedReader(isr);

        /* 調用BufferedReader的readLine()方法整行獲取鍵盤錄入 */
        String line = null;
        try {
            while((line = bufr.readLine()) != null) {
                /* 如果輸入over 結束鍵盤錄入 */
                if(line.equals("over"))
                    break;
                System.out.println(line.toUpperCase());
            }
        } catch(IOException e) {
            throw new RuntimeException("Exception occured...");
        }
    }
}

將字節流轉換爲字符流,相當於在字節流上套了兩根管子;一根使字節流變成字符流;另一根使字節流可以使用字符流的緩衝技術。

    2.OutputStreamWriter類
      OutputStreamWriter類用於將字符流轉換爲字節流。該類的編碼表同樣可以指定或使用系統默認。

用OutputStreamWriter類修改上面的代碼:

package com.heisejiuhuche.io;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;

public class InputStreamReaderDemo {
    public static void main(String[] args) {
        InputStream in = System.in;
        InputStreamReader isr = new InputStreamReader(in);
        BufferedReader bufr = new BufferedReader(isr);

        /* 創建標準輸出對象 */
        OutputStream out = System.out;

        /* 用轉換流將字符流轉換爲字節流 */
        OutputStreamWriter osw = new OutputStreamWriter(out);

        /* 使用緩衝字符輸出流增強字符輸出流 */
        BufferedWriter bufw = new BufferedWriter(osw);

        String line = null;
        try {
            while((line = bufr.readLine()) != null) {
                /* 如果輸入over 結束鍵盤錄入 */
                if(line.equals("over"))
                    break;
                bufw.write(line.toUpperCase());
                bufw.newLine();
                bufw.flush();
            }
        } catch(IOException e) {
            throw new RuntimeException("Exception occured...");
        } finally {
            try {
                if(bufw != null)
                    bufw.close();
            } catch(IOException e) {
                throw new RuntimeException("資源關閉失敗");
            }
        }
    }
}

可以將字節流轉字符流的三個步驟簡化爲一行代碼:

BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));

    3.轉換流的作用
      轉換流可以指定讀寫時使用的字符編碼集;如不指定,將使用系統默認的編碼集,本機默認使用GBK。下面的代碼演示了用UTF-8寫入,用GBK讀取會發生亂碼的情況。

示例代碼:

package com.heisejiuhuche.io;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;

public class CharSetDemo {
    public static void main(String[] args) {
        write();
    }

    private static void read() {
        BufferedReader bufr = null;

        try {
            /* 使用默認GBK字符集讀取數據,結果會出現亂碼 */
            bufr = new BufferedReader(new FileReader("C:/Users/jeremy/Documents/javaTmp/demo.txt"));
            System.out.println(bufr.readLine());
        } catch(FileNotFoundException e) {
            e.printStackTrace();
        } catch(IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if(bufr != null)
                    bufr.close();
            } catch(IOException e) {
                e.printStackTrace();
            }
        }
    }

    private static void write() {
        InputStreamReader isr = null;
        OutputStreamWriter osw = null;
        BufferedReader bufr = null;
        BufferedWriter bufw = null;
        String line = null;

        try {
            bufr = new BufferedReader(new InputStreamReader(System.in));
            /* 指定使用UTF-8字符集寫入數據 */
            bufw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(
                    "C:/Users/jeremy/Documents/javaTmp/demo.txt"),"UTF-8"));

            while((line = bufr.readLine()) != null) {
                if(line.equals("over")) {
                    break;
                }
                bufw.write(line);
                bufw.newLine();
                bufw.flush();
            }
            /* 將寫入的數據讀取打印在控制檯 */
            read();

        } catch(FileNotFoundException e) {
            e.printStackTrace();
        } catch(IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if(bufr != null)
                    bufr.close();
            } catch(IOException e) {
                e.printStackTrace();
            }
            try {
                if(bufw != null)
                    bufw.close();
            } catch(IOException e) {
                e.printStackTrace();
            }
        }

    }
}

程序輸出結果:

你好,這裏是虹橋鎮~
over
浣犲ソ錛岃繖閲屾槸鉶規ˉ闀噡

爲了正常顯示,用InputStreamReader指定讀取時編碼集爲UTF-8即可

bufr = new BufferedReader(new InputStreamReader(new FileInputStream(
                    "C:/Users/jeremy/Documents/javaTmp/demo.txt"),"UTF-8"));

程序輸出結果:

你好,這裏是虹橋鎮~
over
你好,這裏是虹橋鎮~

小擴展
使用System類的方法setIn()和setOut()改變標準輸入輸出。

示例代碼:

package com.heisejiuhuche.io;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;

public class SystemSetDemo {
    public static void main(String[] args) throws IOException {
        BufferedReader bufr = null;
        BufferedWriter bufw = null;

        /* 改變標準輸入源,從鍵盤變爲指定目錄的文件 */
        System.setIn(new FileInputStream("C:/Users/jeremy/Documents/javaTmp/fos.txt"));

        /* 改變標準輸出目的地,從控制檯變爲指定目錄的文件 */
        System.setOut(new PrintStream("C:/Users/jeremy/Documents/javaTmp/fos1.txt"));

        bufr = new BufferedReader(new InputStreamReader(System.in));
        bufw = new BufferedWriter(new OutputStreamWriter(System.out));
        String line = bufr.readLine();
        bufw.write(line);
        bufw.flush();
        bufr.close();
        bufw.close();
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章