Java I/O流總結(一)

/**
@author StormWangxhu
@date    2017/10/31
*/

面對壓力,我可以挑燈夜戰,不眠不休。面對挑戰,我願意迎難而上,永不退縮!

在學習的這段時間裏,基礎知識學了很久,但卻總是記不住。在前兩天,自己找了一個qq項目,在這個小項目中,關於讀寫數據應用很頻繁。也就是I/O流的應用。今天想進行一下流的總結。

在Java中,輸入和輸出多以流的方式進行,他的特點是數據的發送和獲取都是沿數據序列進行的,每個數據必須等待它前面的數據發送或讀入後才能被讀寫。
數據在兩設備間的傳輸稱爲流,流的本質是數據傳輸,根據數據傳輸特性將流抽象爲各種類,方便更直觀的進行數據操作。

流的分類

  • 根據輸入輸出的數據類型可分爲: 字節流、字符流
  • 根據流的流向可分爲: 輸入流、輸出流
  • 根據角色來分:節點流和處理流

字符流和字節流

1、讀寫單位不同:字節流操作的數據單元一次讀取或寫入8位。字符流以字符爲單位,操作的數據單元是16位的字符,根據碼錶映射字符。
2、處理對象不同:字節流能夠處理所有的對象,而字符流只能處理字符。
3、字節流主要InputStream和OutputStream作爲基類,而字符流主要由Reader和Writer作爲基類


輸入流和輸出流

輸入流: 只能從中讀取數據,不能寫入數據。
輸出流: 只能從中寫入數據,不能讀取數據。

注意:此處的輸入、輸出涉及一個方向問題,要更具具體的問題需求確定輸入和輸出流的方向問題。

節點流(低級流)和處理流(包裝流)

1、節點流: 可以從一個特定的IO設備(如磁盤、網絡)讀、寫數據的流,稱爲節點流。節點流也被稱爲低級流(Low Level Stream)。

特點: 程序直接連接到實際的數據源。

2、處理流:對一個已存在的節點流進行連接或封裝,通過封裝後的流試現數據讀寫功能。處理流也被稱爲高級流。

(1)特點:實際上,Java使用處理流來包裝節點流,是一種典型的裝飾設計模式,通過使用處理流來包裝不同的節點流,既可以消除不同節點流的實現差異,也可以提供更方便的方法來完成輸入/輸出功能。因此,處理流也被稱爲包裝流。處理流可以“嫁接”到任何已存在的流的基礎之上。通過使用處理流,Java程序無須理會輸入/輸出設備,程序只要將節點流包裝成處理流,就可以用形同的輸入/輸出操作代碼來完成不同的輸入/輸出設備的數據。

(2)處理流的功能主要體現在以下兩個方面:
—–性能的提高: 主要以增加緩衝的方式來提高輸入輸出的效率。
—–操作的便捷: 處理流可以提供一系列便捷的方法來一次輸入/輸出大批量的內容,而不是輸入/輸出一個或多個“水滴”。


I/O流的基本概念就先介紹到這裏,下面來一起看看IO流的體系框架。

這裏寫圖片描述

InputSream和Reader

inputStream和Reader是所有輸入流的抽象基類,本身並不能創建實例來執行輸入,但他們將成爲所有輸入流的模板,所以他們的以下方法是所有輸入流都可以使用的方法。

  • 在inputStream裏包含得方法:
    (1)、int read():
    從輸入流中讀取單個字節(相當於從水管 中讀取一個水滴),返回所讀取的字節數據。
    (2)、int read(byte[] b):
    從輸入流中最多讀取b.length個字節的數據,並將其存放在字節數組b中,返回實際讀取的字節數。
    (3)int read(byte[] , int off ,int len ):
    從輸入流中最多讀取len個字節的數據,並將其存放在字節數據組b中。比不是從數組起點開始,而是從off位置開始,返回實際讀取的字節數。
  • Reader
    (1)、int read()
    (2)、int read(char[] cbuf )
    從輸入流中最多讀取cbuf.length長度個字符數據,並將其存放在char數組中。返回實際讀取的字符數。
    (3)、int read(char[] , int off ,int len )

  • Writer

  • OutputStream
    上述兩個基本流我們將在第(二)篇中講述。

不難發現,inputStream和Reader兩個基類的功能基本一樣。但他們都有一個用於讀取文件的輸入流:
FileInputStream
FileReader  
下面我們通過程序是來示由Reader和Writer衍生出來的
FileReader
FileWriter
及其:
BufferedReader
BufferedWriter
的讀取功能:

package com.stormwang.fileinputstream;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;

/**
* @author StormWangxhu
* @version 創建時間:2017年10月31日 下午4:36:55
*
*/
/*
創建文件,通過流實現數據的寫入
*/
public class FileWriterDemo {

