JAVA I/O(一)基本字節和字符IO流


最近再看I/O這一塊,故作為總結記錄於此。JDK1.4引入NIO后,原來的I/O方法都基於NIO進行了優化,提高了性能。I/O操作類都在java.io下,大概將近80個,大致可以分為4類:

  • 基於字節操作的I/O接口:以InputStream和OutputStream為基類,也是I/O操作的基礎。
  • 基於字符操作的I/O接口:以Reader和Writer為基類,字符的讀寫是基於字節進行的,中間進行了轉換。
  • 基於磁盤操作的I/O接口:主要是File,代表目錄下的所有文件。
  • 基於網絡操作的I/O接口:主要是Socket,實現網絡數據的傳輸。

本文大致總結一下基於字節和字符的I/O操作,主要理清JAVA I/O中的類關系。

摘自《Java編程思想》:類庫中常使用“流”這個抽象的概念,代表任何有能力產出數據的數據源對象或有能力接收數據的接收端對象。其屏蔽了I/O設備中數據處理的細節。I/O類分為輸入和輸出兩類。通過 繼承,任何InputStream或Reader的派生類都含有read()方法,用於讀取單個字節或字符,任何OutputStream或Writer的派生類都含有write()方法,用於寫單個字節或字符。通常不會使用單一的類創建流對象,而是通過疊合多個對象提供期望的功能(即采用裝飾器模式)。

一、基於字節的I/O操作

1. InputStream類型

InputStream的作用表示那些從不同數據源產生輸入的類,即其派生類多是不同數據源對應的流對象。如下:

  ByteArrayInputStream:從內存緩沖區讀取字節數組

  FileInputStream:從文件中讀取字節,其構造參數可以是文件名、File對象或FileDescriptor

  ObjectInputStream:主要用於反序列化,讀取基本數據類型或對象

  PipedInputStream:產生用於寫入相關PipedOutputStream的數據,實現“管道化”概念,多用於多線程中。

  FilterInputStream:作為裝飾器類,其子類與上述不同流對象疊合使用,以控制特定輸入流。

其中,FilterInputStream的子類通過添加屬性或有用的接口控制字節輸入流,其構造函數為InputStream,常見的幾個如下:

  DataInputStream:與DataOutputStream搭配使用,讀取基本類型數據及String對象。

  BufferdInputStream:使用緩沖區的概念,避免每次都進行實際讀操作,提升I/O性能。(不是減少磁盤IO操作次數(這個OS已經幫我們做了),而是通過減少系統調用次數來提高性能的

  InflaterInputStream:其子類GZIPInputStream和ZipInputStream可以讀取GZIP和ZIP格式的數據。

2. OutputStream類型

與InputStream相對應,OutputStream的作用表示將數據寫入不同的數據源,常用的輸出流對象如下:

  ByteArrayOutputStream:在內存中創建緩沖區,寫入字節數組

  FileOutputStream:將字節數據寫入文件中,其構造參數可以是文件名、File對象或FileDescriptor

  ObjectOutputStream:主要用於序列化,作用於基本數據類型或對象

  PipedOutputStream:任何寫入其中的數據,都會自動作為相關PipedInputStream的輸出,實現“管道化”概念,多用於多線程中。

  FilterOutputStream:作為裝飾器類,其子類與上述不同流對象疊合使用,以控制特定輸出流。

其中,FilterOutputStream的子類通過添加屬性或有用的接口控制字節輸入流,其構造函數為InputStream,常見的幾個如下:

  DataOutputStream:與DataInputStream搭配使用,寫入基本類型數據及String對象。

  PrintStream:用於格式化輸出顯示。

  BufferdOutputStream:使用緩沖區的概念,避免每次都進行實際寫操作,提升I/O性能。

  DeflaterOutputStream:其子類GZIPOutputStream和ZipOutputStream可以寫GZIP和ZIP格式的數據。

二、基於字符的I/O操作

不管是磁盤還是網絡傳輸,數據處理的最小單元都是字節,而不是字符。故所有I/O操作的都是字節而不是字符。為了方便引入了字符操作,其中涉及字節到字符的轉換適配,InputStreamReader可以把InputStream轉為Reader,OutputStreamWriter可以把OutputStream轉為Writer。對上述按字節操作的流對象,可以采用FilterInputStream和FilterOutputStream的裝飾器子類控制流。Reader和Writer沿用相似的思想,但不完全相同。

1. Reader類型

繼承自Reader類的,字符型數據來源常用類,如下:

  InputStreamReader:字節與字符適配器,子類包含FileReader(以字符形式讀取文件) 

  CharArrayReader:讀取字符數組

  StringReader:數據源是字符串

  BufferedReader:讀取字符輸入流,並進行緩存,常用法:BufferedReader in = new BufferedReader(new FileReader("foo.in")); 表示采用緩存的方式從文件讀取數據

  PipedReader:管道形式讀取字符  

  FilterReader:對Reader裝飾,直接使用的不多,如PushbackReader

2. Writer類型

繼承自Writer類的,字符型數據來源常用類,如下:

  OutputStreamReader:字節與字符適配器,子類包含FileWriter(以字符形式寫文件) 

  CharArrayWriter:寫字符數組

  StringWriter:內部有StringBuffer,用於緩存構造字符串

  BufferedWriter:字符輸出流,常用法:PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter("foo.out"))); 表示將數據格式化並用緩存的方式寫入文件

  PipedWriter:管道形式輸出字符  

  FilterWriter:對Writer裝飾,如XMLWriter

三、自我獨立的類RandomAccessFile

