JAVA基礎之I/O體系及常用類

I/O流的概念

I/O流 即輸入Input流/ 輸出Output流的縮寫,常見的是電腦屏幕輸出設備和鍵盤鼠標輸入設備,其廣義上的定義就是:數據在內部存儲器和外部存儲器或其他周邊設備之間的輸入和輸出;即:數據/ 輸入/ 輸出。

流是一個抽象但形象的概念,Java中把不同的輸入/輸出源(鍵盤,文件,網絡連接等)抽象的表述爲 “流”(Stream)。可以簡單理解成一個數據的序列,輸入流表示從一個源讀取數據,輸出流則表示向一個目標寫數據,在Java程序中,對於數據的輸入和輸出都是採用 “流” 這樣的方式進行的。

I/O流的分類

按流向分:輸入流,輸出流
輸入流:只能從中讀取數據,不能寫入數據
輸出流,只能向其寫入數據,不能讀取數據

這裏的輸入輸出是相對而言的,在本地用程序讀寫文件時,始終是以程序爲中心,即程序從文件中讀取時就是硬盤向內存中輸入,程序向文件中寫入就是內存向硬盤輸出。

但對於服務器和客戶端來說,服務器將數據輸出到網絡中,這是Sever端程序的輸出流。客戶端從網絡中讀取數據,這是Client端的輸入流。
 

按操作單元分:字節流和字符流
字節流:圖片、視頻文件中存儲的都是二進制的字節(byte)。直觀的想法,以一個字節單位來運輸的,比如一杯一杯的取水。

以InputStream和OutputStream作爲基類


字符流:一般文本文件中存放都是有明確含義的,可供人閱讀理解的字符(char)。直觀的想法,以多個字節來運輸的,比如一桶一桶的取水,一桶水又可以分爲幾杯水。

以Reader和Writer作爲基類

 

不管是文本、還是圖書、視頻最終在磁盤上的時候都是按照byte存儲的。因此,Java要提供基於字符流的機制,就要處理字節和字符的相互轉化,這裏就涉及字符集合字符編碼的問題。

字節流和字符流的區別:

字節流讀取單個字節,字符流讀取單個字符(一個字符根據編碼的不同,對應的字節也不同,如 UTF-8 編碼是 3 個字節,中文編碼是 2 個字節。)字節流用來處理二進制文件(圖片、MP3、視頻文件)。

字符流用來處理文本文件(可以看做是特殊的二進制文件,使用了某種編碼,人可以閱讀)。

簡而言之,字節是個計算機看的,字符纔是給人看的。

其實現子類:FileInputStream和FileOutputStream可以對任意類型的文件進行讀寫操作,但 FileReader和FileWriter只能對純文本文件進行操作。

從網上找來一張圖,方便對Java I/O有個總統的認識。從這張圖可以很清楚的看清Java I/O的字節流和字符流的整體情況。


 

IO體系的基類

InputStream/Reader,OutputStream/Writer

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

  • InputStream 讀文件方法

int read():從輸入流中讀取單個字節,返回所讀取的字節數據(字節數據可直接轉爲int型)

int read(byte []):從輸入流中最多讀取b.length個字節的數據,並將其存儲在字節數組b中,返回實際讀取的字節數。
int read(byte[], int off, int len): 從輸入流中最多讀取len個字節的數據,並將其存儲在數組b中,放入b數組時,並不是從數組起點開始,而是從off位置開始,返回實際讀取的字節數

  • Reader讀文件方法

int read():從輸入流中讀取單個字符,返回所讀取的字符數據(可直接轉爲int類型)
int read(char[]):從輸入流中最多讀取b.length個字符的數據,並將其存儲在數組b中,返回實際讀取的字符數。
int read(char[] b, int off, int len):從輸入流中最多讀取len個字符的數據,並將其存儲在數組b中,放入數組b時,並不是從數組七點開始,而是從off位置開始,返回實際讀取的字符數。
 

  • OutputStream寫文件方法

void write(int c):將指定的字節/字符輸出到輸出流中,c可以代表字節,也可以代表字符
void write(byte[]/byte[] buf):將字節數組,字符數組的數據輸出到指定輸出流中
void write(byte[]/byte[] buf, int off, int len):將字節數組/字符數組從off位置開始,長度爲len的字節/字符數組輸出到輸出流中

  • Writer寫文件方法

因爲字符流直接以字符爲操作單位,所以Writer可以用字符串代替字符數組,即以String對象爲參數。Writer中還包括這兩個方法:

void write(String str):將str字符串裏包含的字符輸出到指定輸出流中
void write(String str, int off, int len):將str字符串裏從off位置開始,長度爲len的字符輸出到指定輸出流中

 

