天天看點

JavaSE基礎——(20)IO流&位元組流一、IO流二、位元組流

目錄

一、IO流

1.1 IO流概述及分類

1.2 IO流常用父類

1.3程式書寫

二、位元組流

2.1FileInputStream

2.2FileOutputStream

2.3FileOutputStream追加

2.4拷貝檔案

2.4.1按位元組逐個拷貝

2.4.2位元組數組整體拷貝

2.4.3小數組拷貝

2.4.4緩沖位元組流拷貝

2.5flush和close方法

2.6位元組流操作中文

2.7流的異常處理

2.7.1老版本(1.6版本及以前)

2.7.2新版本(1.7版本)

2.8給圖檔加密

2.9拷貝檔案

2.10錄入資料拷貝到檔案

一、IO流

1.1 IO流概述及分類

IO流是用來處理裝置之間的資料傳輸,Java對資料的操作是通過流的方式,

Java用于操作流的類都在IO包中,流按照流向可以分為輸入流(InputStream)和輸出流(OutputStream),

按照操作類型可以分為:位元組流和字元流,

  • 位元組流:位元組流可以操作任何資料,因為在計算機中任何資料都是以位元組的形式存儲的
  • 字元流:字元流隻能操作純字元資料,比較友善

1.2 IO流常用父類

  • 位元組流抽象父類
    • InputStream
    • OutputStream
  • 字元流抽象父類
    • Reader
    • Writer

1.3程式書寫

  • 使用前,導入IO包中的類
  • 使用時,進行IO異常處理
  • 使用後,釋放資源

二、位元組流

位元組流主要分為InputStream和OutputStream,因為這兩個是抽象類,我們在使用的時候隻能使用它的子類,

JAVA中針對檔案的讀寫操作設定了一系列的流,

其中主要有FileInputStream、FileOutputStream、FileReader、FileWriter四種最為常用的流,

我們首先學習一下位元組流FileInputStream和FileOutputStream。

2.1FileInputStream

FileInputStream是從檔案系統中的某個檔案中獲得輸入位元組,用于讀取諸如圖像資料之類的原始資料流,

我們一般使用read方法來讀取位元組,read()可以從輸入流中讀取一個資料位元組,并且以int類型傳回,當檔案到末尾時會傳回-1,

我們利用這個特點來讀取一個txt檔案測試一下,

import java.io.FileInputStream;
import java.io.IOException;

public class IOTest {
    public static void main(String[] args) throws IOException {
        FileInputStream f=new FileInputStream("data.txt");
        int ch;
        while((ch=f.read())!=-1){
            System.out.println(ch);
        }
        f.close();
    }
}
           
JavaSE基礎——(20)IO流&位元組流一、IO流二、位元組流

這裡有一個小問題,既然read方法每次讀取的是一個位元組,為什麼不用byte類型接收而用int類型,

因為位元組輸入流可以操作任意類型的檔案,比如圖檔音頻等,這些檔案底層都是以二進制形式存儲的,

如果每次讀取傳回的類型都是byte,有可能讀到11111111,這個二進制補碼代表的數字為-1,而程式碰到-1後就會停止讀檔案,

後面的資料就沒有辦法讀到了,是以在讀取的時候用int類型接收,如果是11111111,那麼前面也會補足24個0湊夠4個位元組,然後傳回

這樣就可以保證正常讀取後面的資料了。

2.2FileOutputStream

FileOutputStream是檔案輸出流,是用于将資料寫入File或者FileDescriptor的輸出流。

FileOutputStream用于寫入諸如圖像資料之類的原始位元組的流,我們一般用的是write方法,

void write(byte b[])//将b.length個位元組從指定的byte數組寫入檔案輸出流中
void write(byte b[],int off,int len)//将指定byte數組從偏移量off開始的len個位元組寫入檔案輸出流
void write(int b)//将指定位元組寫入檔案輸出流
           

我們往txt檔案寫入字元串"java"試一下,

FileOutputStream f=new FileOutputStream("dataOutput.txt");
        f.write(106);//字元j(雖然寫入的是一個int數,但是會自動去除前3個八位,變為byte一個位元組)
        f.write(97);//字元a
        f.write(118);//字元v
        f.write(97);//字元a

        f.close();
           
JavaSE基礎——(20)IO流&位元組流一、IO流二、位元組流

2.3FileOutputStream追加

當我們想保留原來檔案的内容,并在原來内容的基礎上添加一些字元,這時就不能隻用檔案名構造FileOutputStream()了,

因為用檔案名構造FileOutputStream對象,在建立對象的時候如果沒有檔案會幫我們建立檔案,如果有則會将檔案清空再重新寫入,

我們需要添加一個額外的布爾參數append,append如果為true ,則位元組将被寫入檔案的末尾而不是開頭,也就是不會覆寫前面的内容

FileOutputStream(File file, boolean append)//建立檔案輸出流以寫入由指定的 File對象表示的檔案
FileOutputStream(String name, boolean append)//建立檔案輸出流以指定的名稱寫入檔案
           

我們來測試一下,接着上面的内容"java",繼續再寫入一個"java"。

FileOutputStream f=new FileOutputStream("dataOutput.txt",true);
        f.write(106);
        f.write(97);
        f.write(118);
        f.write(97);

        f.close();
           
JavaSE基礎——(20)IO流&位元組流一、IO流二、位元組流

2.4拷貝檔案

在java檔案流中,拷貝檔案一般有四種方式。

2.4.1按位元組逐個拷貝

FileInputStream fis=new FileInputStream("luffy.jpg");
        FileOutputStream fos=new FileOutputStream("luffyCopy.jpg");

        int b;
        while((b=fis.read())!=-1){
            fos.write(b);//逐個位元組拷貝,複制圖檔
        }
        fis.close();
        fos.close();
           
JavaSE基礎——(20)IO流&位元組流一、IO流二、位元組流

可以看到圖檔已經拷貝成功了。

但是這種速度非常慢,是逐個位元組進行拷貝的,如果碰到比較大的檔案效率就會很低。

2.4.2位元組數組整體拷貝

如果遇到位元組數比較多的檔案,拷貝的時候時間消耗就會比較大,這裡我們可以用到available方法,

int availabe()//傳回檔案可以讀取的位元組個數

int read(byte[] b)//從輸入流中将最多b.length個位元組的資料讀取到byte數組中,并傳回讀取的有效位元組個數
int read(byte[] b,int off, int len)//從輸出流中将最多len個位元組的資料讀取到byte數組中,開始位置為off,并傳回讀取的有效位元組個數

int write(byte[] b)//将b.length個位元組從指定byte數組寫入檔案輸出流,并傳回寫入的有效位元組個數
int write(byte[] b,int off, int len)//将指定byte數組從偏移量off開始的len個位元組寫入檔案輸出流,并傳回寫入的有效位元組個數
           

那麼我們看看如何使用available方法拷貝位元組數組,

FileInputStream fis=new FileInputStream("testVideo.avi");
        FileOutputStream fos=new FileOutputStream("testVideoCopy.avi");

        byte arr[]=new byte[fis.available()];
        fis.read(arr);
        fos.write(arr);

        fis.close();
        fos.close();
           
JavaSE基礎——(20)IO流&位元組流一、IO流二、位元組流

這種方式相比于一個位元組一個位元組讀取,效率快了很多,但是在開發中這種方式還是不常用的,

因為如果檔案大小過大的話,一次将檔案讀取到程式中,JVM可能會出現記憶體溢出的錯誤,是以在開發中這兩種方法都不常用。

2.4.3小數組拷貝

小數組拷貝就是定義一個容量相對小的數組,讓數組一次無法接收完檔案流中的資料,

然後不斷讀取寫入,讀取寫入直到檔案流中沒有資料了,我們來看看代碼如何實作,

FileInputStream fis=new FileInputStream("testVideo.avi");
        FileOutputStream fos=new FileOutputStream("testVideoCopy.avi");

        byte arr[]=new byte[1024];//這裡一般定義為1024,也可以自己設定為其他較小的數字(最好為1024的整數倍)
        int len;
        while((len=fis.read(arr))!=-1){//當檔案流中還有資料時
            fos.write(arr,0,len);//寫入讀取進來的有效資料
        }

        fis.close();
        fos.close();
           

2.4.4緩沖位元組流拷貝

緩沖位元組流就是用到了BufferedInputStream和BufferedOutputStream,其構造方法如下,

BufferedInputStream(InputStream in)//使用輸入流in建立一個BufferedInputStream對象,供以後使用
BufferedInputStream(InputStream in, int size)//使用輸入流in建立BufferedInputStream,具有指定緩沖區大小size

BufferedOutputStream(OutputStream out)//建立一個新的緩沖輸出流,以将資料寫入指定的底層輸出流
BufferedOutputStream(OutputStream out, int size)//建立一個新的緩沖輸出流,以便以指定的緩沖區大小将資料寫入指定的底層輸出流
           

當建立BufferedInputStream時,将建立一個内部緩沖區數組,大小一般為1024*8個位元組,裝滿後一次重新整理到檔案上,

當從流中讀取或跳過位元組時,内部緩沖區将根據需要從所包含的輸入流中重新填充,一次有多個位元組,

然後我們使用緩沖位元組流對象進行讀寫操作,其功能與前面的讀寫相同,我們看看如何具體使用的,

FileInputStream fis=new FileInputStream("data.txt");
        FileOutputStream fos=new FileOutputStream("dataCopy.txt");
        BufferedInputStream bis=new BufferedInputStream(fis);
        BufferedOutputStream bos=new BufferedOutputStream(fos);

        int len;
        while((len=bis.read())!=-1){//當檔案流中還有資料時
            bos.write(len);//寫入讀取進來的有效資料
        }

        bis.close();
        bos.close();
        fis.close();
        fos.close();
           

2.5flush和close方法

  • flush()方法
    • 用來重新整理緩沖區的,将緩沖區的位元組全部都重新整理到檔案上,重新整理之後可以再次寫出資料
  • close()方法
    • 用于關閉流釋放資源的,如果是帶緩沖區的流對象使用close方法,會先重新整理緩沖區再關閉流,将緩沖區的位元組全部都重新整理到檔案上,關閉之後不可以寫出資料

2.6位元組流操作中文

位元組流在讀取中文的時候,因為一個中文用兩個位元組表示,位元組流一個位元組一個位元組讀取時有可能會讀到半個中文,造成亂碼,

如果我們用小數組來讀取,那麼怎麼設定小數組的大小也是一個問題,如果大小設定為兩個,那麼兩個中文之間要是有一個标點符号一樣還是會亂碼,

是以我們一般使用字元流讀取中文,在用位元組流讀取中文時可能會出現亂碼的情況,

在位元組流寫出中文時,位元組流直接操作的是位元組,是以寫出中文也必須将字元串轉換為位元組數組再寫出,

比如write("你好".getBytes()),再比如寫回車換行為write("\r\n".getBytes())  

2.7流的異常處理

2.7.1老版本(1.6版本及以前)

import java.io.*;

public class IOTest {
    public static void main(String[] args) throws IOException {
        FileInputStream fis=null;
        FileOutputStream fos=null;
        try{//try finally語句保證了建立輸出流有異常時,輸入流可以正常關閉
            fis=new FileInputStream("data.txt");
            fos=new FileOutputStream("dataCopy.txt");

            int b;
            while((b=fis.read())!=-1){
                fos.write(b);
            }
        }finally {//裡面的try finally嵌套保證fis關閉異常時可以正常關閉fos
            try{
                if(fis!=null)
                    fis.close();
            }finally {
                if(fos!=null)
                    fos.close();
            }
        }
    }
}
           

2.7.2新版本(1.7版本)

import java.io.*;

public class IOTest {
    public static void main(String[] args) throws IOException {
        try(
            FileInputStream fis=new FileInputStream("data.txt");
            FileOutputStream fos=new FileOutputStream("dataCopy.txt");
        ){
            int b;
            while((b=fis.read())!=-1) {
                fos.write(b);
            }
        }
        //不用顯示寫close關閉流,在try後的小括号中建立流對象,
        //執行完大括号的代碼後,流對象會自動關閉
    }
}
           

