精通java图片_面试必备:详解Java I/O流,掌握这些就可以说精通了?
Java IO概述
IO就是輸入/輸出。Java IO類庫基于抽象基礎類InputStream和OutputStream構建了一套I/O體系,主要解決從數據源讀入數據和將數據寫入到目的地問題。我們把數據源和目的地可以理解為IO流的兩端。當然,通常情況下,這兩端可能是文件或者網絡連接。
我們用下面的圖描述下,加深理解:
從一種數據源中通過InputStream流對象讀入數據到程序內存中
在這里插入圖片描述
當然我們把上面的圖再反向流程,就是OutputStream的示意了。
在這里插入圖片描述
其實除了面向字節流的InputStream/OutputStream體系外,Java IO類庫還提供了面向字符流的Reader/Writer體系。Reader/Writer繼承結構主要是為了國際化,因為它能更好地處理16位的Unicode字符。
在學習是這兩套IO流處理體系可以對比參照著學習,因為有好多相似之處。
要理解總體設計
剛開始寫IO代碼,總被各種IO流類搞得暈頭轉向。這么多IO相關的類,各種方法,啥時候能記住。
其實只要我們掌握了IO類庫的總體設計思路,理解了它的層次脈絡之后,就很清晰。知道啥時候用哪些流對象去組合想要的功能就好了,API的話,可以查手冊的嘛。
首先從流的流向上可以分為輸入流InputStream或Reader,輸出流OutputStream或Writer。任何從InputStream或Reader派生而來的類都有read()基本方法,讀取單個字節或字節數組;任何從OutputSteam或Writer派生的類都含有write()的基本方法,用于寫單個字節或字節數組。
從操作字節還是操作字符的角度,有面向字節流的類,基本都以XxxStream結尾,面向字符流的類都以XxxReader或XxxWriter結尾。當然這兩種類型的流是可以轉化的,有兩個轉化流的類,這個后面會說到。
一般在使用IO流的時候會有下面類似代碼:
new?FileInputStream(new?File("a.txt"));
new?BufferedInputStream(inputStream);
這里其實是一種裝飾器模式的使用,IO流體系中使用了裝飾器模式包裝了各種功能流類。不了解裝飾器模式的看下這篇【詳解設計模式】-裝飾者模式
在Java IO流體系中FilterInputStream/FilterOutStream和FilterReader/FilterWriter就是裝飾器模式的接口類,從該類向下包裝了一些功能流類。有DataInputStream、BufferedInputStream、LineNumberInputStream、PushbackInputStream等,當然還有輸出的功能流類;面向字符的功能流類等。
下面幾張圖描述了整個IO流的繼承體系結構
InputStream流體系
在這里插入圖片描述
OutputStream流體系
在這里插入圖片描述
Reader體系
在這里插入圖片描述
Writer體系
在這里插入圖片描述
最后再附加一張表加深印象:
在這里插入圖片描述
File其實是個工具類
File類其實不止是代表一個文件,它也能代表一個目錄下的一組文件(代表一個文件路徑)。下面我們盤點一下File類中最常用到的一些方法
String???getAbsolutePath()???//?獲取絕對路徑
long???getFreeSpace()???????//?返回分區中未分配的字節數。
getName()?????????//?返回文件或文件夾的名稱。
getParent()?????????//?返回父目錄的路徑名字符串;如果沒有指定父目錄,則返回?null。
getParentFile()??????//?返回父目錄File對象
getPath()?????????//?返回路徑名字符串。
long???getTotalSpace()??????//?返回此文件分區大小。
long???getUsableSpace()????//返回占用字節數。
int???hashCode()?????????????//文件哈希碼。
long???lastModified()???????//?返回文件最后一次被修改的時間。
long???length()??????????//?獲取長度,字節數。
boolean?canRead()??//判斷是否可讀
boolean?canWrite()??//判斷是否可寫
boolean?isHidden()??//判斷是否隱藏
//?成員函數
static?File[]????listRoots()????//?列出可用的文件系統根。
boolean????renameTo(File?dest)????//?重命名
boolean????setExecutable(boolean?executable)????//?設置執行權限。
boolean????setExecutable(boolean?executable,?boolean?ownerOnly)????//?設置其他所有用戶的執行權限。
boolean????setLastModified(long?time)???????//?設置最后一次修改時間。
boolean????setReadable(boolean?readable)????//?設置讀權限。
boolean????setReadable(boolean?readable,?boolean?ownerOnly)????//?設置其他所有用戶的讀權限。
boolean????setWritable(boolean?writable)????//?設置寫權限。
boolean????setWritable(boolean?writable,?boolean?ownerOnly)????//?設置所有用戶的寫權限。
需要注意的是,不同系統對文件路徑的分割符表是不一樣的,比如Windows中是“\”,Linux是“/”。而File類給我們提供了抽象的表示File.separator,屏蔽了系統層的差異。因此平時在代碼中不要使用諸如“\”這種代表路徑,可能造成Linux平臺下代碼執行錯誤。
下面是一些示例:
根據傳入的規則,遍歷得到目錄中所有的文件構成的File對象數組
public?class?Directory{
public?static?File[]?getLocalFiles(File?dir,?final?String?regex){
return?dir.listFiles(new?FilenameFilter()?{
private?Pattern?pattern?=?Pattern.compile(regex);
public?boolean?accept(File?dir,?String?name){
return?pattern.matcher(new?File(name).getName()).matches();
//?重載方法
public?static?File[]?getLocalFiles(String?path,?final?String?regex){
return?getLocalFiles(new?File(path),regex);
public?static?void?main(String[]?args){
"d:";
".*\\.txt");
for(File?file?:?files){
輸出結果:
上面的代碼中dir.listFiles(FilenameFilter ) 是策略模式的一種實現,而且使用了匿名內部類的方式。
上面的例子是《Java 編程思想》中的示例,這本書中的每個代碼示例都很經典,Bruce Eckel大神把面向對象的思想應用的爐火純青,非常值得細品。
InputStream和OutputStream
InputStream是輸入流,前面已經說到,它是從數據源對象將數據讀入程序內容時,使用的流對象。通過看InputStream的源碼知道,它是一個抽象類,
public?abstract?class?InputStream??extends?Object??implements?Closeable
提供了一些基礎的輸入流方法:
//從數據中讀入一個字節,并返回該字節,遇到流的結尾時返回-1
abstract?int?read()
//讀入一個字節數組,并返回實際讀入的字節數,最多讀入b.length個字節,遇到流結尾時返回-1
int?read(byte[]?b)
//?讀入一個字節數組,返回實際讀入的字節數或者在碰到結尾時返回-1.
//b:代表數據讀入的數組,?off:代表第一個讀入的字節應該被放置的位置在b中的偏移量,len:讀入字節的最大數量
int?read(byte[],int?off,int?len)
//?返回當前可以讀入的字節數量,如果是從網絡連接中讀入,這個方法要慎用,
int?available()
//在輸入流中跳過n個字節,返回實際跳過的字節數
long?skip(long?n)
//標記輸入流中當前的位置
void?mark(int?readlimit)
//判斷流是否支持打標記,支持返回true
boolean?markSupported()
//?返回最后一個標記,隨后對read的調用將重新讀入這些字節。
void?reset()
//關閉輸入流,這個很重要,流使用完一定要關閉
void?close()
直接從InputStream繼承的流,可以發現,基本上對應了每種數據源類型。
類
功能
ByteArrayInputStream
將字節數組作為InputStream
StringBufferInputStream
將String轉成InputStream
FileInputStream
從文件中讀取內容
PipedInputStream
產生用于寫入相關PipedOutputStream的數據。實現管道化
SequenceInputStream
將兩個或多個InputStream對象轉換成單一的InputStream
FilterInputStream
抽象類,主要是作為“裝飾器”的接口類,實現其他的功能流
OutputStream是輸出流的抽象,它是將程序內存中的數據寫入到目的地(也就是接收數據的一端)。看下類的簽名:
public?abstract?class?OutputStream?implements?Closeable,?Flushable{}
提供了基礎方法相比輸入流來說簡單多了,主要就是write寫方法(幾種重載的方法)、flush沖刷和close關閉。
//?寫出一個字節的數據
abstract?void?write(int?n)
//?寫出字節到數據b
void?write(byte[]?b)
//?寫出字節到數組b,off:代表第一個寫出字節在b中的偏移量,len:寫出字節的最大數量
void?write(byte[]?b,?int?off,?int?len)
//沖刷輸出流,也就是將所有緩沖的數據發送到目的地
void?flush()
//?關閉輸出流
void?close()
同樣地,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的基類。Reader與InputStream類似,不同點在于,Reader基于字符而非基于字節。
Writer是Java IO中所有Writer的基類。與Reader和InputStream的關系類似,Writer基于字符而非基于字節,Writer用于寫入文本,OutputStream用于寫入字節。
Reader和Writer的基礎功能類,可以對比InputStream、OutputStream來學習。
面向字節
面向字符
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。
"hello?world")
簡單到令人發指,今天就說說標準的輸入/輸出流。
在標準IO模型中,Java提供了System.in、System.out和System.error。
先說System.in,看下源碼
public?final?static?InputStream?in
是一個靜態域,未被包裝過的InputStream。通常我們會使用BufferedReader進行包裝然后一行一行地讀取輸入,這里就要用到前面說的適配器流InputStreamReader。
while?((s?=?reader.readLine())?!=?null?&&?s.length()?!=?0){
該程序等待會一直等待我們輸入,輸入啥,后面會接著輸出。輸入空字符串可以結束。
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)進行重定向。比如可以將控制臺的輸出重定向到文件中。
new?FileOutputStream("d:/system.out.txt");
new?PrintStream(output);
壓縮(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類型:
public?CheckedInputStream(InputStream?in,?Checksum?cksum){
super(in);
this.cksum?=?cksum;
而Checksum 是一個接口,可以看到這里又用到了策略模式,具體的校驗算法是可以選擇的。Java類庫給我提供了兩種校驗和算法:Adler32 和 CRC32,性能方面可能Adler32 會更好一些,不過CRC32可能更準確。各有優劣吧。
好了,接下來看下壓縮/解壓縮流的具體使用。
將多個文件壓縮成zip包
public?class?ZipFileUtils{
public?static?void?compressFiles(File[]?files,?String?zipPath)?throws?IOException{
//?定義文件輸出流,表明是要壓縮成zip文件的
new?FileOutputStream(zipPath);
//?給輸出流增加校驗功能
new?CheckedOutputStream(f,new?Adler32());
//?定義zip格式的輸出流,這里要明白一直在使用裝飾器模式在給流添加功能
//?ZipOutputStream?也是從FilterOutputStream?繼承下來的
new?ZipOutputStream(checkedOs);
//?增加緩沖功能,提高性能
new?BufferedOutputStream(zipOut);
//對于壓縮輸出流我們可以設置個注釋
"zip?test");
//?下面就是從Files[]?數組中讀入一批文件,然后寫入zip包的過程
for?(File?file?:?files){
//?建立讀取文件的緩沖流,同樣是裝飾器模式使用BufferedReader
//?包裝了FileReader
new?BufferedReader(new?FileReader(file));
//?一個文件對象在zip流中用一個ZipEntry表示,使用putNextEntry添加到zip流中
new?ZipEntry(file.getName()));
int?c;
while?((c?=?bfReadr.read())?!=?-1){
//?注意這里要關閉
public?static?void?main(String[]?args)?throws?IOException{
"d:";
"d:/test.zip";
".*\\.txt");
在main函數中我們使用了本文中 File其實是個工具類 章節里的Directory工具類。
解壓縮zip包到目標文件夾
if(!destPath.endsWith(File.separator)){
if(!file.exists()){
while?((zipEntry?=?zipIn.getNextEntry())?!=?null){
"解壓中"?+?zipEntry);
while?((size?=?buffIn.read(buffer,?0,?buffer.length))?!=?-1)?{
"校驗和:"?+?checkedIns.getChecksum().getValue());
"d:";
"d:/test.zip";
".*\\.txt");
"F:/ziptest");
這里解壓zip包還有一種更加簡便的方法,使用ZipFile對象。該對象的entries()方法直接返回ZipEntry類型的枚舉。看下代碼片段:
new?ZipFile("test.zip");
while?(e.hasMoreElements()){
"file:"?+?zipEntry);
對象序列化
什么是序列化和反序列化呢?
序列化就是將對象轉成字節序列的過程,反序列化就是將字節序列重組成對象的過程。
在這里插入圖片描述
為什么要有對象序列化機制
程序中的對象,其實是存在有內存中,當我們JVM關閉時,無論如何它都不會繼續存在了。那有沒有一種機制能讓對象具有“持久性”呢?序列化機制提供了一種方法,你可以將對象序列化的字節流輸入到文件保存在磁盤上。
序列化機制的另外一種意義便是我們可以通過網絡傳輸對象了,Java中的 遠程方法調用(RMI),底層就需要序列化機制的保證。
在Java中怎么實現序列化和反序列化
首先要序列化的對象必須實現一個Serializable接口(這是一個標識接口,不包括任何方法)
public?interface?Serializable{
其次需要是用兩個對象流類:ObjectInputStream 和ObjectOutputStream。主要使用ObjectInputStream對象的readObject方法讀入對象、ObjectOutputStream的writeObject方法寫入對象到流中
下面我們通過序列化機制將一個簡單的pojo對象寫入到文件,并再次讀入到程序內存。
toString()?{
return?"User{"?+
"name='"?+?name?+?'\''?+"?+?age?+?'\''?+二營長",18);f:/user.out"));f:/user.out"));
程序運行結果:
'二營長',?age='18'}
不想序列化的數據使用transient(瞬時)關鍵字屏蔽
如果我們上面的user對象有一個password字段,屬于敏感信息,這種是不能走序列化的方式的,但是實現了Serializable 接口的對象會自動序列化所有的數據域,怎么辦呢?在password字段上加上關鍵字transient就好了。
private?transient?String?password;
序列化機制就簡單介紹到這里吧。這是Java原生的序列化,現在市面上有好多序列化協議可以選擇,比如Json、FastJson、Thrift、Hessian 、protobuf等。
I/O流的典型使用方式
IO流種類繁多,可以通過不同的方式組合I/O流類,但平時我們常用的也就幾種組合。下盤通過示例的方式盤點幾種I/O流的典型用法。
緩沖輸入文件
public?class?BufferedInutFile{
public?static?String?readFile(String?fileName)?throws?IOException{
new?BufferedReader(new?FileReader(fileName));
//?這里讀取的內容存在了StringBuilder,當然也可以做其他處理
new?StringBuilder();
while?((s?=?bf.readLine())?!=?null){
"\n");
return?sb.toString();
public?static?void?main(String[]?args)?throws?IOException{
"d:/1.txt"));
格式化內存輸入
要讀取格式化的數據,可以使用DataInputStream。
public?class?FormattedMemoryInput{
public?static?void?main(String[]?args)?throws?IOException{
try?{
new?DataInputStream(
new?ByteArrayInputStream(BufferedInutFile.readFile("f:/FormattedMemoryInput.java").getBytes()));
while?(true){
char)?dataIns.readByte());
catch?(EOFException?e)?{
"End?of?stream");
上面程序會在控制臺輸出當前類本身的所有代碼,并且會拋出一個EOFException異常。拋出異常的原因是已經到留的結尾了還在讀數據。這里可以使用available()做判斷還有多少可以的字符。
package?com.herp.pattern.strategy;
import?java.io.ByteArrayInputStream;
import?java.io.DataInputStream;
import?java.io.IOException;
public?class?FormattedMemoryInput{
public?static?void?main(String[]?args)?throws?IOException{
new?DataInputStream(
new?ByteArrayInputStream(BufferedInutFile.readFile("FormattedMemoryInput.java").getBytes()));
while?(true){
char)?dataIns.readByte());
基本的文件輸出
FileWriter對象可以向文件寫入數據。首先創建一個FileWriter和指定的文件關聯,然后使用BufferedWriter將其包裝提供緩沖功能,為了提供格式化機制,它又被裝飾成為PrintWriter。
public?class?BasicFileOutput{
static?String?file?=?"BasicFileOutput.out";
public?static?void?main(String[]?args)?throws?IOException{
new?BufferedReader(new?StringReader(BufferedInutFile.readFile("f:/BasicFileOutput.java")));
new?PrintWriter(new?BufferedWriter(new?FileWriter(file)));
int?lineCount?=?1;
while?((s?=?in.readLine())?!=?null){
":?"?+?s);
下面是我們寫出的BasicFileOutput.out文件,可以看到我們通過代碼字節加上了行號
1:?package?com.herp.pattern.strategy;
2:
3:?import?java.io.*;
4:
5:?public?class?BasicFileOutput{
6:?????static?String?file?=?"BasicFileOutput.out";
7:
8:?????public?static?void?main(String[]?args)?throws?IOException{
9:?????????BufferedReader?in?=?new?BufferedReader(new?StringReader(BufferedInutFile.readFile("f:/BasicFileOutput")));
10:?????????PrintWriter?out?=?new?PrintWriter(new?BufferedWriter(new?FileWriter(file)));
11:
12:?????????int?lineCount?=?1;
13:?????????String?s;
14:?????????while?((s?=?in.readLine())?!=?null){
15:?????????????out.println(lineCount?++?+?":?"?+?s);
16:?????????}
17:?????????out.close();
18:?????????in.close();
19:?????}
20:?}
數據的存儲和恢復
為了輸出可供另一個“流”恢復的數據,我們需要使用DataOutputStream寫入數據,然后使用DataInputStream恢復數據。當然這些流可以是任何形式(這里的形式其實就是我們前面說過的流的兩端的類型),比如文件。
public?class?StoringAndRecoveringData{
public?static?void?main(String[]?args)?throws?IOException{
new?DataOutputStream(new?BufferedOutputStream(new?FileOutputStream("data.txt")));
3.1415926);
"我是二營長");
125);
"點贊加關注");
new?DataInputStream(new?BufferedInputStream(new?FileInputStream("data.txt")));
輸出結果:
需要注意的是我們使用writeUTF()和readUTF()來寫入和讀取字符串。
好了。關于Java I/O流體系就總結這么多吧。
我是二營長,一個轉行的程序員,菜雞一枚,熱衷于碼磚。
總結
以上是生活随笔為你收集整理的精通java图片_面试必备:详解Java I/O流,掌握这些就可以说精通了?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: es用canals怎么和mysql同步_
- 下一篇: infinity mysql_MySql