1. 程式人生 > >高階Java工程師必備 ----- 深入分析 Java IO (三)

高階Java工程師必備 ----- 深入分析 Java IO (三)

概述

Java IO即Java 輸入輸出系統。不管我們編寫何種應用,都難免和各種輸入輸出相關的媒介打交道,其實和媒介進行IO的過程是十分複雜的,這要考慮的因素特別多,比如我們要考慮和哪種媒介進行IO(檔案、控制檯、網路),我們還要考慮具體和它們的通訊方式(順序、隨機、二進位制、按字元、按字、按行等等)。Java類庫的設計者通過設計大量的類來攻克這些難題,這些類就位於java.io包中。

在JDK1.4之後,為了提高Java IO的效率,Java又提供了一套新的IO,Java New IO簡稱Java NIO。它在標準java程式碼中提供了高速的面向塊的IO操作。本篇文章重點介紹Java IO,關於Java NIO請參考我的另兩篇文章: 

高階Java工程師必備 ----- 深入分析 Java IO (一)BIO

高階Java工程師必備 ----- 深入分析 Java IO (二)NIO

Java IO類庫的框架

首先看個圖:

 Java IO的型別

雖然java IO類庫龐大,但總體來說其框架還是很清楚的。從是讀媒介還是寫媒介的維度看,Java IO可以分為:

  1. 輸入流:InputStream和Reader
  2. 輸出流:OutputStream和Writer

而從其處理流的型別的維度上看,Java IO又可以分為:

  1. 位元組流:InputStream和OutputStream
  2. 字元流:Reader和Writer

下面這幅圖就清晰的描述了JavaIO的分類:

-位元組流字元流
輸入流 InputStream Reader
輸出流 OutputStream Writer

我們的程式需要通過InputStream或Reader從資料來源讀取資料,然後用OutputStream或者Writer將資料寫入到目標媒介中。其中,InputStream和Reader與資料來源相關聯,OutputStream和writer與目標媒介相關聯。

Java IO的基本用法

Java IO :位元組流

通過上面的介紹我們已經知道,位元組流對應的類應該是InputStream和OutputStream,而在我們實際開發中,我們應該根據不同的媒介型別選用相應的子類來處理。下面我們就用位元組流來操作檔案媒介:

例1,用位元組流寫檔案

public static void writeByteToFile() throws IOException{
    String hello= new String( "hello word!");
     byte[] byteArray= hello.getBytes();
    File file= new File( "d:/test.txt");
     //因為是用位元組流來寫媒介,所以對應的是OutputStream 
     //又因為媒介物件是檔案,所以用到子類是FileOutputStream
    OutputStream os= new FileOutputStream( file);
     os.write( byteArray);
     os.close();
}

例2,用位元組流讀檔案

public static void readByteFromFile() throws IOException{
    File file= new File( "d:/test.txt");
     byte[] byteArray= new byte[( int) file.length()];
     //因為是用位元組流來讀媒介,所以對應的是InputStream
     //又因為媒介物件是檔案,所以用到子類是FileInputStream
    InputStream is= new FileInputStream( file);
     int size= is.read( byteArray);
    System. out.println( "大小:"+size +";內容:" +new String(byteArray));
     is.close();
}

CopyFileDemo

package com.chenhao.io.byteIO;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * @author ChenHao
 *
 */
public class CopyFileDemo {

