【Java】I/O工作機制

流的理解

一個流可以理解爲一個字節數據的序列。

輸入流(InputStream)表示從一個源讀取(read)數據。

輸出流(OutputStream)表示向一個目標寫(write)數據。

一個不恰當但是很形象的例子:

輸入流就是從磁盤或網絡讀取字節數據到程序內存;

輸出流就是把字節數據從程序內存寫到磁盤或網絡中去。

這樣來看,所謂的輸入或輸出,參考方向都是當前程序內存而言。所以一個流不可能既是輸入流也是輸出流的說法。

流分類

按功能分:

低級流:有明確的數據源。

高級流:沒有明確的數據源,用於處理其他低級流,高級流不能單獨存在,只能與低級流配合使用。

所以區分也很好區分,也就是看有沒有明確的數據源,是否依賴低級流。

按讀取數據單位分:

字節流:以字節爲單位進行讀寫,InputStream OutputStream

字符流:以字符爲單位進行讀寫,Writer  Reader

==================================

字節(Byte):字節是通過網絡傳輸信息(或在硬盤或內存中存儲信息)的單位。字節是計算機信息技術用於計量存儲容量和傳輸容量的一種計量單位,1個字節等於8位二進制(1 byte  = 8  bit),它是一個8位的二進制數,是一個很具體的存儲空間。
字符:字符是指計算機中使用的文字和符號人們使用的記號,抽象意義上的一個符號。 
關於字節和字符的關係,就不得不說道編碼方式。不同的編碼方式裏,字符和字節的對應關係不同。字符之間的編碼不一致也是造成亂碼的原因。

=======================================

以下詳細說明幾種流

Java 爲 I/O 提供了強大的而靈活的支持,使其更廣泛地應用到文件傳輸和網絡編程中。

Java所有的流類位於java.io包中,都分別繼承字以下四種抽象流類型。

繼承自InputStream/OutputStream的流都是用於向程序中輸入/輸出數據,且數據的單位都是字節(byte=8bit)。

繼承自Reader/Writer的流都是用於向程序中輸入/輸出數據,且數據的單位都是字符(2byte=16bit)。

Type
字節流
字符流
輸入流
InputStream
Reader
輸出流
OutputStream
Writer


字節流:

1、用於讀寫文件的輸入輸出流

FileInputStream:文件字節輸入流

該流用於從文件讀取數據,它的對象可以用關鍵字 new 來創建。
有多種構造方法可用來創建對象。
可以使用字符串類型的文件名來創建一個輸入流對象來讀取文件:
InputStream f =new FileInputStream("C:/xxxx/xxxx");
也可以使用一個文件對象來創建一個輸入流對象來讀取文件。我們首先得使用 File() 方法來創建一個文件對象:
File file =new File("C:/xxxx/xxxxx");
InputStream out =new FileInputStream(file);


FileOutputStream:文件字節輸出流

該類用來創建一個文件並向文件中寫數據。
如果該流在打開文件進行輸出前,目標文件不存在,那麼該流會創建該文件。
有兩個構造方法可以用來創建 FileOutputStream 對象。
使用字符串類型的文件名來創建一個輸出流對象:
OutputStream f =new FileOutputStream("C:/xxxx/xxxx")
也可以使用一個文件對象來創建一個輸出流來寫文件。我們首先得使用File()方法來創建一個文件對象:
File f = new File("C:/xxxx/xxxx");
OutputStream f =new FileOutputStream(f);

下面是一個演示 InputStream 和 OutputStream 用法的例子:

public class FileStreamsDemo {
    public static void main(String[] args) {
        try{
            byte bWrite [] = {11,21,3,40,5};
            OutputStream os = new FileOutputStream("test.txt");
            for(int x=0; x < bWrite.length ; x++){
                os.write( bWrite[x] ); // writes the bytes
            }
            os.close();

            InputStream is = new FileInputStream("test.txt");
            int size = is.available();

            for(int i=0; i< size; i++){
                System.out.print((char)is.read() + "  ");
            }
            is.close();
        }catch(IOException e){
            System.out.print("Exception");
        }
    }
}

