1. 程式人生 > >Java中的I/O操作

Java中的I/O操作

首先先了解一下檔案型別,我們都知道資料是以二進位制形式儲存的,但是為了資料處理方便,高階語言中引入了資料型別的概念。檔案也引入了檔案型別的概念。

檔案型別通常是以副檔名體現出來的,每種檔案型別都有一定的格式,代表檔案含義和二進位制之間的對映關係。一個World檔案,其中有文字、圖片、表格,文字可能有顏色、字型、字號等,doc檔案型別就定義了這些內容和二進位制表示之間的對映關係。

在作業系統中,一種副檔名往往關聯一個應用程式,比如.doc檔案關聯Word應用。使用者通過雙擊,作業系統找到對應的應用程式,並啟動該程式,傳遞該檔案路徑給它,程式再開啟檔案。

檔案是放在硬碟上的,每個檔案都有檔案路徑的概念,路徑有兩種形式:

絕對路徑:從根目錄到當前檔案的完整路徑

相對路徑:相對路徑是相對於當前目錄而言的。

程式處理檔案需將檔案讀入記憶體,修改後,再寫回硬碟。硬碟的訪問延時,相比記憶體,是很慢的,作業系統一般是按塊批量傳輸,而不是按位元組。

1.檔案和目錄操作

檔案和目錄操作最終是與作業系統和檔案系統相關的,不同的系統實現是不一樣的,但Java中的java.io.File類提供了統一的介面,底層會通過本地方法呼叫作業系統和檔案系統的具體細節。

1.1 構造方法:

    public File(String pathname)//pathname表示完整路徑,該路徑可以是相對路徑,也可以是絕對路徑
    public File(String parent, String child)
    public File(File parent, String child) 

新建一個File物件只是建立一個檔案或目錄物件,new之後,File物件的路徑是不可變的

1.2檔案元操作:

檔案元資料主要包括檔名、路徑、檔案基本資訊以及一些安全和許可權相關的資訊,主要有下面的方法:

    public String getName()//返回檔案或目錄名,不含路徑
    public File getParentFile()//返回父目錄的File物件
    public String getPath()//返回構造File物件時的完整路徑名,包括路徑和檔名稱
    public boolean isAbsolute()//判斷File中的路徑是否是絕對路徑
    public String getAbsolutePath()//返回完整的絕對路徑名
    public boolean isFile()//是否為檔案
    public boolean isDirectory()//是否為目錄
    public boolean exists()//檔案或目錄是否存在
    public boolean canWrite()//是否可寫
    public boolean canRead()//是否可讀
    public boolean isHidden()//是否為隱藏檔案
    public long lastModified()//最後一次修改時間
    public long length()//檔案長度,位元組數,對目錄無意義

下面是對上述方法的使用,以及結果: 

public class Test {
    public static void main(String[] args) throws IOException {
        //File(String pathname);
        File file = new File("C:"+File.separator+"Users"+File.separator+"acer"+File.separator+"Desktop");
        //File(File parent, String child)
       File file1 = new File(file,"Text");
       //File(String parent, String child)
       File file2 = new File("C:"+File.separator+"Users"+File.separator+"acer"+File.separator+"Desktop"+File.separator+"Text","JavaIO");
        System.out.println(file.getName());
        System.out.println(file1.getName());
        System.out.println(file2.getName());

        System.out.println(file.getParentFile());
        System.out.println(file1.getParentFile());
        System.out.println(file2.getParentFile());

        System.out.println(file.getPath());
        System.out.println(file1.getPath());
        System.out.println(file2.getPath());

        System.out.println(file.isAbsolute());
        System.out.println(file1.isAbsolute());
        System.out.println(file2.isAbsolute());

        System.out.println(file.getAbsolutePath());
        System.out.println(file1.getAbsolutePath());
        System.out.println(file2.getAbsolutePath());

        System.out.println(file.isFile());
        System.out.println(file1.isFile());
        System.out.println(file2.isFile());

        System.out.println(file.isDirectory());
        System.out.println(file1.isDirectory());
        System.out.println(file2.isDirectory());

        System.out.println(file.exists());
        System.out.println(file1.exists());
        System.out.println(file2.exists());
    }
}