    /**
     * @param args
     * @throws FileNotFoundException 
     */
    public static void main(String[] args) {
        String src ="E:/xp/test";
        String dest="e:/xp/test/4.jpg";
        try {
            copyFile(src,dest);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            System.out.println("檔案不存在");
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("拷貝檔案失敗|關閉流失敗");
        }
    }
    /**
     * 檔案的拷貝
     * @param  原始檔路徑
     * @param  目錄檔案路徑
     * @throws FileNotFoundException,IOException
     * @return 
     */
    public static void copyFile(String srcPath,String destPath) throws FileNotFoundException,IOException {
        //1、建立聯絡 源(存在且為檔案) +目的地(檔案可以不存在)  
        File src =new File(srcPath);
        File dest =new File(destPath);
        if(! src.isFile()){ //不是檔案或者為null
            System.out.println("只能拷貝檔案");
            throw new IOException("只能拷貝檔案");
        }
        //2、選擇流
        InputStream is =new FileInputStream(src);
        OutputStream os =new FileOutputStream(dest);
        //3、檔案拷貝   迴圈+讀取+寫出
        byte[] flush =new byte[1024];
        int len =0;
        //讀取
        while(-1!=(len=is.read(flush))){
            //寫出
            os.write(flush, 0, len);
        }
        os.flush(); //強制刷出
        
        //關閉流
        os.close();
        is.close();
    }

}

Java IO :字元流

同樣,字元流對應的類應該是Reader和Writer。下面我們就用字元流來操作檔案媒介:

例3,用字元流讀檔案

public static void writeCharToFile() throws IOException{
    String hello= new String( "hello word!");
    File file= new File( "d:/test.txt");
    //因為是用字元流來讀媒介,所以對應的是Writer,又因為媒介物件是檔案,所以用到子類是FileWriter
    Writer os= new FileWriter( file);
    os.write( hello);
    os.close();
}

例4,用字元流寫檔案

public static void readCharFromFile() throws IOException{
    File file= new File( "d:/test.txt");
    //因為是用字元流來讀媒介,所以對應的是Reader
    //又因為媒介物件是檔案,所以用到子類是FileReader
    Reader reader= new FileReader( file);
    char [] byteArray= new char[( int) file.length()];
    int size= reader.read( byteArray);
    System. out.println( "大小:"+size +";內容:" +new String(byteArray));
    reader.close();
}

Java IO :位元組流轉換為字元流

位元組流可以轉換成字元流,java.io包中提供的InputStreamReader類就可以實現,當然從其命名上就可以看出它的作用。其實這涉及到另一個概念,IO流的組合,後面我們詳細介紹。下面看一個簡單的例子:

例5 ,位元組流轉換為字元流

public static void convertByteToChar() throws IOException{
    File file= new File( "d:/test.txt");
    //獲得一個位元組流
    InputStream is= new FileInputStream( file);
    //把位元組流轉換為字元流,其實就是把字元流和位元組流組合的結果。
    Reader reader= new InputStreamReader( is);
    char [] byteArray= new char[( int) file.length()];
    int size= reader.read( byteArray);
    System. out.println( "大小:"+size +";內容:" +new String(byteArray));
    is.close();
    reader.close();
}

Java IO:檔案媒介操作

例6 ,File操作

public class FileDemo {
  public static void main(String[] args) {
         //檢查檔案是否存在
        File file = new File( "d:/test.txt");
         boolean fileExists = file.exists();
        System. out.println( fileExists);
         //建立檔案目錄,若父目錄不存在則返回false
        File file2 = new File( "d:/fatherDir/subDir");
         boolean dirCreated = file2.mkdir();
        System. out.println( dirCreated);
         //建立檔案目錄,若父目錄不存則連同父目錄一起建立
        File file3 = new File( "d:/fatherDir/subDir2");
         boolean dirCreated2 = file3.mkdirs();
        System. out.println( dirCreated2);
        File file4= new File( "d:/test.txt");
         //判斷長度
         long length = file4.length();
         //重新命名檔案
         boolean isRenamed = file4.renameTo( new File("d:/test2.txt"));
         //刪除檔案
         boolean isDeleted = file4.delete();
        File file5= new File( "d:/fatherDir/subDir");
         //是否是目錄
         boolean isDirectory = file5.isDirectory();
         //列出檔名
        String[] fileNames = file5.list();
         //列出目錄
        File[]   files = file4.listFiles();
  }
}

隨機讀取File檔案

