面試必備:詳解Java I/O流,掌握這些就能夠說精通了?

@TOCjava

Java IO概述

IO就是輸入/輸出。Java IO類庫基於抽象基礎類InputStream和OutputStream構建了一套I/O體系,主要解決從數據源讀入數據和將數據寫入到目的地問題。咱們把數據源和目的地能夠理解爲IO流的兩端。固然,一般狀況下,這兩端多是文件或者網絡鏈接。程序員

咱們用下面的圖描述下,加深理解:web

從一種數據源中經過InputStream流對象讀入數據到程序內存中算法

在這裏插入圖片描述
在這裏插入圖片描述

固然咱們把上面的圖再反向流程,就是OutputStream的示意了。編程

在這裏插入圖片描述
在這裏插入圖片描述

其實除了面向字節流的InputStream/OutputStream體系外,Java IO類庫還提供了面向字符流的Reader/Writer體系。Reader/Writer繼承結構主要是爲了國際化,由於它能更好地處理16位的Unicode字符。設計模式

在學習是這兩套IO流處理體系能夠對比參照着學習,由於有好多類似之處。數組

要理解整體設計

剛開始寫IO代碼,總被各類IO流類搞得暈頭轉向。這麼多IO相關的類,各類方法,啥時候能記住。bash

其實只要咱們掌握了IO類庫的整體設計思路,理解了它的層次脈絡以後,就很清晰。知道啥時候用哪些流對象去組合想要的功能就行了,API的話,能夠查手冊的嘛。網絡

首先從流的流向上能夠分爲輸入流InputStream或Reader,輸出流OutputStream或Writer。任何從InputStream或Reader派生而來的類都有read()基本方法,讀取單個字節或字節數組;任何從OutputSteam或Writer派生的類都含有write()的基本方法,用於寫單個字節或字節數組。app

從操做字節仍是操做字符的角度,有面向字節流的類,基本都以XxxStream結尾,面向字符流的類都以XxxReader或XxxWriter結尾。固然這兩種類型的流是能夠轉化的,有兩個轉化流的類,這個後面會說到。

通常在使用IO流的時候會有下面相似代碼:

1 FileInputStream inputStream = new FileInputStream(new File("a.txt"));
2 BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);

這裏實際上是一種裝飾器模式的使用,IO流體系中使用了裝飾器模式包裝了各類功能流類。不瞭解裝飾器模式的看下這篇【詳解設計模式】-裝飾者模式

在Java IO流體系中FilterInputStream/FilterOutStreamFilterReader/FilterWriter就是裝飾器模式的接口類,從該類向下包裝了一些功能流類。有DataInputStream、BufferedInputStream、LineNumberInputStream、PushbackInputStream等,固然還有輸出的功能流類;面向字符的功能流類等。

下面幾張圖描述了整個IO流的繼承體系結構

InputStream流體系

在這裏插入圖片描述
在這裏插入圖片描述

OutputStream流體系

在這裏插入圖片描述
在這裏插入圖片描述

Reader體系

在這裏插入圖片描述
在這裏插入圖片描述

Writer體系

在這裏插入圖片描述
在這裏插入圖片描述

最後再附加一張表加深印象:

在這裏插入圖片描述
在這裏插入圖片描述

File實際上是個工具類

File類其實不止是表明一個文件,它也能表明一個目錄下的一組文件(表明一個文件路徑)。下面咱們盤點一下File類中最經常使用到的一些方法

 1File.delete() 刪除文件或文件夾目錄。
