1. 程式人生 > >IO流常規操作

IO流常規操作

IO流

IO就是輸入輸出,IO裝置在計算機中起著舉足輕重的作用,IO流也就是輸入輸出流,用來互動資料,程式和程式互動,程式也可以和網路等媒介互動。

一、IO流的分類

要分類,肯定得站得不同角度來看這個問題。

  • 根據流向劃分,分為輸入流和輸出流
  • 根據資料的單位來分劃,分為位元組流和字元流
  • 根據功能劃分,分為節點流和包裝流
  • 還有很多

二、四大基本流

在java中IO流非常之多,但都繼承於這四大基流,分別為,位元組輸入流(InputStream),位元組輸出流(OutputStream),字元輸入流(Reader)和字元輸出流(Writer).

它們都是抽象類,不能建立物件,只能建立它們的子類物件,閱讀API發現,它們都有一個共同的方法close方法,用來關閉流,如果不關閉流的話程式會一直引用著檔案。

三、模板操作

  • 建立源和目標
  • 建立流物件
  • 具體的IO操作
  • 關閉流

在下面的例子中穿插講述

四、檔案流

檔案流操作是是檔案。分為字元流和位元組流,位元組流操作是所有的二進位制檔案包括視訊啊,圖片啊什麼的。字元流主要解決的還是中文的一些問題,因為中文多個位元組(GBK是2個位元組,UTF-8是3個位元組),所有可能出現讀一半或者寫一半就不讀了或者不寫了,這樣就出現亂碼了。具體用字元流還是位元組流得具體選擇,位元組流是可以操作字元流的,但是反過來就可能出現問題。輸入就是從檔案中讀到程式中來,而輸出就是從程式中輸出到檔案中。在這樣章節記住一句話:讀進來,寫出去。下面操作一下各個流的常用方法,

  • 檔案位元組輸入流(FileInputStream)

    int read()  從此輸入流中讀取一個數據位元組。 
    int read(byte[] b) 從此輸入流中將最多 b.length 個位元組的資料讀入一個 byte 陣列中
    int read(byte[] b, int off, int len)從此輸入流中將最多 len 個位元組的資料讀入一個 byte 陣列中

    讀取檔案中所有的資料,用到了一個緩衝陣列,也就是每次讀取5個位元組,直到讀完為止有個len返回當前讀了幾個位元組,如果後面沒有位元組了,那麼它就返回-1

    //讀取檔案中所有的資料
    byte[] buffer = new byte[5];//定義一個緩衝陣列長度為5,每次只能讀取5個位元組
    int len = 0;//當前讀取的位元組數,沒有位元組就返回 -1
    while((len = in.read(buffer))!= -1 ){
        String str = new String(buffer,0,len);
        System.out.println(str);
    }
  • 檔案位元組輸出流(FileOutputStream)

    1.void write(byte[] b) 
    將 b.length 個位元組從指定 byte 陣列寫入此檔案輸出流中。 
    2.void write(byte[] b, int off, int len)
    將指定 byte 陣列中從偏移量 off 開始的 len 個位元組寫入此檔案輸出流。 
    3.void write(int b) 
    將指定位元組寫入此檔案輸出流。 

    輸出就是想檔案中寫資料,可以一次寫一個位元組,也可以寫一個數組中的元素,這個比較簡單。可以用一下四個步驟操作一下

    
    public class FileOutputStreamDemo {
      public static void main(String[] args) throws Exception {
          //1.建立源
          File f = new File("C:/Users/15626/Desktop/test/123.txt");
          //2.建立輸出流物件
          FileOutputStream out = new FileOutputStream(f);
          //3.寫操作
          out.write(97);//只能一個一個的寫
    
          //將buffer陣列中的資料寫在檔案中
          byte[] buffer = "ABCDEF".getBytes();
          out.write(buffer);
    
          //從索引1開始的3個位元組寫在檔案中
          out.write(buffer, 1, 3);
    
          //4.關閉資源
          out.close();
      }
    }
  • 檔案字元輸入流(FileReader)

    字元流的方法是從父類中繼承過來的,也和上面位元組流的方法大同小異

     int read()  讀取單個字元。 
     int read(char[] cbuf, int offset, int length)  將字元讀入陣列中的某一部分。 
    //檔案字元輸入流
    public class FileReaderDemo {
      public static void main(String[] args) throws IOException {
          //1.建立源
          File src = new File("C:/Users/15626/Desktop/新建資料夾 (2)/test.txt");
          //2.建立字元輸入流物件
          FileReader in = new FileReader(src);
          //3.讀操作
          char[] buffer = new char[10];
          int len = -1;
          while((len = in.read(buffer)) != -1){
              String str = new String(buffer,0,len);
              System.out.println(str);
          }
          //4.關閉流
          in.close(); 
      }
    }
  • 檔案字元輸出流(FileWriter)

    void write(char[] cbuf, int off, int len) 寫入字元陣列的某一部分。          
    void write(int c)  寫入單個字元。 
    void write(String str, int off, int len) 寫入字串的某一部分。 