該類可以隨機訪問文件,實現了DataOutput, DataInput,不是InputStream或OutputStream繼承層次結構的一部分。與其他I/O類本質有所不同,可以在一個文件內向前或向后移動。

工作方式類似與DataOutputStream和 DataInputStream,用法如下:

  RandomAccessFile randomAccessFile = new RandomAccessFile("data.dat", "rw")

其中,r代表讀,w代表寫。

四、常用實例

1. 緩存輸入文件

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
/**
 * 緩存輸入文件,防止頻繁與文件交互
 * 1.采用裝飾器模式,BufferedReader從FileReader中讀取字符,FileReader為字符數據源
 * 2.FileReader繼承InputStreamReader,實例化一個FileInputStream對象作為字節數據源,
 * 3.InputStreamReader繼承Reader,包含StreamDecoder,將字節數據轉換為字符;編碼格式沒有指定時采用默認編碼。
 * 4.Reader可以實現對FileInputStream加鎖*/
public class BufferedInputFile {

    public static String read(String filename) throws IOException {
        BufferedReader bufferedReader = new BufferedReader(new FileReader(filename));
        String s;
        StringBuilder sb = new StringBuilder();
        while((s = bufferedReader.readLine()) != null) {
            sb.append(s + "\n");
        }
        bufferedReader.close();
        return sb.toString();
    }
    
    public static void main(String[] args) throws IOException {
        System.out.println(read("src/com/test/io/BufferedInputFile.java"));
    }
}
//輸出類文件到控制台

2.從內存輸入

import java.io.IOException;
import java.io.StringReader;
/**
 * 將文件讀入內存
 * 具體形式:new StringReader(new BufferdReader(new FileReader(filename)))
 * 通過緩存讀文件,防止每讀一個字節,都與文件直接交互*/
public class MemoryINput {

    static String filename = "src/com/test/io/BufferedInputFile.java";
    
    public static void main(String[] args) throws IOException{
        StringReader in = new StringReader(BufferedInputFile.read(filename));
        int c;
        while((c = in.read()) != -1) {
            System.out.println((char)c);
        }
    }
}

3.格式化內存輸入

import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
/**
 * 格式化的內存輸入
 * 1.in.readByte()讀取字節,無法判斷字節是否有效合法,因此無法判斷結尾,報java.io.EOFException
 * 2.采用available()方法預估還有多少字節可存取*/
public class FormattedMemoryInput {
    
    public static void main(String[] args) throws IOException {
        DataInputStream in = new DataInputStream(
                new ByteArrayInputStream(BufferedInputFile.read("src/com/test/io/BufferedInputFile.java").getBytes()));
//        byte c;
//        while((c = in.readByte()) !=-1) {
//            System.out.print((char) c);
//        }
        while(in.available() != 0) {
            System.out.print((char) in.readByte());
        }
    }
}

4.基本文件輸出

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringReader;
/**
 * 基本文件輸出
 * 1.先利用BufferedInputFile和StringReader將數據讀到內存,記住輸入流已經關閉
 * 2.new PrintWriter(new BufferedWriter(new FileWriter(outfile)))輸出字符到文件
 * 注意,此處使用BufferedWriter進行緩沖,防止每個字節都與文件交互
 * 3.文本文件輸出快捷方式 PrintWriter out = new PrintWriter(outfile);
 * 底層實現了緩存new BufferedWriter(new OutputStreamWriter(new FileOutputStream(fileName)))*/
public class BasicFIleOutput {

    static String filename = "src/com/test/io/BufferedInputFile.java";
    static String outfile = "BasicFIleOutput.out";
    
    public static void main(String[] args) throws IOException {
        BufferedReader in = new BufferedReader(new StringReader(BufferedInputFile.read(filename)));
//        PrintWriter out = new PrintWriter(new FileWriter(outfile));
//        PrintWriter out = new PrintWriter(outfile);
        PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(outfile)));
        int lineCount = 1;
        String s;
        while((s = in.readLine()) != null) {
            out.println(lineCount++ + ":" +s);
        }
        out.close();
    }
}

5.存儲與恢復數據

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
/**
 * 1.DataInputStream能從DataOutputStream中准確讀取數據
 * 2.讀數據時,必須知道數據精確的位置,否則會報錯
 * 3.writeUTF與readUTF采用UTF-8的變體進行編碼
 *
 */
public class StoringAndRecoveringData {

    public static void main(String[] args) throws IOException {
        
        DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream("Data.txt")));
        out.writeDouble(3.12159);
        out.writeUTF("this is pi");
        out.writeDouble(1.4414);
        out.writeUTF("this is root of 2");
        out.close();
        
        DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream("Data.txt")));
        System.out.println(in.readDouble());
        System.out.println(in.readUTF());
        System.out.println(in.readDouble());
        in.close();
    }
} 

五、總結

1.  I/O操作本質是基於字節流的操作,InputStream和OutputStream對輸入和輸出源進行了抽象,其子類代表不同的數據源。

2. FilterInputStream和FilterOutputStream采用裝飾器模式,對輸入和輸出流進行控制,如采用緩沖器、讀基本數據類型等。

3. Reader和Writer代表基於字符的操作,底層是基於字節操作,經過InputStreamReader和OutputStreamWriter,采用StreamEncoder和StreamDecoder,將輸入輸出流,按Charset進行轉換

4. 所有基於字節或字符的操作,基本都采用疊合的方式。如輸入流采用緩存的方式從文件中讀取,輸出流采用緩存的方式按格式輸出到文件。

5. 理清他們之間的關系,有利於了解I/O的操作過程。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM