1. 程式人生 > >Java中的輸入/輸出

Java中的輸入/輸出

位元組流和字元流

位元組流和字元流的操作幾乎完全一樣,不同的是位元組流操作的資料單元是位元組,而字元流操作的是字元。

InputStream和Reader

InputStream和Reader是所有輸入流的抽象基類。

在InputStream裡包含的三個方法:

  • int read():從輸入流中讀取單個位元組,返回所讀取的位元組資料。
  • int read(byte[] b):從輸入流中最多讀取b.length個位元組的資料,並將其儲存在位元組陣列中,返回實際讀取的位元組數;
  • int read(byte[] b,int off,int len):從輸入流中最多讀取len個位元組的資料,並將其儲存在位元組陣列中,並不是從陣列起點開始,而是從off位置開始,返回實際讀取的位元組數;
public class FileInputStreamdemo {
    public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream("FileInputStreamdemo.java");

        //建立byte陣列,長度為1024
        byte[] bytes = new byte[1024];
        //用於儲存實際讀取的位元組數
        int hasRead=0;
        while ((hasRead=fis.read(bytes))>0){
            System.out.println(new String(bytes,0,hasRead));
        }
        //關閉檔案輸入流
        fis.close();
    }

在Reader裡包含三個方法:

  • int read():從輸入流中讀取單個字元,返回所讀取的字元資料。
  • int read(char[] c):從輸入流中最多讀取c.length個字元的資料,並將其儲存在字元陣列中,返回實際讀取的字元數;
  • int read(char[] b,int off,int len):從輸入流中最多讀取len個字元的資料,並將其儲存在字元陣列中,並不是從陣列起點開始,而是從off位置開始,返回實際讀取的字元數;

除此之外,InputStream和Reader還支援幾個方法來移動記錄指標:

  • void mark(int readAheadLimit)
    :在記錄指標當前位置記錄一個標記;
  • boolean markSupported():判斷此流是否支援mark()操作,是否支援記錄標記;
  • void reset():將此流的記錄指標重新定位到上一次記錄標記的位置;
  • long skip(long n):記錄指標向前移動n個位元組/字元。

OutputStream和Writer

都提供了以下方法:

  • void write(int c):將指定位元組/字元輸出到輸出流中,c既可以是位元組,也可以是字元。
  • void write(byte[]/char[] buf):將位元組陣列/字元陣列的資料輸出到指定輸出流中。
  • void write(byte[]/char[] buf,int off,int len):將位元組陣列/字元陣列中從off開始,長度為len的資料輸出到指定輸出流中。

除此之外Writer還包含了兩個方法:

  • void write(String str)將str字串包含的字元輸出到指定輸出流中。
  • void write(String str,int off,int len):將str字串裡從off位置開始,長度為len的字元輸出到指定輸出流中。

例項:複製檔案

    public static void main(String[] args) throws IOException {
        FileInputStream in = new FileInputStream("FileInputStreamdemo.java");
        FileOutputStream out = new FileOutputStream("javafile.txt");
        byte[] bytes = new byte[1024];
        int len=0;
        while ((len=in.read(bytes))!=-1){
            out.write(bytes);
        }
        in.close();
        out.close();
    }

如果想直接輸出字串內容,則使用Writer更好。

    public static void main(String[] args) throws IOException {
        //true表示是否追加內容,如果為false則覆蓋之前的內容
        FileWriter fw = new FileWriter("abcd.txt",true);
        fw.write("床前明月光,疑是地上霜。\r\n");
        fw.write("舉頭望明月,低頭思故鄉. \r\n");
        fw.close();
    }

輸入/輸出流體系

處理流的用法

處理流可以隱藏底層裝置上節點流的差異,並對外提供更加方便的輸入/輸出方法,讓程式設計師只需關心高階流的操作。
實際識別處理流非常簡單,只要流的構造器引數不是一個物理節點,而是已經存在的流,那麼這個流就是處理流。

    public static void main(String[] args) {
        try (
            FileOutputStream fos = new FileOutputStream("test.txt");
            PrintStream ps = new PrintStream(fos);
        ){
            ps.println("普通字串");
            ps.println(new PrintStreamdemo());
        }
        catch (IOException ioe){
            ioe.printStackTrace();
        }
    }