通過上面的例子我們已經知道,我們可以用FileInputStream(檔案字元流)或FileReader(檔案位元組流)來讀檔案,這兩個類可以讓我們分別以字元和位元組的方式來讀取檔案內容,但是它們都有一個不足之處,就是隻能從檔案頭開始讀,然後讀到檔案結束。

但是有時候我們只希望讀取檔案的一部分,或者是說隨機的讀取檔案,那麼我們就可以利用RandomAccessFile。RandomAccessFile提供了seek()方法,用來定位將要讀寫檔案的指標位置,我們也可以通過呼叫getFilePointer()方法來獲取當前指標的位置,具體看下面的例子:

例7,隨機讀取檔案

public static void randomAccessFileRead() throws IOException {
     // 建立一個RandomAccessFile物件
    RandomAccessFile file = new RandomAccessFile( "d:/test.txt", "rw");
     // 通過seek方法來移動讀寫位置的指標
     file.seek(10);
     // 獲取當前指標
     long pointerBegin = file.getFilePointer();
     // 從當前指標開始讀
     byte[] contents = new byte[1024];
     file.read( contents);
     long pointerEnd = file.getFilePointer();
    System. out.println( "pointerBegin:" + pointerBegin + "\n" + "pointerEnd:" + pointerEnd + "\n" + new String(contents));
     file.close();
}

例8,隨機寫入檔案

public static void randomAccessFileWrite() throws IOException {
     // 建立一個RandomAccessFile物件
     RandomAccessFile file = new RandomAccessFile( "d:/test.txt", "rw");
     // 通過seek方法來移動讀寫位置的指標
     file.seek(10);
     // 獲取當前指標
     long pointerBegin = file.getFilePointer();
     // 從當前指標位置開始寫
     file.write( "HELLO WORD".getBytes());
     long pointerEnd = file.getFilePointer();
     System. out.println( "pointerBegin:" + pointerBegin + "\n" + "pointerEnd:" + pointerEnd + "\n" );
     file.close();
}

Java IO:BufferedInputStream和BufferedOutputStream

BufferedInputStream顧名思義,就是在對流進行寫入時提供一個buffer來提高IO效率。在進行磁碟或網路IO時,原始的InputStream對資料讀取的過程都是一個位元組一個位元組操作的,而BufferedInputStream在其內部提供了一個buffer,在讀資料時,會一次讀取一大塊資料到buffer中,這樣比單位元組的操作效率要高的多,特別是程序磁碟IO和對大量資料進行讀寫的時候,能提升IO效能。

使用BufferedInputStream十分簡單,只要把普通的輸入流和BufferedInputStream組合到一起即可。我們把上面的例2改造成用BufferedInputStream進行讀檔案,請看下面例子:

例10 ,用緩衝流讀檔案

public static void readByBufferedInputStream() throws IOException {
     File file = new File( "d:/test.txt");
     byte[] byteArray = new byte[( int) file.length()];
     //可以在構造引數中傳入buffer大小
     InputStream is = new BufferedInputStream( new FileInputStream(file),2*1024);
     int size = is.read( byteArray);
     System. out.println( "大小:" + size + ";內容:" + new String(byteArray));
     is.close();
}

BufferedOutputStream的情況和BufferedInputStream一致,在這裡就不多做描述了。

copyFile

package com.chenhao.io.buffered;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
 * 位元組流檔案拷貝+緩衝流 ,提高效能
 * 緩衝流(節點流)
 * @author ChenHao
 *
 */
public class BufferedByteDemo {