2File.createNewFile() 建立一個新的空文件。
3File.mkdir() 建立一個新的空文件夾。
4File.list() 獲取指定目錄下的文件和文件夾名稱。
5File.listFiles() 獲取指定目錄下的文件和文件夾對象。
6File.exists() 文件或者文件夾是否存在
7
8String   getAbsolutePath()   // 獲取絕對路徑
9long   getFreeSpace()       // 返回分區中未分配的字節數。
10String   getName()         // 返回文件或文件夾的名稱。
11String   getParent()         // 返回父目錄的路徑名字符串;若是沒有指定父目錄,則返回 null。
12File   getParentFile()      // 返回父目錄File對象
13String   getPath()         // 返回路徑名字符串。
14long   getTotalSpace()      // 返回此文件分區大小。
15long   getUsableSpace()    //返回佔用字節數。
16int   hashCode()             //文件哈希碼。
17long   lastModified()       // 返回文件最後一次被修改的時間。
18long   length()          // 獲取長度,字節數。
19boolean canRead()  //判斷是否可讀
20boolean canWrite()  //判斷是否可寫
21boolean isHidden()  //判斷是否隱藏
22
23
24// 成員函數
25static File[]    listRoots()    // 列出可用的文件系統根。
26boolean    renameTo(File dest)    // 重命名
27boolean    setExecutable(boolean executable)    // 設置執行權限。
28boolean    setExecutable(boolean executable, boolean ownerOnly)    // 設置其餘全部用戶的執行權限。
29boolean    setLastModified(long time)       // 設置最後一次修改時間。
30boolean    setReadable(boolean readable)    // 設置讀權限。
31boolean    setReadable(boolean readable, boolean ownerOnly)    // 設置其餘全部用戶的讀權限。
32boolean    setWritable(boolean writable)    // 設置寫權限。
33boolean    setWritable(boolean writable, boolean ownerOnly)    // 設置全部用戶的寫權限。
34

須要注意的是,不一樣系統對文件路徑的分割符表是不同的,好比Windows中是「\」,Linux是「/」。而File類給咱們提供了抽象的表示File.separator,屏蔽了系統層的差別。所以平時在代碼中不要使用諸如「\」這種表明路徑,可能形成Linux平臺下代碼執行錯誤。

下面是一些示例:

根據傳入的規則,遍歷獲得目錄中全部的文件構成的File對象數組

 1public class Directory {
2    public static File[] getLocalFiles(File dir, final String regex){
3        return dir.listFiles(new FilenameFilter() {
4            private Pattern pattern = Pattern.compile(regex);
5            public boolean accept(File dir, String name) {
6                return pattern.matcher(new File(name).getName()).matches();
7            }
8        });
9    }
10
11    // 重載方法
12    public static File[] getLocalFiles(String path, final String regex){
13        return getLocalFiles(new File(path),regex);
14    }
15
16    public static void main(String[] args) {
17        String dir = "d:";
18        File[] files = Directory.getLocalFiles(dir,".*\\.txt");
19        for(File file : files){
20            System.out.println(file.getAbsolutePath());
21        }
22    }
23}

輸出結果:

1d:\\1.txt
2d:\\新建文本文檔.txt

上面的代碼中dir.listFiles(FilenameFilter ) 是策略模式的一種實現,並且使用了匿名內部類的方式。

【詳解設計模式】-策略模式

Java內部類超詳細總結(含代碼示例)

上面的例子是《Java 編程思想》中的示例,這本書中的每一個代碼示例都很經典,Bruce Eckel大神把面向對象的思想應用的爐火純青,很是值得細品。

InputStream和OutputStream

InputStream是輸入流,前面已經說到,它是從數據源對象將數據讀入程序內容時,使用的流對象。經過看InputStream的源碼知道,它是一個抽象類,

1public abstract class InputStream  extends Object  implements Closeable
2

提供了一些基礎的輸入流方法:

 1//從數據中讀入一個字節,並返回該字節,遇到流的結尾時返回-1
2abstract int read() 
3
4//讀入一個字節數組,並返回實際讀入的字節數,最多讀入b.length個字節,遇到流結尾時返回-1
5int read(byte[] b)
6
7// 讀入一個字節數組,返回實際讀入的字節數或者在碰到結尾時返回-1.
8//b:表明數據讀入的數組, off:表明第一個讀入的字節應該被放置的位置在b中的偏移量,len:讀入字節的最大數量
9int read(byte[],int off,int len)
10
11// 返回當前能夠讀入的字節數量,若是是從網絡鏈接中讀入,這個方法要慎用,
12int available() 
13
14//在輸入流中跳過n個字節,返回實際跳過的字節數
15long skip(long n)
16
17//標記輸入流中當前的位置
18void mark(int readlimit) 
19
20//判斷流是否支持打標記,支持返回true
21boolean markSupported() 
22
23// 返回最後一個標記,隨後對read的調用將從新讀入這些字節。
24void reset() 
25
26//關閉輸入流,這個很重要,流使用完必定要關閉
27void close()
28