IO常用類

  • 文件流:FileInputStream/FileOutputStream, FileReader/FileWriter

FileInputStream/FileOutputStream, FileReader/FileWriter是專門操作文件流的,用法高度相似,區別在於前面兩個是操作字節流,後面兩個是操作字符流。它們都會直接操作文件流,直接與OS底層交互。因此他們也被稱爲節點流

使用這幾個流的對象之後,需要關閉流對象,因爲java垃圾回收器不會主動回收。

不過在Java7之後,可以在 try() 括號中打開流,最後程序會自動關閉流對象,不再需要顯示的close。

下面演示這四個流對象的基本用法

//FileInputStream一個一個字節讀取
public class FileInputStreamDemo1 {
            public static void main(String[] args) throws IOException{
                FileInputStream fis = new FileInputStream("c:\\a.txt");
                //讀取一個字節,調用方法read 返回int
                //使用循環方式,讀取文件,  循環結束的條件  read()方法返回-1
                int len = 0;//接受read方法的返回值
            
                while( (len = fis.read()) != -1){
                    System.out.print((char)len);
                }
                //關閉資源
                fis.close();
            }
        }

//FileInputStream讀取字節數組
  public class FileInputStreamDemo2 {
            public static void main(String[] args) throws IOException {
                FileInputStream fis = new FileInputStream("c:\\a.txt");
                //創建字節數組
                byte[] b = new byte[1024];
                
                int len = 0 ;
                while( (len = fis.read(b)) !=-1){
                    System.out.print(new String(b,0,len));
                }
                fis.close();
            }
        }

//FileOutputStream寫單個字節
 public class FileOutputStreamDemo {
            public static void main(String[] args)throws IOException {
                FileOutputStream fos = new FileOutputStream("c:\\a.txt");
                //流對象的方法write寫數據
                //寫1個字節
                fos.write(97);
                //關閉資源
                fos.close();
            }
        }

//FileOutputStream寫字節數組
  public class FileOutputStreamDemo {
            public static void main(String[] args)throws IOException {
                FileOutputStream fos = new FileOutputStream("c:\\a.txt");
                //流對象的方法write寫數據
                //寫字節數組
                byte[] bytes = {65,66,67,68};
                fos.write(bytes);
                
                //寫字節數組的一部分,開始索引,寫幾個
                fos.write(bytes, 1, 2);
                //寫入字節數組的簡便方式
                //寫字符串
                fos.write("hello".getBytes());
                //關閉資源
                fos.close();
            }
        }

//字符流複製文本
//FileReader讀取數據源,FileWriter寫入到數據目的

public class Copy {
        public static void main(String[] args) {
            FileReader fr = null;
            FileWriter fw = null;
            try{
                fr = new FileReader("c:\\1.txt");
                fw = new FileWriter("d:\\1.txt");
                char[] cbuf = new char[1024];
                int len = 0 ;
                while(( len = fr.read(cbuf))!=-1){
                    fw.write(cbuf, 0, len);
                    fw.flush();
                }
                
            }catch(IOException ex){
                System.out.println(ex);
                throw new RuntimeException("複製失敗");
            }finally{
                try{
                    if(fw!=null)
                        fw.close();
                }catch(IOException ex){
                    throw new RuntimeException("釋放資源失敗");
                }finally{
                    try{
                        if(fr!=null)
                            fr.close();
                    }catch(IOException ex){
                        throw new RuntimeException("釋放資源失敗");
                    }
                }
            }
        }
    }
  • 轉換流:InputStreamReader/OutputStreamWriter

InputStreamReader/OutputStreamWriter可以將字節流轉換成字符流,被稱爲字節流與字符流之間的橋樑。經常在讀取鍵盤輸入(System.in)或網絡通信的時候,需要使用這兩個類。

轉換流作用:

1 . 是字符流和字節流之間的橋樑

2 . 可對讀取到的字節數據經過指定編碼換成字符

3 . 可對讀取到的字符數據經過指定編碼轉成字節

 /*
   * 轉換流對象OutputStreamWriter寫文本
   * 採用UTF-8編碼表寫入
   */
  public static void writeUTF()throws IOException{
      //創建字節輸出流,綁定文件
      FileOutputStream fos = new FileOutputStream("c:\\utf.txt");
      //創建轉換流對象,構造方法保證字節輸出流,並指定編碼表是UTF-8
      OutputStreamWriter osw = new OutputStreamWriter(fos,"UTF-8");
      osw.write("你好");
      osw.close();
  }
  
  /*
   * 轉換流對象 OutputStreamWriter寫文本
   * 文本採用GBK的形式寫入
   */
  public static void writeGBK()throws IOException{
      //創建字節輸出流,綁定數據文件
      FileOutputStream fos = new FileOutputStream("c:\\gbk.txt");
      //創建轉換流對象,構造方法,綁定字節輸出流,使用GBK編碼表
      OutputStreamWriter osw = new OutputStreamWriter(fos);
      //轉換流寫數據
      osw.write("你好");
      
      osw.close();
  }