1.3 檔案操作:

檔案操作主要有建立、刪除、重新命名

    public boolean createNewFile() throws IOException//建立檔案

建立成功返回true、否則返回false,建立新的檔案內容為空。如果檔案已存在,不會建立。

    public static File createTempFile(String prefix, String suffix, File directory) throws IOException//建立臨時檔案
    public static File createTempFile(String prefix, String suffix) throws IOException//建立臨時檔案

臨時檔案的完整路徑名是系統指定的、唯一的,但可以通過引數制定字首(prefix)、字尾(suffix)和目錄(directory)。prefix是必須的,且至少要三個字元;suffix如果為空,則預設為.tmp;directory如果不指定獲指定為null,則使用系統預設目錄。

File 類的刪除方法:

public boolean delete()
public void deleteOnExit()

delete 刪除檔案或目錄,成功返回true,失敗返回false。如果File是目錄且不為空,則delete不會成功,返回false。

deleteOnExit,是先將File物件加入到待刪除列表,在Java虛擬機器正常退出的時候進行實際刪除。

1.4 目錄操作

當File代表目錄的時候,可以執行目錄相關的操作,如建立,遍歷

建立目錄:

public boolean  mkdir()
public boolean  mkdirs()

建立成功返回true,否則返回false,如果目錄已存在,則返回false。

當某個中間不存在的時候,mkdir會失敗,但是mkdirs會建立該目錄。

遍歷目錄:

public  String[] list()
public  File[] listFiles()

返回的直接子目錄或檔案。

public class Test {
    public static void main(String[] args) throws IOException {

       File file = new File("C:"+File.separator+"Users"+File.separator+"acer"+File.separator+"Desktop"+File.separator+"Text","JavaIO01");
       file.mkdirs();
       File file1 = new File(file,"JavaIO.txt");
       file1.createNewFile();
    }
}

下面就是檔案File使用的一些例子:

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        new Thread(()->{
            listAllFiles(new File("C:"+File.separator+"Users"+File.separator+"acer"+File.separator+"Desktop"));
        },"輸出執行緒").start();
        System.out.println("開始進行檔案輸出...");
    }
    public static void listAllFiles(File file){
        if (file.isDirectory()){
            File[] result = file.listFiles();
            if (result!=null){
                for(File file1:result){
                    listAllFiles(file);
                }
            }
        }
        else {
            System.out.println(file);
        }
    }

 2.二進位制檔案和位元組流

2.1InputStream/OutputStream

這是基類,他們都是抽象類

1.InputStream:

    public abstract int read() throws IOException;

從流中讀取一個位元組,返回值為int,取值範圍在0~255,當讀到流結尾的時候,返回-1,如果流中沒有資料,read方法會阻塞直到資料到來、流關閉、或異常出現。

public int read(byte b[]) throws IOException 

讀入的位元組放入引數陣列中,一次最多放入的位元組數為陣列b的長度,但實際上讀入的長度可能小於陣列長度,返回值為實際讀入的位元組個數。

    public int read(byte b[], int off, int len) throws IOException

跟上述方法類似,只不過讀入的第一個位元組放入b[off],最多讀取len個位元組。

    public void close() throws IOException {}

不管是read方法是否丟擲異常,都應該呼叫close方法,所以close方法通常被放在finally語句內。 

2.OutputStream

    public abstract void write(int b) throws IOException

向流中輸入一個位元組,引數雖然是int,但是隻會用到最低的8位。

同樣還有兩個向流中寫入的批量方法:

    public void write(byte b[]) throws IOException
    public void write(byte b[], int off, int len) throws IOException

將b陣列中的位元組輸入到流中。

    public void flush() throws IOException
    public void close() throws IOException

flush方法是將緩衝而未實際寫入資料進行實際寫入,OutputStream中沒有緩衝,flush程式碼為空,close方法就是關閉流操作。

2.2FileInputStream/FileOutputStream

FileInputStrea和FileOutputStream的輸入源和輸出目標是檔案

1.FileOutputStream

    public class FileOutputStream extends OutputStream
    public FileOutputStream(String name) throws FileNotFoundException
    public FileOutputStream(File file) throws FileNotFoundException
    public FileOutputStream(String name, boolean append) throws FileNotFoundException
    public FileOutputStream(File file, boolean append) throws FileNotFoundException

可以從上面看出,FileOutputStream為OutputStream的子類。

在它的構造方法中,name,file都表示檔案路徑,append引數表示追加還是覆蓋,true表示追加,false表示覆蓋,當沒有傳入append引數值時,預設表示為覆蓋。

2.FileInputStream

    public class FileInputStream extends InputStream
    public FileInputStream(String name) throws FileNotFoundException
    public FileInputStream(File file) throws FileNotFoundException

引數同樣可以是檔案路徑或File物件,但必須是一個已經存在的檔案,不能是目錄。

2.3ByteArrayInputStream/ByteArrayOutputStream

ByteArrayInputStream和ByteArrayOutputStream輸入源和輸出目標是位元組陣列

1.ByteArrayOutputStream:

    public class ByteArrayOutputStream extends OutputStream
    public ByteArrayOutputStream(int size)
    public ByteArrayOutputStream()

 ByteArrayOutputStream輸出目標是一個byte陣列,這個陣列的長度是根據資料內容動態擴充套件的,size指定的就是初始陣列大小,如果沒有指定,則長度為32.

再呼叫write方法的過程中,如果陣列大小不夠,會進行擴充套件,擴充套件策略同樣是指數擴充套件,每次增加至少一倍。

2. ByteArrayInputStream:

public class ByteArrayInputStream extends InputStream
    public ByteArrayInputStream(byte buf[])
    public ByteArrayInputStream(byte buf[], int offset, int length)

ByteArrayInputStream是將byte陣列包裝為一個輸入流,是一種介面卡模式。第二個構造方法中以buf中的offset開始的length個位元組為背後的資料,ByteArrayInputStream的所有資料都在記憶體,支援重複讀取。 

2.4DataInputStream/DataOutputStream

DataInputStream和DataOutputStream都是裝飾類,可以進行其他型別的讀寫,比如int、double。

1.DataOutputStream:

public class DataOutputStream extends FilterOutputStream implements DataOutput;
public class FilterOutputStream extends OutputStream

DataOutputStream是FilterOutputStream的子類,而 FilterOutputStream是OutputStream的子類;

FilterOutputStream的構造方法:

    public FilterOutputStream(OutputStream out)

而DataOutputStream的構造方法:

    public DataOutputStream(OutputStream out) {
        super(out);
    }

 接受了一個OutputStream的例項對像。

2.DataInputStream:

public class DataInputStream extends FilterInputStream implements DataInput
    public DataInputStream(InputStream in) {
        super(in);
    }

DataInputStream可以以各種基本和字串讀取, 

2.5BufferedInputStream/BufferedOutputStream

FileInputStream/FileOutputStream是沒有緩衝區的,按單個位元組讀取的方式效率太過低下,方法就是將檔案流包裝到緩衝流中。

    public class BufferedInputStream extends FilterInputStream
    public class BufferedOutputStream extends FilterOutputStream 
    //構造方法
    public BufferedInputStream(InputStream in)
    public BufferedInputStream(InputStream in, int size)
    public BufferedOutputStream(OutputStream out)
    public BufferedOutputStream(OutputStream out, int size)

下面是BufferedInputStream的用法示例:

           InputStream inputStream = new BufferedInputStream(new FileInputStream("hello.txt"));
           OutputStream outputStream = new BufferedOutputStream(new FileOutputStream("hello.txt"));