檔案的拷貝操作

把指定目錄的.java檔案拷貝到指定的目錄


//拷貝檔案:把指定目錄的.java檔案拷貝到指定的目錄
public class FileCopyDemo2 {
    public static void main(String []args) throws Exception{
        //得到指定目錄的檔案
        File srcDir = new File("C:/Users/15626/Desktop/新建資料夾 (2)");
        File destDir = new File("C:/Users/15626/Desktop/新建資料夾 (2)/qqq");
        //篩選出字尾是.java的檔案儲存在fs這個檔案陣列中
        File[] fs = srcDir.listFiles(new FilenameFilter(){
            @Override
            public boolean accept(File dir, String name){
                return new File(dir,name).isFile() && name.endsWith(".java");
            }
        });
        //遍歷這些檔案
        for (File srcFile : fs) {
            //1.建立目標
            File destFile = new File(destDir,srcFile.getName());
            //2.建立輸入流和輸出流物件
            FileInputStream in = new FileInputStream(srcFile);
            FileOutputStream out = new FileOutputStream(destFile);
            //3.輸入輸出操作
            byte[] buffer = new byte[10];
            int len = -1;
            while((len = in.read(buffer)) != -1){
                out.write(buffer, 0, len);
            }
            //4.關閉流
            in.close();
            out.close();
        }
    }
}

資料夾的拷貝

https://www.cnblogs.com/tfper/p/9855228.html

檔案流正確關閉資源

https://www.cnblogs.com/tfper/p/9833722.html

五、緩衝流

緩衝流是一個包裝流,目的起緩衝作用.操作流的時候,習慣定義一個byte/char陣列. int read():每次都從磁碟檔案中讀取一個位元組. 直接操作磁碟檔案效能極低,為了解決這個問題,我們 定義一個數組作為緩衝區. byte[] buffer = new byte[1024]; 該陣列其實就是一個緩衝區. 一次性從磁碟檔案中讀取1024個位元組. 如此以來,操作磁碟檔案的次數少了,效能得以提升,java提供的預設快取區大小是8192(1024*8),我們一般不用修改大小.

緩衝流也有四種,分別是 BufferedInputStream,BufferedOutputStream,BufferedReader,BufferedWriter.操作方法都和檔案流差不多,我們來看一下緩衝流到底快了多少?

/**
 * @author 15626
 *  操作字元或者位元組流都應該用緩衝流包裝起來,現在比較一下用緩衝流和不用緩衝流的情況寫,複製一個檔案所花費時間
 *  長度的比較
 */
public class BufferCompare {
    public static void main(String[] args) throws Exception {
        File srcFile = new File("C:/Users/15626/Desktop/File/test.pdf");
        File destFile = new File("C:/Users/15626/Desktop/File/copy.pdf");
        //test1(srcFile,destFile);//不用緩衝流包裝       
        test2(srcFile,destFile);//用緩衝流包裝       
    }
    //使用緩衝流,從記憶體每次讀取1024個位元組:15ms
    public static void test2(File srcFile, File destFile) throws Exception {
        long begin = System.currentTimeMillis();
        BufferedInputStream in = new BufferedInputStream(new FileInputStream(srcFile));
        BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(destFile));
    