/*
 *  轉換流,InputSteamReader讀取文本
 *  採用UTF-8編碼表,讀取文件utf
 */
public static void readUTF()throws IOException{
    //創建自己輸入流,傳遞文本文件
    FileInputStream fis = new FileInputStream("c:\\utf.txt");
    //創建轉換流對象,構造方法中,包裝字節輸入流,同時寫編碼表名
    InputStreamReader isr = new InputStreamReader(fis,"UTF-8");
    char[] ch = new char[1024];
    int len = isr.read(ch);
    System.out.println(new String(ch,0,len));
    isr.close();
}
/*
 *  轉換流,InputSteamReader讀取文本
 *  採用系統默認編碼表,讀取GBK文件
 */
public static void readGBK()throws IOException{
    //創建自己輸入流,傳遞文本文件
    FileInputStream fis = new FileInputStream("c:\\gbk.txt");
    //創建轉換流對象,構造方法,包裝字節輸入流
    InputStreamReader isr = new InputStreamReader(fis);
    char[] ch = new char[1024];
    int len = isr.read(ch);
    System.out.println(new String(ch,0,len));
    
    isr.close();
}

 

  • 緩衝流:BufferedReader/BufferedWriter , BufferedInputStream/BufferedOutputStream

沒有經過Buffered處理的IO, 意味着每一次讀和寫的請求都會由OS底層直接處理,這會導致非常低效的問題。

經過Buffered處理過的輸入流將會從一個buffer內存區域讀取數據,本地API只會在buffer空了之後纔會被調用(可能一次調用會填充很多數據進buffer)。

經過Buffered處理過的輸出流將會把數據寫入到buffer中,本地API只會在buffer滿了之後纔會被調用。

 

BufferedReader/BufferedWriter可以將字符流(Reader)包裝成緩衝流,這是最常見用的做法。

另外,BufferedReader提供一個readLine()可以方便地讀取一行,因此BufferedReader也被稱爲行讀取器。而FileInputStream和FileReader只能讀取一個字節或者一個字符。

在原有的節點流對象外部包裝緩衝流,爲IO流增加了內存緩衝區,增加緩衝區的兩個目的:

1 .允許IO一次不止操作一個字符,這樣提高整個系統的性能
2 .由於有緩衝區,使得在流上執行skip,mark和reset方法都成爲可能
緩衝流要套接在節點流之上,對讀寫的數據提供了緩衝功能,增加了讀寫的效率,同時增加了一些新的方法。例如:BufferedReader中的readLine方法,BufferedWriter中的newLine方法。

//字符輸入流
BufferedReader(Reader in)//創建一個32字節的緩衝區
BufferedReader(Reader in, int size)//size爲自定義緩存區的大小

//字符輸出流
BufferedWriter(Writer out)
BufferedWriter(Writer out, int size)

//字節輸入流
BufferedInputStream(InputStream in)
BufferedInputStream(InputStream in, int size)

//字節輸出流
BufferedOutputStream(OutputStream in)
BufferedOutputStream(OutputStream in, int size)

對於輸出的緩衝流,BufferedWriter和BufferedOutputStream會先在內存中緩存,使用flush方法會使內存中的數據立刻寫出。

//字節輸出流緩衝流BufferedOutputStream 
 public class BufferedOutputStreamDemo {
          public static void main(String[] args)throws IOException {
              //創建字節輸出流,綁定文件
              //FileOutputStream fos = new FileOutputStream("c:\\buffer.txt");
              //創建字節輸出流緩衝流的對象,構造方法中,傳遞字節輸出流
              BufferedOutputStream bos = new
                      BufferedOutputStream(new FileOutputStream("c:\\buffer.txt"));
              
              bos.write(55);
              
              byte[] bytes = "HelloWorld".getBytes();
              
              bos.write(bytes);
              
              bos.write(bytes, 3, 2);
              
              bos.close();
          }
      }

//字節輸入流緩衝流BufferedInputStream
 public class BufferedInputStreamDemo {
          public static void main(String[] args) throws IOException{
              //創建字節輸入流的緩衝流對象,構造方法中包裝字節輸入流,包裝文件
              BufferedInputStream bis = new 
                      BufferedInputStream(new FileInputStream("c:\\buffer.txt"));
              byte[] bytes = new byte[10];
              int len = 0 ;
              while((len = bis.read(bytes))!=-1){
                  System.out.print(new String(bytes,0,len));
              }
              bis.close();
          }
      }