PrintStream ps = new PrintStream(fos);像這種構造器引數為已存在流,而不是物理節點的流,就是處理流。

輸入/輸出流體系

在這裡插入圖片描述

字串流

    public static void main(String[] args) {
        String str="我想要一座房子 \n"+
                "面朝大海 \n"+
                "那裡海風呼嘯 \n"+
                "沒有煩惱 \n";

        char[] buffer = new char[32];
        int hasRead=0;

        try (StringReader sr = new StringReader(str)){
            while ((hasRead=sr.read(buffer))>0){
                System.out.println(new String(buffer,0,hasRead));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        //這裡實際上是以一個StringBuffer作為輸出節點
        try (StringWriter sw = new StringWriter()){
            sw.write("等愛的人很多,\n");
            sw.write("不預設你會在乎我\n");
            System.out.println(sw.toString());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

以字串作為物理節點。

轉換流

輸入/輸出流體系中還提供了兩個轉換流,這兩個轉換流用於實現將位元組流傳換成字元流。
例項:獲取鍵盤輸入

Java使用System.in代表標準輸入,但這個標準輸入是InputStream類的例項,使用不太方便,而且鍵盤輸入內容都是文字內容,所以可以使用InputStreamReader將其轉化成字元輸入流,可以將普通的Reader再次包裝成BufferedReader,利用它的readLine()方法可以一次性獲取一行。

    public static void main(String[] args) {
        try (
                //將System.in轉換成Reader物件
                InputStreamReader reader = new InputStreamReader(System.in);
                //將普通Reader包裝成BufferedReader
                BufferedReader br = new BufferedReader(reader);
                ){
            String line=null;
            while ((line=br.readLine())!=null){
                //如果讀取的字串是exit則程式退出
                if(line.equals("exit")){
                    System.exit(1);
                }
                System.out.println("輸入的內容為:"+line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

BufferedReader具有緩衝功能,他可以一次讀取一行文字,以換行符為標誌。

推回輸入流

在輸入/輸出流體系中,有兩個特殊的流與眾不同,就是PushbackInputStream和PushbackReader,他們都提供瞭如下三個方法:

  • void unread(byte[]/char[] buf):將一個位元組/字元陣列內容推回到推回緩衝區裡,從而允許重複讀取剛剛讀取的內容。
  • void unread(byte[]/char[] buf,int off,int len):將一個位元組/字元數組裡從off開始,長度為len位元組/字元推回到推回緩衝區裡,從而允許重複讀取剛剛讀取的內容。
  • void unread(int b):將一個位元組/字元推回到推回緩衝區裡,從而允許重複讀取剛剛讀取的內容。

這兩個推回輸入流都提供了一個推回緩衝區當程式呼叫這兩個推回輸入流的unread()方法時,系統將會把指定陣列的內容推回到該緩衝區裡,而推回輸入流每次呼叫read()方法時總是先從推回緩衝區讀取,只有完全讀取了推回緩衝區的內容後,但還沒有裝滿read()所需陣列時才會從原輸入流中讀取。

當程式建立一個推回緩衝流時需要指定推回緩衝區的大小,預設為1,如果推回內容大於推回緩衝區大小時,則會引發Pashback buffer overflow 的IOException異常。

例項:
試圖找出程式中“new PushbackReader”字串,找到後,程式只是打印出目標字串之前的內容。

package org.westos.demo7;

import java.io.*;

public class PushbaskTest {
    public static void main(String[] args) {
        try (
                //建立一個PushbackReader,指定推回緩衝區大小為64
                PushbackReader pr = new PushbackReader(new FileReader("PushbaskTest.java"), 64);
                ){
            char[] buf = new char[32];
            //用以儲存上次讀取的字串內容
            String lastContent="";
            int hasRead=0;
            while ((hasRead=pr.read(buf))>0){
                //將讀取的內容轉換為字串
                String content = new String(buf, 0, hasRead);
                //將上次讀取的內容和本次讀取的字串拼起來
                //檢視是否包含目標字串
                int targetIndex=0;
                if ((targetIndex=(lastContent+content).indexOf("new PushbackReader"))>0){
                    //將兩次的內容推回到推回緩衝區
                    pr.unread((lastContent+content).toCharArray());
                    //重新定義一個長度為targetIndex的char陣列
                    if(targetIndex>32){
                         buf=new char[targetIndex];
                    }
                    //再次讀取指定長度的內容,
                    pr.read(buf,0,targetIndex);
                    System.out.println(new String(buf,0,targetIndex));
                    System.exit(0);
                }else {
                    System.out.println(lastContent);
                    //將本次內容設為上次讀取的內容
                    lastContent=content;
                }
            }
        }  catch (IOException e) {
            e.printStackTrace();
        }
    }
}

重定向標準輸入/輸出

在java中預設的標準輸出和標準輸入分別是螢幕和鍵盤。
在System類裡提供了三個重定向標準輸入、輸出的方法:

  • static void setErr(PrintStream err):重定向“標準”錯誤輸出流;
  • static void setIn(InputStream in):重定向“標準”輸入流;
  • static void setOut(PrintStream out):重定向“標準”輸出流;

例項:
將System.out的輸出重定向到檔案輸出,而不是在螢幕輸出;

package org.westos.demo7;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;

public class RedirectOut {
    public static void main(String[] args) {

        try(
                //一次性建立PrintStream輸出流
                PrintStream ps = new PrintStream(new FileOutputStream("out.txt"));
                ) {
            //將標準輸出流重定向到ps輸出流
            System.setOut(ps);
            //下面的輸出將輸出到out.txt檔案中
            System.out.println("普通字串");
            System.out.println(new RedirectOut());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }
}

例項:
重定向標準輸入,從而可以將System.in重定向到指定檔案,而不是鍵盤輸入。

package org.westos.demo7;

import java.io.*;
import java.util.Scanner;

public class RedirectIn {
    public static void main(String[] args) {


        try (
                //一次性建立PrintStream輸出流
                FileInputStream fis = new FileInputStream("out.txt");
        ) {
            //將標準輸入流重定向到ps輸出流
            System.setIn(fis);
            //獲取標準輸入
            Scanner sc = new Scanner(System.in);
            //判斷是否還有下一行
            while (sc.hasNext()) {
                System.out.println("鍵盤輸入的內容是:" + sc.next());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Java虛擬機器讀寫其他程序的資料

使用Runtime物件的exec()方法可以執行平臺上的其他程式,該方法產生一個Process物件,Process物件代表由該Java程式啟動的子程序。
Process類提供瞭如下三個方法用於讓程式和其他子程序進行通訊。

  • InputStream getErrorStream():獲取子程序的錯誤流;
  • InputStream getInputStream():獲取子程序的輸入流;
  • OutputStream getOutputStream():獲取子程序的輸出流;

例項:
讀取其他程序的輸出資訊

    public static void main(String[] args) throws IOException {
        //執行javac命令,返回執行該命令的子程序
        Process p = Runtime.getRuntime().exec("javac");

        try(
                //以p程序的錯誤流建立BufferedReader物件
                //這個錯誤流對本程式是輸入流,對p程序則是輸出流
                BufferedReader br = new BufferedReader(new InputStreamReader(p.getErrorStream()));
                )
        {
            String buff=null;
            while ((buff=br.readLine())!=null){
                System.out.println(buff);
            }
        }
    }

不僅如此,也可以通過Process的getOutputStream()方法獲取向程序輸入資料的流,該流對程式來說是輸出流,對子程序來說是輸入流。

RandomAccessFile

RandomAccessFile是java輸入/輸出流體系中功能最豐富的檔案內容訪問類,它提供了眾多的方法來訪問檔案內容。與普通的輸入/輸出流不同的是RandomAccessFile支援“隨機訪問”的方式,程式可以直接跳轉到檔案的任意地方來訪問資料。
RandomAccessFile允許自由定位檔案記錄指標,可以不從開始的地方開始輸出。包含的指標用以標識當前讀寫處的位置,當程式新建立一個RandomAccessFile物件時,該物件檔案記錄指標位於檔案頭,當讀/寫了n個位元組後,檔案記錄指標將會向後移動n個位元組。除此之外,RandomAccessFile還可以自由移動記錄指標。

RandomAccessFile包含了如下兩個方法來操作檔案記錄指標:

  • long getFilePointer():返回檔案記錄指標的當前位置;
  • void seek(long pos):將檔案記錄指標定到pos位置。

RandomAccessFile可以讀寫檔案,所以既包含類似於InputStream的三個read()方法,也包含類似於OutputStream的三個write()方法。

RandomAccessFile類有兩個構造器,一個用String引數來指定檔名,一個使用File引數來指定檔案本身。建立RandomAccessFile物件時還需要一個引數,該引數具有4個值:

  • “r”:以只讀的方式開啟指定檔案。如果試圖向其中寫入資料則引發IO異常;
  • “rw”:以讀寫的方式開啟指定檔案。
  • “rws”:以讀寫的方式開啟指定檔案,與上面的引數不同的是,還要求對檔案的內容或元資料的每個更新都同步寫入到底層儲存裝置。
  • “rwd”:同上;

例項:
用RandomAccessFile來訪問指定的中間部分資料:

package org.westos.demo7;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;

public class RandomAccessFileTest {
    public static void main(String[] args) {

        try(
                RandomAccessFile r = new RandomAccessFile("RandomAccessFileTest.java", "r");
                ) {
            //獲取RandomAccessFile物件檔案指標位置,初始位置是0
            System.out.println("檔案指標的初始位置:"+r.getFilePointer());
            //移動r的檔案記錄指標位置
            r.seek(300);

            byte[] buf = new byte[1024];
            int hasRead=0;
            while ((hasRead=r.read(buf))>0){
                System.out.println(new String(buf,0,hasRead));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

例項:
向指定檔案後追加內容,程式先將指標移動到最後位置,然後開始向檔案中輸出內容。

package org.westos.demo7;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;

public class AppendContent {
    public static void main(String[] args) {

        try (
                RandomAccessFile raf = new RandomAccessFile("out.txt","rw");
                )
        {
            //將記錄指標移動到檔案最後位置
            raf.seek(raf.length());
            raf.write("追加的內容 \r\n".getBytes());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

RandomAccessFile依然不能向檔案的指定位置插入內容,如果直接將檔案記錄指標移動到中間某位置後開始輸入,則新的輸出內容會覆蓋檔案中原有的內容,如果需要向檔案中間插入內容,則需要將插入點後面的內容讀入緩衝區,等把需要插入的內容插入到檔案後,再將緩衝區的內容讀取出來追加到檔案後面。

例項:
實現向指定檔案、位置插入內容的功能:

package org.westos.demo7;

import java.io.*;

public class InsertContent {
    public static void insert(String filename,long pos,String insertContent) throws IOException {
        File tmp = File.createTempFile("tmp", null);
        //在程式退出時檔案被刪除
        tmp.deleteOnExit();



        try (
                RandomAccessFile raf = new RandomAccessFile(filename, "rw");
                //使用臨時檔案來儲存插入點後的資料
                FileOutputStream tmpOut = new FileOutputStream(tmp);
                FileInputStream tmpIn = new FileInputStream(tmp);
                ){
            raf.seek(pos);
            //下面程式碼將插入後的內容讀入臨時檔案中儲存
            byte[] tmpBuf = new byte[64];
            int hasRead=0;
            while ((hasRead=raf.read(tmpBuf))>0){
                tmpOut.write(tmpBuf,0,hasRead);
            }
            //插入內容
            raf.seek(pos);
            //需要插入的內容
            raf.write(insertContent.getBytes());
            //追加臨時檔案中的內容
            while ((hasRead=tmpIn.read(tmpBuf))>0){
                raf.write(tmpBuf,0,hasRead);
            }
        }
    }

    public static void main(String[] args) throws IOException {
        insert("InsertContent.java",45,"插入的內容! \r\n");
    }
}

實現:斷點複製檔案,正在複製的檔案由於出現故障中斷了複製,再次複製的時候,從上一次斷點位置開始複製。

package org.westos.demo7;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;

public class InterruptCopy {
    public static void main(String[] args) {


        try (
                RandomAccessFile file = new RandomAccessFile("yinyue.mp3", "rw");
                RandomAccessFile file2 = new RandomAccessFile("yinyue2.mp3", "rw");
                ){
            //將指標移到兩個檔案上次中斷的位置
            file.seek(file2.length());
            file2.seek(file2.length());
            byte[] buf = new byte[1024];
            int hasRead=0;
            System.out.println(file.read(buf));
            while ((hasRead=file.read(buf))>0){
                file2.write(buf,0,hasRead);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}