        byte[] buffer = new byte[1024];
        int len = -1;
        while((len = in.read(buffer)) != -1){
            out.write(buffer, 0, len);
        }
        in.close();
        out.close();
        System.out.print(System.currentTimeMillis() - begin);
    }

    //使用位元組流,從磁碟中每次讀取1024個位元組:38ms
    public static void test1(File srcFile,File destFile) throws Exception {
        long begin = System.currentTimeMillis();
        FileInputStream in = new FileInputStream(srcFile);
        FileOutputStream out = new FileOutputStream(destFile);
        byte[] buffer = new byte[1024];
        int len = -1;
        while((len = in.read(buffer)) != -1){
            out.write(buffer, 0, len);
        }
        in.close();
        out.close();
        System.out.print(System.currentTimeMillis() - begin);
    }
}

緩衝流的效率很高,所有以後操作字元位元組流都可以用緩衝流包裝起來。

六、記憶體流

記憶體流就是把程式中的資料暫存在記憶體中,在這裡也就是陣列中,或者把記憶體中的資料輸出到其他媒介,記憶體流也是分為位元組流,字元流還有一個字串流(儲存在字串中)。簡單操作一下,熟悉一個方法

import java.io.*;
/**
 * @author 15626
 *  位元組記憶體流,或者說是位元組陣列流
 *  位元組記憶體輸入流 : ByteArrayOutputStream
 *  位元組記憶體輸出流 : ByteArrayInputStream
 */
public class ByteArrayStreamDemo {
    public static void main(String[] args) throws Exception {
        //位元組記憶體輸出流:把程式的內容寫到記憶體中的byte陣列中
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        out.write("ABSAK".getBytes());
        //把程式中的資料存在了緩衝陣列中,toByteArray()方法用來獲取緩衝區的資料
        byte[] buffer = out.toByteArray();
        
        //位元組記憶體輸入流: 把記憶體中的資料讀到程式中
        ByteArrayInputStream in = new ByteArrayInputStream(buffer);
        byte[] by = new byte[1024];
        int len = -1;
        while((len = in.read(by)) != -1){
            System.out.print(new String(by,0,len));
        }
        
        //關閉流無效,因為本身就在儲存在記憶體中
        in.close();
        out.close();
    }
}
/**
 * @author 15626
 *  字元記憶體流或者說是字元陣列流
 *  字元記憶體輸出流:CharArrayWriter
 *  字元記憶體輸入流:CharArrayReader
 */
public class CharArrayReaderWriterDemo {
    public static void main(String[] args) throws Exception {
        //字元記憶體輸出流 : 從程式把資料儲存到記憶體中的一個緩衝陣列中
        CharArrayWriter cWriter = new CharArrayWriter();
        cWriter.write("你是天邊最美的雲彩");
        //獲取緩衝陣列中的內容,用toCharArray()方法獲取
        char[] ch= cWriter.toCharArray();
        
        //字元記憶體輸入流:從記憶體中被資料讀取到程式中來
        CharArrayReader sReader = new CharArrayReader(ch);
        char[] buffer = new char[1024];
        int len = -1;
        while((len = sReader.read(buffer)) != -1){
            System.out.println(new String(buffer,0,len));
        }
        
        
        //關閉流無效,因為本身就在儲存在記憶體中
        sReader.close();
        cWriter.close();
    }
}
/**
 * @author 15626
 *  字串的記憶體流,把程式中的資料儲存在一個緩衝字串中
 *  字串的輸出流 :StringWriter
 *  字串的輸入流:StringReader
 */
public class StringWriterReaderDemo {
    public static void main(String[] args) throws Exception {
        //字串的輸出流
        StringWriter out = new StringWriter();
        out.write("你真的想成為一隻帥帥氣氣的一隻小人嗎");
        //獲取緩衝區的字串
        //System.out.print(out.toString());
        
        //字串的輸入流
        StringReader in = new StringReader("海納百川,有容乃大");
        char[] buffer = new char[1024];
        int len = -1;
        while((len = in.read(buffer)) != -1){
            System.out.print(new String(buffer,0,len));
        }
        
        //關閉流無效,因為本身就在儲存在記憶體中
        in.close();
        out.close();
    }
}