//字符流緩衝區流複製文本文件
*
         *  使用緩衝區流對象,複製文本文件
         *  數據源  BufferedReader+FileReader 讀取
         *  數據目的 BufferedWriter+FileWriter 寫入
         *  讀取文本行, 讀一行,寫一行,寫換行
         */
        public class Copy_1 {
            public static void main(String[] args) throws IOException{
                BufferedReader bfr = new BufferedReader(new FileReader("c:\\w.log"));   
                BufferedWriter bfw = new BufferedWriter(new FileWriter("d:\\w.log"));
                //讀取文本行, 讀一行,寫一行,寫換行
                String line = null;
                while((line = bfr.readLine())!=null){
                    bfw.write(line);
                    bfw.newLine();
                    bfw.flush();
                }
                bfw.close();
                bfr.close();
            }
        }

 

  • 對象流(ObjectInputStream/ObjectOutputStream)

如果我們要寫入文件內的數據不是基本數據類型(使用DataInputStream),也不是字符型或字節型數據,而是一個對象,應該怎麼寫?這個時候就用到了處理流中的對象流。

對象的序列化
對象中的數據,以流的形式,寫入到文件中保存過程稱爲寫出對象,對象的序列化
ObjectOutputStream將對象寫道文件中,實現序列化

對象的反序列化
在文件中,以流的形式,將對象讀出來,讀取對象,對象的反序列化
ObjectInputStream 將文件對象讀取出來

注意:

  • 先序列化後反序列化; 反序列化順序必須與序列化一致
  • 不是所有對象都可以序列化 必須實現 java.io.Serializable
  • 不是所有屬性都需要序列化 不需要序列化的屬性 要加 transient
    public class Person implements Serializable {
        private String name;
        private int age;
        private transient String intro;   //不需要序列化
    
        public Person(String name, int age, String intro) {
            this.name = name;
            this.age = age;
            this.intro = intro;
        }
    
        public String getName() {
            return name;
        }
    
        public int getAge() {
            return age;
        }
    
        public String getIntro() {
            return intro;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public void setIntro(String intro) {
            this.intro = intro;
        }
    
        public String toString(){
            return name + " " + age + " " + intro;
        }
    }

    進行序列化和反序列化後:

    public class WriteObject {
        public static void main(String[] args) throws IOException, ClassNotFoundException {
            ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(
                    new File("d:\\test.txt"))));
    
            oos.writeObject(new Person("shi",13,"A"));
            oos.writeObject(new Person("zhang",15,"B"));
            oos.writeObject(new Person("hao",18,"C"));
    
            oos.close();
    
            ObjectInputStream ois = new ObjectInputStream(new BufferedInputStream(
                    new FileInputStream(new File("d:\\test.txt"))));
            Person p1 = (Person) ois.readObject();
            Person p2 = (Person) ois.readObject();
            Person p3 = (Person) ois.readObject();
            ois.close();
            System.out.println(p1);
            System.out.println(p2);
            System.out.println(p3);
        }
    }

    可以發現,自定義類中被transient 標記的數據將不被序列化和反序列化,而且自定義類也必須要實現Serializable接口。

注意:

ObjectOutputStream 對JAVA對象進行序列化處理,處理後的對象不是文本數據。所以數據保存到文件中後,用記事本、寫字板、Word等文本編輯器打開,是無法識別的,一定會顯示亂碼。只有使用相同版本的Java的ObjectInputStream進行讀取操作,方可獲取文件中的對象內容。

序列化時的參數類型和反序列化時的返回類型都是Object類型,所以在反序列化接收類對象數據時要用強制類型轉換。

 

總結上面幾種流的應用場景:

  • FileInputStream/FileOutputStream  需要逐個字節處理原始二進制流的時候使用,效率低下
  • FileReader/FileWriter 需要組個字符處理的時候使用
  • StringReader/StringWriter 需要處理字符串的時候,可以將字符串保存爲字符數組
  • PrintStream/PrintWriter 用來包裝FileOutputStream 對象,方便直接將String字符串寫入文件 
  • Scanner 用來包裝System.in流,很方便地將輸入的String字符串轉換成需要的數據類型
  • InputStreamReader/OutputStreamReader ,  字節和字符的轉換橋樑,在網絡通信或者處理鍵盤輸入的時候用
  • BufferedReader/BufferedWriter , BufferedInputStream/BufferedOutputStream , 緩衝流用來包裝字節流後者字符流,提升IO性能,BufferedReader還可以方便地讀取一行,簡化編程。

參考文章:

https://blog.csdn.net/wintershii/article/details/81281710

https://blog.csdn.net/sj13051180/article/details/47710099

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