直接從InputStream繼承的流,能夠發現,基本上對應了每種數據源類型。

功能
ByteArrayInputStream 將字節數組做爲InputStream
StringBufferInputStream 將String轉成InputStream
FileInputStream 從文件中讀取內容
PipedInputStream 產生用於寫入相關PipedOutputStream的數據。實現管道化
SequenceInputStream 將兩個或多個InputStream對象轉換成單一的InputStream
FilterInputStream 抽象類,主要是做爲「裝飾器」的接口類,實現其餘的功能流

OutputStream是輸出流的抽象,它是將程序內存中的數據寫入到目的地(也就是接收數據的一端)。看下類的簽名:

1public abstract class OutputStream implements CloseableFlushable {}

提供了基礎方法相比輸入流來講簡單多了,主要就是write寫方法(幾種重載的方法)、flush沖刷和close關閉。

 1// 寫出一個字節的數據
2abstract void write(int n)
3
4// 寫出字節到數據b
5void write(byte[] b)
6
7// 寫出字節到數組b,off:表明第一個寫出字節在b中的偏移量,len:寫出字節的最大數量
8void write(byte[] b, int off, int len)
9
10//沖刷輸出流,也就是將全部緩衝的數據發送到目的地
11void flush()
12
13// 關閉輸出流
14void close()
15

一樣地,OutputStream也提供了一些基礎流的實現,這些實現也能夠和特定的目的地(接收端)對應起來,好比輸出到字節數組或者是輸出到文件/管道等。

功能
ByteArrayOutputStream 在內存中建立一個緩衝區,全部送往「流」的數據都要放在此緩衝區
FileOutputStream 將數據寫入文件
PipedOutputStream 和PipedInputStream配合使用。實現管道化
FilterOutputStream 抽象類,主要是做爲「裝飾器」的接口類,實現其餘的功能流

使用裝飾器包裝有用的流

Java IO 流體系使用了裝飾器模式來給哪些基礎的輸入/輸出流添加額外的功能。這寫額外的功能多是:能夠將流緩衝起來提升性能、是流可以讀寫基本數據類型等。

這些經過裝飾器模式添加功能的流類型都是從FilterInputStream和FilterOutputStream抽象類擴展而來的。能夠再返回文章最開始說到IO流體系的層次時,那幾種圖加深下印象。

FilterInputStream類型

功能
DataInputStream 和DataOutputStream搭配使用,使得流能夠讀取int char long等基本數據類型
BufferedInputStream 使用緩衝區,主要是提升性能
LineNumberInputStream 跟蹤輸入流中的行號,可使用getLineNumber、setLineNumber(int)
PushbackInputStream 使得流能彈出「一個字節的緩衝區」,能夠將讀到的最後一個字符回退

FilterOutStream類型

功能
DataOutputStream 和DataInputStream搭配使用,使得流能夠寫入int char long等基本數據類型
PrintStream 用於產生格式化的輸出
BufferedOutputStream 使用緩衝區,能夠調用flush()清空緩衝區

大多數狀況下,其實咱們在使用流的時候都是輸入流和輸出流搭配使用的。目的就是爲了轉移和存儲數據,單獨的read()對咱們而言有啥用呢,讀出來一個字節能幹啥?對吧。所以要理解流的使用就是搭配起來或者使用功能流組合起來去轉移或者存儲數據。

Reader和Writer

Reader是Java IO中全部Reader的基類。ReaderInputStream相似,不一樣點在於,Reader基於字符而非基於字節。

Writer是Java IO中全部Writer的基類。與ReaderInputStream的關係相似,Writer基於字符而非基於字節,Writer用於寫入文本,OutputStream用於寫入字節。

ReaderWriter的基礎功能類,能夠對比InputStreamOutputStream來學習。

面向字節 面向字符
InputStream Reader
OutputStream Writer
FileInputStream FileReader
FileOutputStream FileWriter
ByteArrayInputStream CharArrayReader
ByteArrayOutputStream CharArrayWriter
PipedInputStream PipedReader
PipedOutputStream PipedWriter
StringBufferInputStream(已棄用) StringReader
無對應類 StringWriter