上面的程序首先創建文件test.txt,並把給定的數字以二進制形式寫進該文件,同時輸出到控制檯上。

以上代碼由於是二進制寫入,可能存在亂碼,你可以使用以下代碼實例來解決亂碼問題:

public class FileStreamsDemo2 {
    public static void main(String[] args) throws IOException {
        File f = new File("a.txt");
        FileOutputStream fop = new FileOutputStream(f);
        // 構建FileOutputStream對象,文件不存在會自動新建

        OutputStreamWriter writer = new OutputStreamWriter(fop, "UTF-8");
        // 構建OutputStreamWriter對象,參數可以指定編碼,默認爲操作系統默認編碼,windows上是gbk

        writer.append("中文輸入");
        // 寫入到緩衝區

        writer.append("\r\n");
        //換行

        writer.append("English");
        // 刷新緩存衝,寫入到文件,如果下面已經沒有寫入的內容了,直接close也會寫入

        writer.close();
        //關閉寫入流,同時會把緩衝區內容寫入文件,所以上面的註釋掉

        fop.close();
        // 關閉輸出流,釋放系統資源

        FileInputStream fip = new FileInputStream(f);
        // 構建FileInputStream對象

        InputStreamReader reader = new InputStreamReader(fip, "UTF-8");
        // 構建InputStreamReader對象,編碼與寫入相同

        StringBuffer sb = new StringBuffer();
        while (reader.ready()) {
            sb.append((char) reader.read());
            // 轉成char加到StringBuffer對象中
        }
        System.out.println(sb.toString());
        reader.close();
        // 關閉讀取流

        fip.close();
        // 關閉輸入流,釋放系統資源
    }
}


2、BufferedInputStream和BufferedOutputStream 緩衝字節輸入輸出流
相比文件字節輸入輸出流而言,緩衝流的內部維護了一個緩衝區(字節數組),可以減少讀寫次數,從而提高讀寫效率。緩衝流輸入高級流,依賴低級流,所以在最後關閉流的時候,是先關閉低級流,再關閉高級流。
使用緩衝流一定注意一個問題。緩衝流默認是讀寫滿了緩衝區,纔會真正的讀寫出去。所以,沒有讀寫滿時,一定要記得flush()方法,不然會丟數據。這個有點坑人!!

3、DataInputStream和DataOutputStream 基於基本類型數據讀寫輸入輸出高級流
這種流不同於FileInputStream、FileOutputStream、BufferedInputStream和BufferedOutputStream只有簡單的按字節讀寫的write和read方法。DataOutputStream和DataInputStream都是writeInt,ReadInt方法。相率上沒有BufferedInputStream和BufferedOutputStream高。但是高級流是可以組合使用的。一般這種流用於組合其他高級流使用,從而提高讀寫效率。

4、ObjectInputStream和ObjectOutputStream  基於Java對象的高級流
ObjectInputStream:用於將一個文件先序列化爲字節數據讀取後轉換爲Java對象
ObjectOutputStream:用於將一個Java對象轉換爲響應的字節並寫入文件

這兩個高級流很容易出現的錯誤就是NotSerializableException未序列化異常。所以使用這兩個高級流進行Java對象的轉換就需要Java對象能夠進行序列化,實現了Serivalizable接口。

隨便提及一下關於序列化的問題:
序列化:將基本類型數據(或對象bean)轉化爲對應的字節序列。
反序列化:將字節序列轉化爲對應的基本類型數據(或對象bean)。

把一個對象序列化以後,再反序列化回來,並不是同一個對象,只是內容相同而已。
一個對象序列化以後,能否反序列化回來,還得看序列化的版本號(long serialVersionUID=1L)是否一致,若不一致就不能反序列化回來。會引發InvalidClassException異常。

還有一個關鍵字:transient。被transient關鍵字修飾的屬性,在序列化的時候,會被忽略。

字符流(都是高級流):

字符輸入輸出流使用時有一定的侷限性,只能讀取文本文件,不適用於讀取其他數據類型,比如圖片,視頻,音頻等。要讀取圖片,視頻,音頻這些只能通過字節輸入輸出流進行讀取。
字節 到 字符 之間的相互轉換,中間就會涉及到 字符集編碼轉換的問題。

1、InputStreamReader 和OutputStreamWriter 字符輸入輸出流

以字符爲單位讀寫數據。可修改字符集

2、BufferedReader 和 BufferedWriter  緩衝字符輸入輸出流
以行爲單位讀寫字符串。

3、FileReader 和 FileWriter 讀取文本文件的字符輸入輸出流  
不能修改字符集,只能按照當前系統默認的字符集進行讀取

4、PrintWriter  一種高級的緩衝字符輸出流。工作中常用。
 
web開發中常用,輸出到指定頁面渲染。

((HttpServletResponse) response).setStatus(HttpStatus.SC_INTERNAL_SERVER_ERROR);
response.setContentType("application/json;charset=UTF-8");
Map<String, Object> resultMap = "";
String respString = JSONObject.toJSONString(resultMap);
PrintWriter out = response.getWriter();
out.print(respString);
out.close();


Java中的目錄


File類用於描述文件系統中的一個文件或目錄。
功能:通過File類可以獲取文件或目錄的名稱,大小,修改時間等屬性信息,可以創建,刪除文件,但是不能修改編輯文件內容。

創建目錄:

File類中有兩個方法可以用來創建文件夾:
mkdir( )方法創建一個文件夾,成功則返回true,失敗則返回false。失敗表明File對象指定的路徑已經存在,或者由於整個路徑還不存在,該文件夾不能被創建。
mkdirs()方法創建一個文件夾和它的所有父文件夾。
注意: Java 在 UNIX 和 Windows 自動按約定分辨文件路徑分隔符。

讀取目錄:

一個目錄其實就是一個 File 對象,它包含其他文件和文件夾。
如果創建一個 File 對象並且它是一個目錄,那麼調用 isDirectory() 方法會返回 true。
可以通過調用該對象上的 list() 方法,來提取它包含的文件和文件夾的列表。
下面展示的例子說明如何使用 list() 方法來檢查一個文件夾中包含的內容:
public class ReadDirListDemo {
    public static void main(String[] args) {
        String dirname = "/tmp";
        File f1 = new File(dirname);
        if (f1.isDirectory()) {
            System.out.println( "目錄 " + dirname);
            String s[] = f1.list();
            for (int i=0; i < s.length; i++) {
                File f = new File(dirname + "/" + s[i]);
                if (f.isDirectory()) {
                    System.out.println(s[i] + " 是一個目錄");
                } else {
                    System.out.println(s[i] + " 是一個文件");
                }
            }
        } else {
            System.out.println(dirname + " 不是一個目錄");
        }
    }
}

刪除目錄或文件:

刪除文件可以使用 java.io.File.delete() 方法。
以下代碼會刪除目錄/tmp/java/,即便目錄不爲空。
測試目錄結構:
public class DeleteFileDemo {
    public static void main(String args[]) {
        // 這裏修改爲自己的測試目錄
        File folder = new File("/tmp/java/");
        deleteFolder(folder);
    }

    //刪除文件及目錄
    public static void deleteFolder(File folder) {
        File[] files = folder.listFiles();
        if(files!=null) {
            for(File f: files) {
                if(f.isDirectory()) {
                    deleteFolder(f);
                } else {
                    f.delete();
                }
            }
        }
        folder.delete();
    }
}


RandomAccessFile類:隨機讀寫文件內容

讀寫數據都是基於字節形式的。讀寫完畢後要關閉,釋放對文件的操作權限以及資源。
RandomAccessFile是基於遊標進行讀寫操作的,總是讀取或寫入遊標置頂的位置,所以當連續讀寫完成後,遊標只想文件末尾,如果此時再試圖讀取,就會拋異常了。所以每次讀取之前,先確保遊標的位置。遊標詳細操作參考jdk。


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