注意,小括号中不能建立不具備自動關閉功能的對象(需要實作AutoCloseable接口)。

2.8給圖檔加密

給圖檔加密實作起來比較簡單,将讀出來的位元組通過自己設計的編碼規則對應到另外的一個數,再将結果儲存輸出,

解碼的時候按照編碼規計算出原來的位元組碼,即可得到原來的照片了,我們看看具體如何實作,

import java.io.*;

public class IOTest {
    public static void main(String[] args) throws IOException {
        BufferedInputStream bis=new BufferedInputStream(new FileInputStream("luffy.jpg"));
        BufferedOutputStream bos=new BufferedOutputStream(new FileOutputStream("luffyEncrypt.jpg"));

        int b;
        while((b=bis.read())!=-1){
            //異或操作使用兩次就會得到數本身,解碼的時候再異或一次即可得到原位元組碼
            bos.write(b^123);//将得到的位元組碼異或123
        }
        bis.close();
        bos.close();

        Decode();
    }

    public static void Decode() throws IOException {
        BufferedInputStream bis=new BufferedInputStream(new FileInputStream("luffyEncrypt.jpg"));
        BufferedOutputStream bos=new BufferedOutputStream(new FileOutputStream("luffyDecode.jpg"));

        int b;
        while((b=bis.read())!=-1){
            bos.write(b^123);//異或123進行解碼
        }
        bis.close();
        bos.close();
    }
}
           

我們看看原圖檔,加密後圖檔和解密後的圖檔(順序為依次從左至右),

JavaSE基礎——(20)IO流&位元組流一、IO流二、位元組流

2.9拷貝檔案

要求在控制台錄入檔案的路徑,将檔案拷貝到目前項目下,

我們定義一個方法,擷取鍵盤錄入的檔案路徑,并且封裝成File對象傳回,

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

public class IOTest {
    public static void main(String[] args) throws IOException {
        File file = getFile();//擷取檔案
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file.getName()));

        int b;
        while ((b = bis.read()) != -1) {
            bos.write(b);
        }

        bis.close();
        bos.close();
        System.out.println("拷貝成功");
    }

    public static File getFile() {
        Scanner scanner = new Scanner(System.in);
        System.out.println("請輸入檔案路徑名:");
        while (true) {
            String fileDir = scanner.nextLine();
            File file = new File(fileDir);
            if (!file.exists()) {
                System.out.println("路徑下不存在該檔案,請重新輸入檔案路徑名:");
            } else if (file.isDirectory()) {
                System.out.println("輸入的路徑為檔案夾路徑,請重新輸入檔案路徑名:");
            } else {
                return file;//傳回檔案
            }
        }
    }
}
           
JavaSE基礎——(20)IO流&位元組流一、IO流二、位元組流

我們檢查一下目前目錄下有沒有剛剛的ico.ico檔案,可以看到正确拷貝過來了。 

JavaSE基礎——(20)IO流&位元組流一、IO流二、位元組流

2.10錄入資料拷貝到檔案

要求将鍵盤錄入的資料拷貝到目前項目下的data.txt檔案中,鍵盤錄入的資料當遇到quit時退出,

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

public class IOTest{
    public static void main(String[] args) throws IOException {
        Scanner sc=new Scanner(System.in);
        FileOutputStream fos=new FileOutputStream("data.txt");
        System.out.println("請鍵盤輸入資料,輸入quit結束:");

        while(true){
            String line=sc.nextLine();
            if("quit".equals(line)){
                break;
            }else{
                fos.write(line.getBytes());//字元串寫出必須轉換為位元組數組
                fos.write("\r\n".getBytes());//寫入換行
            }
        }

        fos.close();
        System.out.println("程式已退出");
    }
}
           
JavaSE基礎——(20)IO流&位元組流一、IO流二、位元組流

我們檢查一下文本檔案的内容,發現正确寫入了内容,

JavaSE基礎——(20)IO流&位元組流一、IO流二、位元組流