有兩個「適配器」 流類型,它們能夠將字節流轉化成字節流。這就是InputStreamReader 能夠將InputStream轉成爲Reader,OutputStreamWriter能夠將OutputStream轉成爲Writer。

適配器類,字節流轉字符流

在這裏插入圖片描述
在這裏插入圖片描述

固然也有相似字節流的裝飾器實現方式,給字符流添加額外的功能或這說是行爲。這些功能字符流類主要有:

  • BufferedReader
  • BufferedWriter
  • PrintWriter
  • LineNumberReader
  • PushbackReader

System類中的I/O流

想一想你的第一個Java程序是啥?我沒猜錯的話,應該是 hello world。

1System.out.println("hello world")

簡單到使人髮指,今天就說說標準的輸入/輸出流。

在標準IO模型中,Java提供了System.in、System.out和System.error。

先說System.in,看下源碼

1public final static InputStream in

是一個靜態域,未被包裝過的InputStream。一般咱們會使用BufferedReader進行包裝而後一行一行地讀取輸入,這裏就要用到前面說的適配器流InputStreamReader

1public class SystemInReader {
2    public static void main(String[] args) throws IOException {
3        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
4        String s;
5        while ((s = reader.readLine()) != null && s.length() != 0){
6            System.out.println(s);
7        }
8    }
9}

該程序等待會一直等待咱們輸入,輸入啥,後面會接着輸出。輸入空字符串能夠結束。

11
21
3123
4123

System.out是一個PrintStream流。System.out通常會把你寫到其中的數據輸出到控制檯上。System.out一般僅用在相似命令行工具的控制檯程序上。System.out也常常用於打印程序的調試信息(儘管它可能並非獲取程序調試信息的最佳方式)。

System.err是一個PrintStream流。System.err與System.out的運行方式相似,但它更多的是用於打印錯誤文本。

能夠將這些系統流重定向

儘管System.in, System.out, System.err這3個流是java.lang.System類中的靜態成員,而且已經預先在JVM啓動的時候初始化完成,你依然能夠更改它們。

可使用setIn(InputStream)、setOut(PrintStream)、setErr(PrintStream)進行重定向。好比能夠將控制檯的輸出重定向到文件中。

1OutputStream output = new FileOutputStream("d:/system.out.txt");
2PrintStream printOut = new PrintStream(output);
3System.setOut(printOut);

壓縮(ZIP文檔)

Java IO類庫是支持讀寫壓縮格式的數據流的。咱們能夠把一個或一批文件壓縮成一個zip文檔。這些壓縮相關的流類是按字節處理的。先看下設計壓縮解壓縮的相關流類。

壓縮類 功能
CheckedInputStream getCheckSum()能夠爲任何InputStream產生校驗和(不只是解壓縮)
CheckedOutputStream getCheckSum()能夠爲任何OutputStream產生校驗和(不只是壓縮)
DeflaterOutputStream 壓縮類的基類
ZipOutputStream 繼承自DeflaterOutputStream,將數據壓縮成Zip文件格式
GZIPOutputStream 繼承自DeflaterOutputStream,將數據壓縮成GZIP文件格式
InflaterInputStream 解壓縮類的基類
ZipInputStream 繼承自InflaterInputStream,解壓縮Zip文件格式的數據
GZIPInputStream 繼承自InflaterInputStream,解壓縮GZIP文件格式的數據

表格中CheckedInputStream 和 CheckedOutputStream 通常會和Zip壓縮解壓過程配合使用,主要是爲了保證咱們壓縮和解壓過程數據包的正確性,獲得的是中間沒有被篡改過的數據。

咱們以CheckedInputStream 爲例,它的構造器須要傳入一個Checksum類型:

1    public CheckedInputStream(InputStream in, Checksum cksum) {
2        super(in);
3        this.cksum = cksum;
4    }

而Checksum 是一個接口,能夠看到這裏又用到了策略模式,具體的校驗算法是能夠選擇的。Java類庫給我提供了兩種校驗和算法:Adler32 和 CRC32,性能方面可能Adler32 會更好一些,不過CRC32可能更準確。各有優劣吧。