    public static void main(String[] args) {
        String src ="E:/xp/test";
        String dest="e:/xp/test/4.jpg";
        try {
            copyFile(src,dest);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            System.out.println("檔案不存在");
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("拷貝檔案失敗|關閉流失敗");
        }
    }
    /**
     * 檔案的拷貝
     * @param  原始檔路徑
     * @param  目錄檔案路徑
     * @throws FileNotFoundException,IOException
     * @return 
     */
    public static void copyFile(String srcPath,String destPath) throws FileNotFoundException,IOException {
        //1、建立聯絡 源(存在且為檔案) +目的地(檔案可以不存在)  
        File src =new File(srcPath);
        File dest =new File(destPath);
        if(! src.isFile()){ //不是檔案或者為null
            System.out.println("只能拷貝檔案");
            throw new IOException("只能拷貝檔案");
        }
        //2、選擇流
        InputStream is =new BufferedInputStream(new FileInputStream(src));
        OutputStream os =new BufferedOutputStream( new FileOutputStream(dest));
        //3、檔案拷貝   迴圈+讀取+寫出
        byte[] flush =new byte[1024];
        int len =0;
        //讀取
        while(-1!=(len=is.read(flush))){
            //寫出
            os.write(flush, 0, len);
        }
        os.flush(); //強制刷出
        
        //關閉流
        os.close();
        is.close();
    }

}

Java IO:BufferedReader和BufferedWriter

BufferedReader、BufferedWriter 的作用基本和BufferedInputStream、BufferedOutputStream一致,具體用法和原理都差不多 ,只不過一個是面向字元流一個是面向位元組流。同樣,我們將改造字元流中的例4,給其加上buffer功能,看例子:

public static void readByBufferedReader() throws IOException {
     File file = new File( "d:/test.txt");
     // 在字元流基礎上用buffer流包裝,也可以指定buffer的大小
     Reader reader = new BufferedReader( new FileReader(file),2*1024);
     char[] byteArray = new char[( int) file.length()];
     int size = reader.read( byteArray);
     System. out.println( "大小:" + size + ";內容:" + new String(byteArray));
     reader.close();
}

另外,BufferedReader提供一個readLine()可以方便地讀取一行,而FileInputStream和FileReader只能讀取一個位元組或者一個字元,因此BufferedReader也被稱為行讀取器.

public static void keyIn() throws IOException {
 try (//InputStreamReader是從byte轉成char的橋樑
      InputStreamReader reader = new InputStreamReader(System.in);
      //BufferedReader(Reader in)是char型別輸入的包裝類
      BufferedReader br = new BufferedReader(reader);) {
         
         String line = null;
         while ((line = br.readLine()) != null) {
             if (line.equals("exit")) {
                 //System.exit(1);
                 break;
             }
             System.out.println(line);
         }
     } catch (IOException e) {
         e.printStackTrace();
     }
}

Java IO: 序列化與ObjectInputStream、ObjectOutputStream

Serializable

如果你希望類能夠序列化和反序列化,必須實現Serializable介面,就像所展示的ObjectInputStream和ObjectOutputStream例子一樣。

ObjectInputStream

ObjectInputStream能夠讓你從輸入流中讀取Java物件,而不需要每次讀取一個位元組。你可以把InputStream包裝到ObjectInputStream中,然後就可以從中讀取物件了。程式碼如下:

ObjectInputStream input = new ObjectInputStream(new FileInputStream("object.data"));
MyClass object = (MyClass) input.readObject(); //etc.
input.close();

在這個例子中,你讀取的物件必須是MyClass的一個例項,並且必須事先通過ObjectOutputStream序列化到“object.data”檔案中。

在你序列化和反序列化一個物件之前,該物件的類必須實現了java.io.Serializable介面。

ObjectOutputStream

ObjectOutputStream能夠讓你把物件寫入到輸出流中,而不需要每次寫入一個位元組。你可以把OutputStream包裝到ObjectOutputStream中,然後就可以把物件寫入到該輸出流中了。程式碼如下:

ObjectOutputStream output = new ObjectOutputStream(new FileOutputStream("object.data"));
MyClass object = new MyClass();  output.writeObject(object); //etc.
output.close();

例子中序列化的物件object現在可以從ObjectInputStream中讀取了。

同樣,在你序列化和反序列化一個物件之前,該物件的類必須實現了java.io.Serializable介面。