    public static void main(String[] args) {

        //讀取文件中的內容,首先我們來創建一個文件
        File file = new File("wang.txt");
        FileWriter fileWriter = null ;
        try {
            fileWriter = new FileWriter(file);
             fileWriter.write("abcdefg");
        } catch (FileNotFoundException  e) {
            e.printStackTrace();
        } catch (IOException e){
            e.printStackTrace();
        }finally {
            if (fileWriter != null) {
                try {
                    fileWriter.close();
                } catch (Exception e2) {
                    e2.printStackTrace();
                }
            }
        }
}
我們會看到,文件創建並寫入成功!結果如下所示:

這裏寫圖片描述


接下來我們就來讀取文件裏的內容!

package com.stormwang.fileinputstream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

/**
* @author StormWangxhu
* @version 創建時間:2017年10月31日 下午5:05:24
*
*/
public class FileReaderDemo {

    public static void main(String[] args) {
        FileReader fileReader= null ;
        /*
         * 關於流:
         * 我們發現,流在try外面申明,在try裏面初始化,在finally中關閉。
         * */
        try {
            fileReader = new FileReader("wang.txt");
            char[] buf = new char[1024];
            int hasRead = 0 ;
            /*
             * 
             * 循環讀取
            while ((hasRead = fileReader.read(buf))>0) {
                fileReader.read(buf);
            }*/
            hasRead = fileReader.read(buf);//將輸入流中的數據讀取到char數組中。
            System.out.println("讀取字符數爲:"+hasRead);
            System.out.println("讀取字符數如下:"+new String(buf));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            /*
             * 在finally中關閉流時,要在try...catch中
                 錯誤關閉流資源方法:
                 finally{
                    if(fileReader != null ){
                        fileReader.close();
                    }
                 }
                 爲什麼這種方法是錯誤的呢?
                 理由如下:  待補!
             */
            if (fileReader!=null) {
                try {
                    //關閉流之前會自動刷新
                    fileReader.close();
                } catch (Exception e2) {
                    e2.printStackTrace();
                }
            }
        }

    }
}

我們看到,讀取成功了。
但是很明顯,由於上面的操作是流直接從數據源或目的地進行操作,效率不高。對應的緩衝技術的出現大大減少了流和數據源之間的直接操作。通過API查看,可知BuffererReader裝飾了Reader.從而演變出了裝飾設計模式,這種模式很好的降低了類與類之間的耦合性。
這裏寫圖片描述

BufferedReader演示代碼:

package FileReaderWriter;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

/**
* @author StormWangxhu
* @version 創建時間:2017年10月31日 下午8:18:00
*
*/
public class BufferedReaderDemo {

    public static void main(String[] args) {
        String lines = null;
        //首先我們爲了繼續複習向文件裏寫入東西。進行創建、寫入!
        FileWriter fileWriter = null ;
        FileReader fileReader = null;
        BufferedReader bufferedReader = null  ;
        try {
            //創建文件並寫入數據
            fileWriter = new FileWriter("buf2.txt");
            fileWriter.write("bufferedReader,successful!");
            /*
             * 此處我在運行過程中出現了問題:
             * 在finally中忘記了關閉fileWriter流,從而數據一直停留在緩衝區,無法寫入到文件中。
             * close()方法自帶刷新技術。
             * 則要想寫入數據:
             *(1) 要麼在寫後進行flush();
             * (2)要麼在finally中進行關閉流資源,從而實現先刷新,再關閉。
             * */
            //fileWriter.flush();
            System.out.println("測試:已運行");
            //利用緩衝技術進行讀取
            fileReader = new FileReader("buf2.txt");
            bufferedReader = new BufferedReader(fileReader);
            while ((lines=bufferedReader.readLine())!=null) {
                System.out.println(lines);
            }

        } catch (IOException e) {
            e.printStackTrace();
        }finally {
        //進行流資源的關閉
            if (bufferedReader!=null) {
                try {
                    bufferedReader.close();
                } catch (Exception e2) {
                    e2.printStackTrace();
                }
            }
            if (fileWriter!= null) {
                try {
                    fileWriter.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
                    }
    }

}

讀取成功!
這裏寫圖片描述

讀取和寫入是一樣的,寫入操作BufferesWriter.

我們仔細觀察上面的代碼,不難發現經過BufferedReader的裝飾之後,讀取數據可以一次讀取一行,這樣很好的提高了流的操作效率,但是readLine()方法是怎麼實現的呢?我們可不可以自己定義一個BufferReader類來實現這些功能呢?答案是肯定的!
實現一下吧!
package FileReaderWriter;
/**
* @author StormWangxhu
* @version 創建時間:2017年10月31日 下午9:06:14
*
*/
/*
 * 自定義BufferedReader
 * 實現readLine()方法
 * 實現close()方法
 * */

import java.io.FileReader;
import java.io.IOException;

public class MyBufferesReader {
    private FileReader fReader ;
    /*BuffedReader的初始化參數是流對象
     * 所以這裏的構造函數應該是帶有流參數的構造函數
     * */
    public MyBufferesReader(FileReader fReader) {
        this.fReader = fReader ;
    }
    public String myReadLine() throws IOException {
        /***
         * BuferedReader的readLine方法
         * 內部調用的是FileReader的read方法
         * 並且內部封裝了一個字符數組,來存取讀取的字符。
         * 這裏爲了方便,創建一個StringBuilder來存取字符。
         * 
         * */
        StringBuilder sBuilder = new  StringBuilder();
        int ch=0 ;
        /**
         * 因爲這裏的read()方法有可能拋出異常
         * 再次申明此函數拋出異常
         * */
        while ((ch=fReader.read())!=-1) {
            if (ch == '\r') {
                continue;
            }else if (ch=='\n') {
                return sBuilder.toString();
            }else {
                sBuilder.append((char)ch);
            }
            //避免丟失最後一行
            if (sBuilder.length() != 0) {
                return sBuilder.toString();//toString()方法將對象轉化爲字符串對象。
            }
        }
        return null;
    }
    /**
     * myClose方法
     * 實現關閉流功能
     * @throws IOException 
     *
     * */
    public void myClose() throws IOException {
    //可以看到,其實緩衝流的關閉,實際上是在關閉字符流。
    //因此,在其他緩衝流關閉資源時,可以直接關閉字符流。
        fReader.close();
    }
}

我們新建一個類來測試myBufferedReader

package FileReaderWriter;
/**
* @author StormWangxhu
* @version 創建時間:2017年10月31日 下午9:06:14
*
*/
/*
 * 自定義BufferedReader
 * 實現readLine()方法
 * 
 * */

import java.io.FileReader;
import java.io.IOException;

public class MyBufferesReader {
    private FileReader fReader ;
    /*BuffedReader的初始化參數是流對象
     * 所以這裏的構造函數應該是帶有流參數的構造函數
     * */
    public MyBufferesReader(FileReader fReader) {
        this.fReader = fReader ;
    }
    public String myReadLine() throws IOException {
        /***
         * BuferedReader的readLine方法
         * 內部調用的是FileReader的read方法
         * 並且內部封裝了一個字符數組,來存取讀取的字符。
         * 這裏爲了方便,創建一個StringBuilder來存取字符。
         * 因爲最終還是要將數據變成字符串
         * 
         * */
        StringBuilder sBuilder = new  StringBuilder();
        int ch=0 ;
        /**
         * 因爲這裏的read()方法有可能拋出異常
         * 再次申明此函數拋出異常
         * */
        while ((ch=fReader.read())!=-1) {
            if (ch == '\r') {
                continue;
            }else if (ch=='\n') {
                return sBuilder.toString();
            }else {
                sBuilder.append((char)ch);
            }
            //避免丟失最後一行
            if (sBuilder.length() != 0) {
                return sBuilder.toString();
            }
        }
        return null;
    }
    /**
     * myClose方法
     * 實現關閉流功能
     * @throws IOException 
     *
     * */
    public void myClose() throws IOException {
        fReader.close();
    }
}

最後,我們看到實現了緩衝技術讀取文本數據。
這裏寫圖片描述
但此時問題出現了,每讀取一個就換行一次,和原BufferedReader功能不符合,我查看了半天,好可惜,沒有看出來是爲什麼?此處望路過的大佬指點迷津!


補充修改說明: 2017/11/1  
昨晚上寫的時候還不知道爲什麼會出現這樣的情況,輸出一個就換一次行,和原BufferedReader輸出形式不符合。今天就發現了,原來是使用了System.out.println(),換行打印,導致如是結果,現在回頭看,昨晚估計是在睡夢中寫的。。。哈哈哈
修改代碼後,我們再來看看輸出情況:

這裏寫圖片描述

輸出正常,自定義BufferedReader複寫成功。

緩衝解釋:

爲什麼需要緩衝呢?原因很簡單,效率問題!緩衝中的數據實際上是保存在內存中,而原始數據可能是保存在硬盤或NandFlash中;而我們知道,從內存中讀取數據的速度比從硬盤讀取數據的速度至少快10倍以上。
那幹嘛不乾脆一次性將全部數據都讀取到緩衝中呢?第一,讀取全部的數據所需要的時間可能會很長。第二,內存價格很貴,容量不想硬盤那麼大。


我們再來看看這個流

LineNumberReader

首先看看這個類的API

這裏寫圖片描述


下面我們簡單的演示一下這個類,再自定義一個myLineNumberReader
這個類好像過時了。。。。。。。
今天就寫到這裏吧,再把上述講的總結一下:
字符流:
基類:
    Reader

    FileReader
    FileWriter

    BufferReader
    BufferedWriter
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章