好了,接下來看下壓縮/解壓縮流的具體使用。

將多個文件壓縮成zip包

 1public class ZipFileUtils {
2    public static void compressFiles(File[] files, String zipPath) throws IOException {
3
4        // 定義文件輸出流,代表是要壓縮成zip文件的
5        FileOutputStream f = new FileOutputStream(zipPath);
6
7        // 給輸出流增長校驗功能
8        CheckedOutputStream checkedOs = new CheckedOutputStream(f,new Adler32());
9
10        // 定義zip格式的輸出流,這裏要明白一直在使用裝飾器模式在給流添加功能
11        // ZipOutputStream 也是從FilterOutputStream 繼承下來的
12        ZipOutputStream zipOut = new ZipOutputStream(checkedOs);
13
14        // 增長緩衝功能,提升性能
15        BufferedOutputStream buffOut = new BufferedOutputStream(zipOut);
16
17        //對於壓縮輸出流咱們能夠設置個註釋
18        zipOut.setComment("zip test");
19
20        // 下面就是從Files[] 數組中讀入一批文件,而後寫入zip包的過程
21        for (File file : files){
22
23            // 創建讀取文件的緩衝流,一樣是裝飾器模式使用BufferedReader
24            // 包裝了FileReader
25            BufferedReader bfReadr = new BufferedReader(new FileReader(file));
26
27            // 一個文件對象在zip流中用一個ZipEntry表示,使用putNextEntry添加到zip流中
28            zipOut.putNextEntry(new ZipEntry(file.getName()));
29
30            int c;
31            while ((c = bfReadr.read()) != -1){
32                buffOut.write(c);
33            }
34
35            // 注意這裏要關閉
36            bfReadr.close();
37            buffOut.flush();
38        }
39        buffOut.close();
40    }
41
42    public static void main(String[] args) throws IOException {
43        String dir = "d:";
44        String zipPath = "d:/test.zip";
45        File[] files = Directory.getLocalFiles(dir,".*\\.txt");
46        ZipFileUtils.compressFiles(files, zipPath);
47    }
48}

在main函數中咱們使用了本文中 File實際上是個工具類 章節裏的Directory工具類。

解壓縮zip包到目標文件夾

 1    public static void unConpressZip(String zipPath, String destPath) throws IOException {
2        if(!destPath.endsWith(File.separator)){
3            destPath = destPath + File.separator;
4            File file = new File(destPath);
5            if(!file.exists()){
6                file.mkdirs();
7            }
8        }
9        // 新建文件輸入流類,
10        FileInputStream fis = new FileInputStream(zipPath);
11
12        // 給輸入流增長檢驗功能
13        CheckedInputStream checkedIns = new CheckedInputStream(fis,new Adler32());
14
15        // 新建zip輸出流,由於讀取的zip格式的文件嘛
16        ZipInputStream zipIn = new ZipInputStream(checkedIns);
17
18        // 增長緩衝流功能,提升性能
19        BufferedInputStream buffIn = new BufferedInputStream(zipIn);
20
21        // 從zip輸入流中讀入每一個ZipEntry對象
22        ZipEntry zipEntry;
23        while ((zipEntry = zipIn.getNextEntry()) != null){
24            System.out.println("解壓中" + zipEntry);
25
26            // 將解壓的文件寫入到目標文件夾下
27            int size;
28            byte[] buffer = new byte[1024];
29            FileOutputStream fos = new FileOutputStream(destPath + zipEntry.getName());
30            BufferedOutputStream bos = new BufferedOutputStream(fos, buffer.length);
31            while ((size = buffIn.read(buffer, 0, buffer.length)) != -1) {
32                bos.write(buffer, 0, size);
33            }
34            bos.flush();
35            bos.close();
36        }
37        buffIn.close();
38
39        // 輸出校驗和
40        System.out.println("校驗和:" + checkedIns.getChecksum().getValue());
41    }
42
43    // 在main函數中直接調用
44    public static void main(String[] args) throws IOException {
45        String dir = "d:";
46        String zipPath = "d:/test.zip";
47//        File[] files = Directory.getLocalFiles(dir,".*\\.txt");
48//        ZipFileUtils.compressFiles(files, zipPath);
49
50        ZipFileUtils.unConpressZip(zipPath,"F:/ziptest");
51    }