總結:

(1)InputStream/OutputStream:是抽象基類,有很多面向流的程式碼,以他們為引數。

(2)FileInputStream/FileOutputStream:流的源和目的是檔案

(3)ByteArrayInputStream/ByteArrayOutputStream:源和目的地是位元組陣列

(4)DataInputStream/DataOutputStream:裝飾類,按基本型別和字串讀寫流

(5)BufferedInputStream/BufferedOutputStream:裝飾類,提供緩衝。

下面為一個檔案拷貝的程式:

class CopyFileUtil{
    private CopyFileUtil(){

    }
    public static boolean fileIsExists(String path){
        return new File(path).exists();
    }
    public static void createParentsDir(String path){
        File file = new File(path);
        if (!file.getParentFile().exists()){
            file.getParentFile().exists();
        }
    }
    public static boolean copyFile(String sourcePath,String destPath){
        File inFile = new File(sourcePath);
        File outFile = new File(destPath);
        FileInputStream fileInputStream = null;
        FileOutputStream fileOutputStream = null;
        try {
            fileInputStream=new FileInputStream(inFile);
            fileOutputStream =new FileOutputStream(outFile);
            copyFileHandle(fileInputStream,fileOutputStream);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }finally {
            try {
                fileInputStream.close();
                fileOutputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return true;
    }
    private static void copyFileHandle(InputStream inputStream,OutputStream outputStream) throws IOException {
        long start = System.currentTimeMillis();
        int temp = 0;
        do{
            temp=inputStream.read();
            outputStream.write(temp);

        }while (temp!=-1);
        long end = System.currentTimeMillis();
        System.out.println("拷貝檔案所花費的時間"+(end-start));
    }
}



public class Test {
    public static final File FILE = new File("C:"+File.separator+"Users"+File.separator+"acer"+File.separator+"Desktop","Text"+File.separator+"JavaIO"+File.separator+"JavaIO.txt");

    public static void main(String[] args) {

        if(args.length!=2){
            System.out.println("非法操作,命令為:java  CopyFile 原檔案路徑 目標檔案路徑");
            return;
        }
        String sourcePath = args[0];
        String destPath = args[1];
        if (CopyFileUtil.fileIsExists(sourcePath)){
            CopyFileUtil.createParentsDir(sourcePath);
            System.out.println(CopyFileUtil.copyFile(sourcePath,destPath)?"檔案拷貝成功":"檔案拷貝失敗");
        }else {
            System.out.println("原始檔不存在,無法進行拷貝" );
        }
    }
}

3.文字檔案和字元流

上面都是如何以位元組流的方式處理檔案,對於文字檔案,位元組流沒有編碼的概念,不能按行處理,使用起來不太方便。

位元組流是按位元組讀取的,而字元流是按char讀取的,一個char在檔案中儲存的是幾個位元組預編碼有關。

在Java中字元流的相關類:

3.1 Reader/Writer

其中主要方法有:

    public int read() throws IOException
    public int read(char cbuf[]) throws IOException
    abstract public int read(char cbuf[], int off, int len) throws IOException
     abstract public void close() throws IOException;
    public void write(int c) throws IOException
    public void write(char cbuf[]) throws IOException
    abstract public void write(char cbuf[], int off, int len) throws IOException;
    public void write(String str) throws IOException
    public void write(String str, int off, int len) throws IOException
    abstract public void flush() throws IOException;
    abstract public void close() throws IOException;

方法名稱和含義與InputStream/OutputStream中的對應方法基本類似,區別就是,Reader/Writer處理的單位是char,例如:read讀取的就是一個char,取值範圍是0~65535. 

Writer還接受String型別的引數。

3.2 InputStreamReader/OutputStreamWriter

這是介面卡,是可以將InputStream/OutputStream轉化為Reader/Writer。

3.2.1 OutputStreamWriter:

    public class OutputStreamWriter extends Writer
    public OutputStreamWriter(OutputStream out, String charsetName)
    public OutputStreamWriter(OutputStream out)

 可以通過構造方法看出,除了OutputStream引數之外,還有一個charsetName,這是編碼型別。如果沒有傳入,則按照系統預設編碼。

3.2.2InputStreamReader:

    public class InputStreamReader extends Reader
    public InputStreamReader(InputStream in)
    public InputStreamReader(InputStream in, String charsetName)

同樣,InputStreamReader裡面依然有一個重要的引數是編碼型別。 

3.3 FileReader/FileWriterr

public class FileReader extends InputStreamReader
    public FileReader(String fileName) throws FileNotFoundException
    public FileReader(File file) throws FileNotFoundException

public class FileWriter extends OutputStreamWriter
    public FileWriter(String fileName) throws IOException
    public FileWriter(String fileName, boolean append) throws IOException

上面fileName 表示檔名稱。append表示追加還是覆蓋。

FileReader/FileWriter不指定檔案編碼型別,只能使用預設編碼。

3.4 CharArrayReader/CharArrayWriter

CharArrayWriter是與ByteArrayOutputStream類似,輸出目標是char陣列。

CharArrayReader與ByteArrayInputStream類似,將Char陣列包裝為一個Reader,是一種介面卡模式。

public class CharArrayReader extends Reader
    public CharArrayReader(char buf[])
    public CharArrayReader(char buf[], int offset, int length)
public class CharArrayWriter extends Writer
    public CharArrayWriter(int initialSize)

3.5 StringReader/StringWriter

與CharArrayReader/CharArrayWriter類似,只是輸入源為String。

3.6 BufferedReader/BufferedWriter

裝飾類,提供緩衝,以及按行讀寫功能。

    public class BufferedWriter extends Writer
    public BufferedWriter(Writer out)
    public BufferedWriter(Writer out, int sz)
    public void newLine() throws IOException

引數中sz是緩衝大小,如果沒有提供,預設為8192.其中newLine方法,可以輸出平臺特定的換行符。

    public class BufferedReader extends Reader
    public BufferedReader(Reader in, int sz)
    public BufferedReader(Reader in)
    public String readLine() throws IOException

 引數中sz是緩衝大小,如果沒有提供,預設為8192.readLine方法返回一行內容,但不會包括換行符('\r'或'\n'或'\r\n'),讀到流結尾時,返回null。

3.7 PrintWriter

    public class PrintWriter extends Writer
    public PrintWriter (Writer out)
    public PrintWriter(Writer out,boolean autoFlush)
    public PrintWriter(OutputStream out)
    public PrintWriter(String fileName) throws FileNotFoundException
    public PrintWriter(File file) throws FileNotFoundException

PrintWriter有很多的構造方法,可以接受檔案路徑名,檔案物件,OutputStream,Writer等,構造方法中的autoFlush引數表示同步緩衝區的時機,如果為true,則在呼叫println、print、format方法的時候,同步緩衝區,如果沒有傳入,則不會自動同步。

在PrintWrite方法中的write(int b),PrintWriter是使用最低的兩位,輸出一個char。

首先看看往一個檔案裡輸入:

class PrintUtil{
    private OutputStream outputStream;

    public PrintUtil(OutputStream outputStream) {
        this.outputStream = outputStream;
    }
    public void print(String str) {
        try {
            this.outputStream.write(str.getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public void pringtln(String str){
        this.print(str+"\r\n");
    }
    public void print(int data){
        this.print(String.valueOf(data));
    }
    public void println(int data){
        this.pringtln(String.valueOf(data));
    }
    public void print(double data){
        this.pringtln(String.valueOf(data));
    }
    public void println(double data){
        this.pringtln(String.valueOf(data));
    }
}

public class Test {


    public static void main(String[] args) throws IOException {

        PrintUtil printUtil = new PrintUtil(new FileOutputStream(new File("C:"+File.separator+"Users"+File.separator+"acer"+File.separator+"Desktop"+File.separator+"Text"+File.separator+"JavaIO"+File.separator+"JavaIO1.txt")));
        printUtil.print("姓名:");
        printUtil.pringtln("王昊");
        printUtil.print("年齡");
        printUtil.println(23);
        printUtil.print("工資:");
        printUtil.println(2243.32);

    }

然後看看使用PrintWriter:

public class Test {


    public static void main(String[] args) throws IOException {

        PrintWriter printUtil = new PrintWriter(new FileOutputStream(new File("C:"+File.separator+"Users"+File.separator+"acer"+File.separator+"Desktop"+File.separator+"Text"+File.separator+"JavaIO"+File.separator+"JavaIO1.txt")));
        printUtil.print("姓名:");
        printUtil.println("王昊");
        printUtil.print("年齡");
        printUtil.println(23);
        printUtil.print("工資:");
        printUtil.println(2243.32);
        printUtil.close();
    }

3.8 Scanner

Scanner有很多形式的next()方法,可以讀取下一個基本型別或行。

Scanner也有很多構造方法,可以接收File物件,InputStream、Reader作為引數。

    public boolean hasNext()
    public String next()

下面為用Scanner接收鍵盤上傳入的生日: 

    public static void main(String[] args) throws IOException {

        Scanner scanner = new Scanner(System.in);
        System.out.println("請輸入生日:");
        if (scanner.hasNext("\\d{4}-\\d{2}-\\d{2}")){//利用正則表示式來判斷格式
            String birthday = scanner.next();
            System.out.println("輸入的生日為:"+birthday);
        }else {
            System.out.println("輸入格式非法");
        }
        scanner.close();
    }

 下面是將檔案的內容接收進來:

    public static void main(String[] args) throws IOException {
        Scanner scanner = new Scanner(new FileInputStream(new File("C:"+File.separator+"Users"+File.separator+"acer"+File.separator+"Desktop"+File.separator+"Text"+File.separator+"JavaIO"+File.separator+"JavaIO1.txt")));
        scanner.useDelimiter("\n");//以"\n"為分隔符
        while(scanner.hasNext()){
            System.out.println(scanner.next());
        }
        scanner.close();
}

3.9PrintStream

之前一直使用的System .out向螢幕上輸出,其實就是一個PrintStream,輸出目標是螢幕。除了System.out,java中還有System.in和System.err。

System.in表示標準輸入,它是一個InputStream物件,輸入源經常是鍵盤。

比如:

System in = new Scanner(System.in);

System.err表示標準錯誤流,一般異常和錯誤資訊輸入到這個流,它也是一個PrintStream物件,輸入目標一般也是螢幕。

4.序列化和反序列化

物件序列化就是將記憶體中儲存的物件變為二進位制資料流的形式進行傳輸,或者是將其儲存在文字中。要想實現序列化的類物件往往需要傳輸使用,同時這個類必須實現java.io.Serializable介面。這個介面沒有任何方法,只是一個標識。

反序列化就是從流中恢復Java物件到記憶體。

class Person implements  Serializable{
    private transient String name;
    private int age;

    public Person(String name,int age) {
        this.name = name;
        this.age =age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
public class Test {
    public static final File FILE = new File("C:"+File.separator+"Users"+File.separator+"acer"+File.separator+"Desktop","Text"+File.separator+"JavaIO"+File.separator+"JavaIO.txt");

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        ser(new Person("wanghao",26));
        dser();
    }
    public static void ser(Object obj) throws IOException {
        ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream(FILE));
        oos.writeObject(obj);
        oos.close();
    }
    public static void dser() throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(FILE));
        System.out.println(ois.readObject());
        ois.close();
    }
}

 輸出結果為:Person{name='null', age=26},transient關鍵字修飾的就是不可以序列化。

如果有錯誤請指正!!!

下次再補充,不多說了,敲程式碼了!!!