要注意的關閉流無效,但是為了追求完美的格式,防止忘記了其他流的關閉,我還還是自娛自樂的關一下吧!

七、物件流

物件流包括ObjectInputStream和ObjectOutputStream,名字起得很有範,肯定是位元組流

一般用物件流進行序列化和反序列化通過writeObject方法做序列化操作的,通過readObject方法做反序列化操作

1.序列化

指把堆記憶體中的Java物件資料,通過某種方式把物件儲存到磁碟檔案中或者傳遞給其他網路的節點(在網路上傳輸). 我們把這個過程稱之為序列化.

需要做序列化的物件的類,必須實現序列化介面:java.io.Serializable介面(標誌介面[沒有抽象方法]). 底層會判斷,如果當前物件是Serializable的例項,才允許做序列化.大多數類都已經實現了這個介面了

2.反序列化

把磁碟檔案中的物件資料或者把網路節點上的物件資料,恢復成Java物件的過程.

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

/**
 * @author 15626
 *  物件流的序列化和反序列化操作
 *  ObjectInputStream 用WriterObjcet 來做序列化
 *  ObjectOutputStream 用ReaderObject 來做反序列化
 *  需要做序列化的類必須實現Serializable介面 
 */
public class ObjectStreamDemo {
    public static void main(String[] args) throws Exception {
        File f = new File("file/111.txt");
        writeObject(f);//序列化
        readObject(f);//反序列化
    }
    public static void readObject(File f) throws Exception{
        ObjectInputStream in = new ObjectInputStream(new FileInputStream(f));
        Student s = (Student)in.readObject();
        System.out.println(s);
        in.close();
        
    } 
    public static void writeObject(File f) throws Exception {
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(f));
        out.writeObject(new Student("小夥子",19,"12345"));
        out.close();
    }
}
class Student implements Serializable {
    /**
     * 做反序列化操作必須存在序列化的位元組碼檔案,由於隨著版本升級肯定位元組碼會發生改變,java提供了序列化版本ID,
     * 可以固定這一份位元組碼檔案,解決了這個問題
     */
    private static final long serialVersionUID = 1L;
    private String name;
    private int age;
    //密碼一般不會做序列化的,靜態的欄位和瞬態的欄位不能做序列化,所以給密碼加上瞬態transient修飾符
    //反序列化的結果就為null
    transient private String password;
    @Override
    public String toString() {
        return "Student [name=" + name + ", age=" + age + ", password=" + password + "]";
    }
    public Student(String name, int age, String password) {
        super();
        this.name = name;
        this.age = age;
        this.password = password;
    }
}

可以看到裡面有兩個重要的問題,一個是反序列化物件需要物件的位元組碼檔案,需要提供序列化ID固定這份位元組碼檔案,還有一個是transient瞬息欄位是不能序列化.

八、列印流

1.位元組列印流

import java.io.File;
import java.io.PrintStream;

/**
 * @author 15626
 * 位元組列印流
 *  可以自動重新整理的,不想API說的那樣,必須用換行符或者println才可以重新整理緩衝區
 */
public class PrintStreamDemo {
    public static void main(String[] args) throws Exception {
        File f = new File("file/123.txt");
        PrintStream p = new PrintStream(f);
        p.print("aa");
        p.println(121);
        p.append("a");
        p.close();
        
//      System.out.println();  //這行程式碼等價於下面的兩行程式碼
//      PrintStream ps = System.out;
//      ps.println("a");
    }
}

2.字元列印流

/**
 * @author 15626
 *  字元列印流
 *  當啟用自動重新整理後,即new PrintWriter(new FileOutputStream(f),true);   true
 *  呼叫println方法或者printf或者\n後進行重新整理。
 *
 *  當不啟動自動重新整理,需要手動重新整理,呼叫flush方法,或者close方法會進行重新整理
 */
public class PrintWriterDemo {
    public static void main(String[]args) throws Exception{
        File f = new File("file/123.txt");
        PrintWriter p = new PrintWriter(new FileOutputStream(f),true);
        p.print("wqe");
        p.println();
        p.close();
    }
}