這裏解壓zip包還有一種更加簡便的方法,使用ZipFile對象。該對象的entries()方法直接返回ZipEntry類型的枚舉。看下代碼片斷:

1        ZipFile zipFile = new ZipFile("test.zip");
2        Enumeration e = zipFile.entries();
3        while (e.hasMoreElements()){
4            ZipEntry zipEntry = (ZipEntry) e.nextElement();
5            System.out.println("file:" + zipEntry);
6        }

對象序列化

什麼是序列化和反序列化呢?

序列化就是將對象轉成字節序列的過程,反序列化就是將字節序列重組成對象的過程。

在這裏插入圖片描述
在這裏插入圖片描述

爲何要有對象序列化機制

程序中的對象,實際上是存在有內存中,當咱們JVM關閉時,不管如何它都不會繼續存在了。那有沒有一種機制能讓對象具備「持久性」呢?序列化機制提供了一種方法,你能夠將對象序列化的字節流輸入到文件保存在磁盤上。

序列化機制的另一種意義即是咱們能夠經過網絡傳輸對象了,Java中的 遠程方法調用(RMI),底層就須要序列化機制的保證。

在Java中怎麼實現序列化和反序列化

首先要序列化的對象必須實現一個Serializable接口(這是一個標識接口,不包括任何方法)

1public interface Serializable {
2}

其次須要是用兩個對象流類:ObjectInputStream 和ObjectOutputStream主要使用ObjectInputStream對象的readObject方法讀入對象、ObjectOutputStream的writeObject方法寫入對象到流中

下面咱們經過序列化機制將一個簡單的pojo對象寫入到文件,並再次讀入到程序內存。

 1public class User implements Serializable {
2    private String name;
3    private int age;
4
5    public User(String name, int age) {
6        this.name = name;
7        this.age = age;
8    }
9
10    @Override
11    public String toString() {
12        return "User{" +
13                "name='" + name + '\'' +
14                ", age='
" + age + '\'' +
15                '}';
16    }
17
18    public static void main(String[] args) throws IOException, ClassNotFoundException {
19        User user = new User("
二營長",18);
20        ObjectOutputStream objectOps = new ObjectOutputStream(new FileOutputStream("
f:/user.out"));
21        objectOps.writeObject(user);
22        objectOps.close();
23
24        // 再從文件中取出對象
25        ObjectInputStream objectIns = new ObjectInputStream(new FileInputStream("
f:/user.out"));
26
27        // 這裏要作一次強轉
28        User user1 = (User) objectIns.readObject();
29        System.out.println(user1);
30        objectIns.close();
31    }
32}
33

程序運行結果:

1User{name='二營長', age='18'}

不想序列化的數據使用transient(瞬時)關鍵字屏蔽

若是咱們上面的user對象有一個password字段,屬於敏感信息,這種是不能走序列化的方式的,可是實現了Serializable 接口的對象會自動序列化全部的數據域,怎麼辦呢?在password字段上加上關鍵字transient就行了。

1 private transient String password;

序列化機制就簡單介紹到這裏吧。這是Java原生的序列化,如今市面上有好多序列化協議能夠選擇,好比Json、FastJson、Thrift、Hessian 、protobuf等

I/O流的典型使用方式

IO流種類繁多,能夠經過不一樣的方式組合I/O流類,但平時咱們經常使用的也就幾種組合。下盤經過示例的方式盤點幾種I/O流的典型用法。

緩衝輸入文件

 1public class BufferedInutFile {
2    public static String readFile(String fileName) throws IOException {
3        BufferedReader bf = new BufferedReader(new FileReader(fileName));
4        String s;
5
6        // 這裏讀取的內容存在了StringBuilder,固然也能夠作其餘處理
7        StringBuilder sb = new StringBuilder();
8        while ((s = bf.readLine()) != null){
9            sb.append(s + "\n");
10        }
11        bf.close();
12        return sb.toString();
13    }
14
15    public static void main(String[] args) throws IOException {
16        System.out.println(BufferedInutFile.readFile("d:/1.txt"));
17    }
18}

格式化內存輸入

要讀取格式化的數據,可使用DataInputStream。

 1public class FormattedMemoryInput {
2    public static void main(String[] args) throws IOException {
3        try {
4            DataInputStream dataIns = new DataInputStream(
5                    new ByteArrayInputStream(BufferedInutFile.readFile("f:/FormattedMemoryInput.java").getBytes()));
6            while (true){
7                System.out.print((char) dataIns.readByte());
8            }
9        } catch (EOFException e) {
10            System.err.println("End of stream");
11        }
12    }
13}

上面程序會在控制檯輸出當前類自己的全部代碼,而且會拋出一個EOFException異常。拋出異常的緣由是已經到留的結尾了還在讀數據。這裏可使用available()作判斷還有多少能夠的字符。

 1package com.herp.pattern.strategy;
2
3import java.io.ByteArrayInputStream;
4import java.io.DataInputStream;
5import java.io.IOException;
6
7public class FormattedMemoryInput {
8    public static void main(String[] args) throws IOException {
9        DataInputStream dataIns = new DataInputStream(
10                new ByteArrayInputStream(BufferedInutFile.readFile("FormattedMemoryInput.java").getBytes()));
11        while (true){
12            System.out.println((char) dataIns.readByte());
13        }
14    }
15}

基本的文件輸出

FileWriter對象能夠向文件寫入數據。首先建立一個FileWriter和指定的文件關聯,而後使用BufferedWriter將其包裝提供緩衝功能,爲了提供格式化機制,它又被裝飾成爲PrintWriter

 1public class BasicFileOutput {
2    static String file = "BasicFileOutput.out";
3
4    public static void main(String[] args) throws IOException {
5        BufferedReader in = new BufferedReader(new StringReader(BufferedInutFile.readFile("f:/BasicFileOutput.java")));
6        PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(file)));
7
8        int lineCount = 1;
9        String s;
10        while ((s = in.readLine()) != null){
11            out.println(lineCount ++ + ": " + s);
12        }
13        out.close();
14        in.close();
15    }
16}

下面是咱們寫出的BasicFileOutput.out文件,能夠看到咱們經過代碼字節加上了行號

 11package com.herp.pattern.strategy;
22
33import java.io.*;
44
55public class BasicFileOutput {
66:     static String file = "BasicFileOutput.out";
77
88:     public static void main(String[] args) throws IOException {
99:         BufferedReader in = new BufferedReader(new StringReader(BufferedInutFile.readFile("f:/BasicFileOutput")));
1010:         PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(file)));
1111
1212:         int lineCount = 1;
1313:         String s;
1414:         while ((s = in.readLine()) != null){
1515:             out.println(lineCount ++ + ": " + s);
1616:         }
1717:         out.close();
1818:         in.close();
1919:     }
2020: }

數據的存儲和恢復

爲了輸出可供另外一個「流」恢復的數據,咱們須要使用DataOutputStream寫入數據,而後使用DataInputStream恢復數據。固然這些流能夠是任何形式(這裏的形式其實就是咱們前面說過的流的兩端的類型),好比文件。

 1public class StoringAndRecoveringData {
2    public static void main(String[] args) throws IOException {
3        DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream("data.txt")));
4        out.writeDouble(3.1415926);
5        out.writeUTF("我是二營長");
6        out.writeInt(125);
7        out.writeUTF("點贊加關注");
8        out.close();
9
10        DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream("data.txt")));
11        System.out.println(in.readDouble());
12        System.out.println(in.readUTF());
13        System.out.println(in.readInt());
14        System.out.println(in.readUTF());
15        in.close();
16    }
17}

輸出結果:

13.1415926
2我是二營長
3125
4點贊加關注

須要注意的是咱們使用writeUTF()和readUTF()來寫入和讀取字符串。

好了。關於Java I/O流體系就總結這麼多吧。


我是二營長,一個轉行的程序員,菜雞一枚,熱衷於碼磚。

相關文章
相關標籤/搜索