Java I/O系統
File類
在學習那些真正用於在流中讀寫資料的類之前,我們先學習FIle類,它可以幫助我們處理檔案目錄的問題。
FIle物件既可以代表一個特定檔案的名稱,又可以代表一個目錄下的資料夾的名稱。
目錄列表器
如果你想檢視某個資料夾下有什麼,有兩種方法來使用File物件。
1.如果你想檢視目標資料夾下的所有檔案。呼叫list()它將返回一個字元陣列,其中儲存著目標資料夾下的所有檔案。
2.如果目標資料夾中檔案數量過多,你想篩選具有某些相同特徵的檔案,就要用到"目錄過濾器"。呼叫list(FilenameFilter filter)它同樣會返回一個字元陣列,其中儲存組目標資料夾下過濾後的所有檔案。
public class DirList { public static void main(String[] args) { File path = new File("D:\\idea\\workspace\\project18"); /* 方法一: 呼叫無參list()返回字元陣列,用asList將字元陣列轉換成List然後輸出 */ List<String> list = Arrays.asList(path.list()); for (String s : list) System.out.println(s); /* 方法二: 呼叫有參list(),過濾出字尾是.iml的檔案,存在字元陣列中。 用asList將字元陣列轉換成List然後輸出 */ String[] str =path.list(new FilenameFilter() { @Override public boolean accept(File file, String s) { return s.endsWith("iml"); } }); List<String> list1 = Arrays.asList(str); System.out.println(); for (String s : list1) System.out.println(s); } }
目錄實用工具
File物件比檔名更有用,因為它包含更多資訊。
1.呼叫無參listFiles(),返回一個File[].
2.呼叫有參listFiles(FilenameFilter filter),返回過濾後的File[]。
public class Directory { static void func(File file){ File[] fs = file.listFiles(); for(File f:fs){ if(f.isDirectory()) //若是目錄,則遞迴列印該目錄下的檔案 func(f); if(f.isFile()) //若是檔案,直接列印 System.out.println(f); } } static void func(File file, String regex){ File[] fs = file.listFiles(new FilenameFilter() { @Override public boolean accept(File file, String name) { return Pattern.compile(regex).matcher(new File(name).getName()).matches(); } }); for(File f:fs){ if(f.isDirectory()) //若是目錄,則遞迴列印該目錄下的檔案 func(f); if(f.isFile()) //若是檔案,直接列印 System.out.println(f); } } public static void main(String[] args) { File path = new File("D:\\idea\\workspace\\project11"); /* 方法一: 呼叫無參listFiles(),返回一個File[] */ Directory.func(path); System.out.println(); System.out.println(); System.out.println(); System.out.println(); /* 方法二: 呼叫有參listFiles(FilenameFilter filter),返回過濾後的File[], */ Directory.func(path, "out"); } }
目錄的檢查及建立
File類不僅可以代表存在的檔案或目錄,也可以建立新的目錄或尚不存在的整個目錄路徑。還可以檢視檔案的特性(大小,最後的修改日期,讀寫許可權),以及檢查File物件是一個檔案還是一個目錄。並且可以刪除檔案。
public class MakeDirectories {
private static void fileData(File f) {
System.out.println(
"Absolute path: " + f.getAbsolutePath() +//檔案的絕對路徑
"\n Can read: " + f.canRead() + //是否可讀
"\n Can write: " + f.canWrite() +//是否可寫
"\n getName: " + f.getName() +//檔名
"\n getParent: " + f.getParent() +//上一級檔名
"\n getPath: " + f.getPath() +//相對路徑
"\n length: " + f.length() +//檔案大小
"\n lastModified: " + f.lastModified());//最後修改的時間
if(f.isFile())//判斷是否是檔案
System.out.println("It's a file");
else if(f.isDirectory())//判斷是否是目錄
System.out.println("It's a directory");
}
public static void main(String[] args) {
File file = new File("D:\\idea\\workspace\\project18");
File file1 = new File("D:\\idea\\workspace\\project18\\project18.iml");
MakeDirectories.fileData(file);
MakeDirectories.fileData(file1);
}
}
輸入和輸出
Java類庫中的I/O類分為兩部分輸入和輸出。
輸入:
InputStream 型別
InputStream 的作用是表示那些從不同資料來源產生輸入的類。這些資料來源包括:
(1) 位元組陣列
(2) String 物件
(3) 檔案
(4) “管道”,它的工作原理與現實生活中的管道類似:將一些東西置入一端,它們在另一端出來。
(5) 一個由其他種類的流組成的序列,以便我們可以將它們收集併合併到一個流內。
(6) 其他資料來源,如 Internet 連線等。
每一種資料來源都有相應的InputStream子類。除此以外, FilterInputStream 也屬於 InputStream 的一種型別,用它可為“裝飾器”類提供一個基礎類,以便將屬性或者有用的介面同輸入流連線到一起。
類 | 功能 | 構造器引數如何使用 |
---|---|---|
ByteArrayInputStream | 允許將記憶體中的緩衝區作為 InputStream 使用 | 緩衝區,位元組將從中取出。作為一個數據源使用。通過將其同一個 FilterInputStream 物件連線,可提供一個有用的介面 |
StringBufferInputStream | 將String 轉換成 InputStream | 字串,底層實現實際使用StringBuffer。作為一個數據源使用。通過將其同一個 FilterInputStream 物件連線,可提供一個有用的介面 |
FileInputStream | 用於從檔案讀取資訊 代表檔名的一個 String,或者一個 File 或 FileDescriptor 物件 | 作為一個數據源使用。通過將其同一個 FilterInputStream 物件連線,可提供一個有用的介面 |
PipedInputStream | 產生用於寫入相關 PipedOutputStream 的資料 實現"管道化"概念 | PipedOutputStream 作為多執行緒中的資料來源:將其與 FilterInputStream 物件連線,可提供一個有用的介面 |
SequenceInputStream | 將兩個或更多的 InputStream 物件轉換成單個 InputStream 使用 | 兩個InputStream 物件或者一個容納InputStream 物件的容器 Enumeratio。 作為一個數據源使用。通過將其同一個FilterInputStream 物件連線,可提供一個有用的介面 |
FilterInputStream | 抽象類,作為“裝飾器”的介面。其中“裝飾器”為其他的InputStream類提供有用功能 |
輸出:
OutputStream的型別
該類別的類決定了往何處輸出:
(1) 位元組陣列
(2) 檔案
(3) 管道
除此以外, FilterOutputStream 為“破壞器”類提供了一個基礎類,它將屬性或者有用的介面同輸出流連線起來。
類 | 功能 | 如何使用 |
---|---|---|
ByteArrayOutputStream | 在記憶體中建立緩衝區。所有送往“流”的資料都要放置在此緩衝區 | 用於指定資料的目的地:將其同FilterOutputStream 物件相連以提供有用介面 |
FileOutputStream | 將資訊寫入檔案 | 用於指定資料的目的地:將其同FilterOutputStream 物件相連以提供有用介面 |
PipedOutputStream | 任何寫入其中的資訊都會自動作為相關的PipedInputStream的輸出。實現“管道化”的概念 | 用於指定資料的目的地:將其同FilterOutputStream 物件相連以提供有用介面 |
FilterOutputStream | 抽象類,作為“裝飾器”的介面。其中“裝飾器”為其他的OutputStream類提供有用功能 |
新增屬性和有用的介面
通過FilterInputStream從InputStream讀取資料
類 | 功能 | 如何使用 |
---|---|---|
DataInputStream | 與DataOutputStream搭配使用,可以從流中讀取基本資料型別 | 包含用於讀取基本資料型別的全部介面 |
BufferedInputStream | 使用它可以防止每次讀取時都得進行實際寫操作。代表“使用緩衝區” | 本質上不提供介面,只不過是像程序中新增緩衝區所必需的。與介面物件搭配 |
FilterInputStream能夠完成兩件完全不同的事情。其中DataInputStream允許我們讀取基本資料型別。
其他FilterInputStream類則是在內部修改InputStream的行為方式:是否緩衝等等。
通過FilterOutputStream向OutputStream寫入
類 | 功能 | 如何使用 |
---|---|---|
DataOutputStream | 與DataOutputStream搭配使用,可以向流中寫入基本資料型別 | 包含用於讀取基本資料型別的全部介面 |
PrintStream | 用於產生格式化輸出,其中DataOutputStream處理資料的儲存,PrintStream處理顯示 | 可以用布林值指示是否在每次換行時清空緩衝區。 |
BufferedOutputStream | 使用它可以防止每次傳送資料時都得進行實際寫操作。代表“使用緩衝區”,可以用flush()清空緩衝區 | 本質上不提供介面,只不過是像程序中新增緩衝區所必需的。與介面物件搭配 |
Reader和Writer
InputStream和OutputStream是面向位元組的I/O功能,Reader和Writer面向的是字元的I/O功能。
需要注意的是:有時我們必須把來自於“位元組”層次結構中的類和“字元”層次結構中的類結合起來使用。為了實現這個目的。要用到“介面卡”類。
InputStreamReader可以把InputStream轉換成Reader。OutputStreamWriter可以把OutputStream轉換成Writer。
設計Reader和RWriter的繼承層次結構主要是為了國際化。
更改流的行為
對於InputStream和OutputStream來說,我們用FilterInputStream和FilterOutputStream的裝飾器子類來修改“流”以滿足特殊需求。Reader和Writer的類繼承層次繼續沿用相同的思想(但不完全相同)
過濾器:Java 1.0 類 | 相應的 Java 1.1 類 | |
---|---|---|
FilterInputStream | FilterReader | |
FilterOutputStream | FilterWriter | |
BufferedInputStream | BufferedReader | |
BufferedOutputStream | BufferedWriter | |
DataInputStream | 使用DataInputStream(除了當需要使用readLine()以外,這時應該使用BufferedReader ) | |
PrintStream | PrintWriter |
注意:當我們需要使用readLine(),應該使用BufferedReader。不應該使用DataInputStream,這會遭到編譯器的強烈反對。
自我獨立的類:RandomAccessFile
此類不是InputStream和OutputStream繼承層次結構中的一部分,它擁有和別的I/O型別本質不同的行為:我們可以在檔案內向前或向後移動。
只有RandomAccessFile支援搜尋方法,並且只適用於檔案。
I/O流的典型用法
緩衝輸入檔案
public class BufferedInputFile {
public static String read(String filename) throws IOException {
BufferedReader in = new BufferedReader(new FileReader(filename));
String s;
StringBuilder sb = new StringBuilder();
while((s = in.readLine())!=null){
sb.append(s);
}
in.close();
return sb.toString();
}
public static void main(String[] args) throws IOException {
System.out.println(BufferedInputFile.read("E:\\outlook\\2.txt"));
}
}
從記憶體輸入
public class MemoryInput {
public static void main(String[] args) throws IOException {
StringReader in = new StringReader(BufferedInputFile.read("E:\\outlook\\2.txt"));
int c;
while((c = in.read())!= -1){
System.out.print((char)c);
}
}
}
read()讀到檔案末尾返回-1,由此來判斷檔案是否讀完。
格式化的記憶體輸入
public class FormattedMemoryInput {
public static void main(String[] args) throws IOException {
DataInputStream in = new DataInputStream(new ByteArrayInputStream(read("E:\\outlook\\2.txt").getBytes()));
while(true){
System.out.println((char)in.readByte());
}
}
}
readByte():如果此輸入流已到達檔案末尾,會丟擲EOFException異常。
基本的檔案輸出
public class BasicFileOutput {
public static void main(String[] args) throws IOException {
BufferedReader in = new BufferedReader(new FileReader("E:\\outlook\\1.txt"));//建立輸入流連結原始檔
BufferedWriter out = new BufferedWriter(new FileWriter("E:\\outlook\\2.txt"));//建立輸出流連結目標檔案
String s;
while ((s = in.readLine())!=null)//將原始檔中的資料寫入記憶體並存入字串s
{
System.out.println(s);
out.write(s);//將s中的資料寫入目標檔案
}
out.close();
in.close();
}
儲存和恢復資料
public class StoringAndRecoveringData {
public static void main(String[] args) throws IOException {
DataOutputStream out = new DataOutputStream(
new BufferedOutputStream(new FileOutputStream("E:\\outlook\\1.txt")));//連結目標檔案
out.writeDouble(3.1415926);
out.writeUTF("xxgbl");
out.close();
DataInputStream in = new DataInputStream(
new BufferedInputStream(new FileInputStream("E:\\outlook\\1.txt")));
System.out.println(in.readDouble());
System.out.println(in.readUTF());
}
}
如果我們使用DataOutputStream 寫入資料,Java保證我們可以使用DataInputStream準確的讀取資料。
讀寫隨機訪問檔案
public class UsingRandomAccessFile {
static void display() throws IOException {
RandomAccessFile rf = new RandomAccessFile("E:\\outlook\\1.txt", "r");
for(int i=0; i<8; i++)
System.out.println("Value"+ i+": "+ rf.readDouble());
rf.close();
}
public static void main(String[] args) throws IOException {
RandomAccessFile out = new RandomAccessFile("E:\\outlook\\1.txt", "rw");
for(int i=0; i<8; i++)
out.writeDouble(i*1.414);
display();
out.close();
System.out.println();
out = new RandomAccessFile("E:\\outlook\\1.txt", "rw");
out.seek(5*8);
out.writeDouble(47.00001);
display();
out.close();
}
}
在使用RandomAccessFile時,必須知道檔案排版,此類具有讀取基本型別和UTF-8字串的各種具體方法。
管道流
PipedInputStream,PipedOutputStream,PipedReader,PipedWriter用於任務之間的通訊。它的價值在多執行緒中有所體現。
標準I/O
System.in:是一個InputStream物件
System.out:是一個PrintStream 物件
System.err:是一個PrintStream 物件
壓縮
壓縮類 | 功能 | |
---|---|---|
CheckedInputStream | 一種輸入流,它還維護正在讀取的資料的校驗和。 然後可以使用校驗和來驗證輸入資料的完整性。 | |
CheckedOutputStream | 輸出流,它還維護正在寫入的資料的校驗和。 然後可以使用校驗和來驗證輸出資料的完整性。 | |
DeflaterOutputStream | 壓縮類的基類 | |
ZIPOutputStream | DeflaterOutputStream的子類,用於將資料壓縮成Zip格式 | |
GZIPOutputStream | DeflaterOutputStream的子類,用於將資料壓縮成GZIP格式 | |
InflaterInputStream | 解壓縮的基類 | |
ZipInputStream | InflaterInputStream的子類,用於解壓Zip格式的資料 | |
GZIPInputStream | InflaterInputStream的子類,用於解壓GZIP格式的資料 |
使用GZIP進行簡單壓縮
public class GZIPcompress {
public static void main(String[] args) throws IOException {
BufferedReader in = new BufferedReader(
new InputStreamReader(new FileInputStream("E:\\outlook\\1.txt")));//連結原始檔
BufferedOutputStream out = new BufferedOutputStream(new GZIPOutputStream(
new FileOutputStream("E:\\outlook\\2.gz")));//連結目標檔案
int c;
while((c = in.read())!= -1)//讀取原始檔
out.write(c);//向目標檔案寫入資料。
out.close();
in.close();
}
}
執行上述程式碼,會生成一個2.gz的壓縮包。解壓同理:讀取壓縮檔案,寫入目標檔案中即可。
Zip進行多檔案儲存
public class ZipCompress {
public static void main(String[] args) throws IOException {
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream("E:\\outlook\\test.zip"));//建立壓縮檔案
BufferedOutputStream out = new BufferedOutputStream(zos);//輸出流連結壓縮檔案
BufferedReader in1 = new BufferedReader(new InputStreamReader(
new FileInputStream("E:\\outlook\\1.txt")));//輸入流連結原始檔
zos.putNextEntry(new ZipEntry("1.txt"));//Zip壓縮每新增一個壓縮檔案,都必須呼叫putNextEntry()
int i = 0;
while((i=in1.read())!=-1){
out.write(i);//讀取原始檔資料並寫入壓縮檔案
}
in1.close();
out.flush();//強制寫出快取中的資料。清空快取。
BufferedReader in2 = new BufferedReader(new InputStreamReader(
new FileInputStream("E:\\outlook\\2.txt")));
zos.putNextEntry(new ZipEntry("2.txt"));
int c = 0;
while((c=in2.read())!=-1){
out.write(c);
}
in2.close();
out.close();
}
}
上述程式碼將1.txt,2.txt壓縮成一個test.zip壓縮包。
zip解壓有兩種方式:
//解壓
FileInputStream f = new FileInputStream("E:\\outlook\\test.zip");
ZipInputStream zipInputStream = new ZipInputStream(f);
BufferedReader in = new BufferedReader(new InputStreamReader(zipInputStream));
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("E:\\outlook\\test1.txt")));
/*
方法一:
為了能夠解壓zip,ZipInputStream提供了getNextEntry(),返回一個ZipEntry物件,此物件儲存了壓縮檔案裡的所有檔案。
*/
/* int i =0;
while(zipInputStream.getNextEntry()!=null){
while((i=in.read())!=-1){
out.write(i);
}
}
out.flush();
out.close();
in.close();*/
/*
方法二:
利用ZipFile物件讀取檔案
*/
ZipFile zf = new ZipFile("E:\\outlook\\test.zip");
Enumeration entries =zf.entries();
int i = 0;
while(entries.hasMoreElements()){
ZipEntry zipEntry = (ZipEntry) entries.nextElement();
while(zipInputStream.getNextEntry()!=null){
while((i=in.read())!=-1){
out.write(i);
}
}
}
out.flush();
out.close();
in.close();
}
物件序列化
Java的物件序列化將那些實現Serializable介面的物件轉換成一個位元組序列,並能將位元組序列恢復成原來的物件。此介面沒有方法沒有欄位,只是一個標識功能。
使用物件序列化的原因:
1.支援Java的遠端方法呼叫(RMI),它使存活在其他計算機上的物件使用起來就像使存活在本機一樣。
2.使用JavaBean。使用一個Bean時,一般情況下是在設計階段對它的狀態資訊進行配置。這種狀態資訊必須儲存下來,並在程式啟動時進行後期恢復,這種具體工作是由物件序列化完成的。
要序列化一個物件很簡單,首先建立OutputStream物件,然後將其封裝在一個ObjectOutputStream物件內。這時呼叫writeObject()即可將物件例項化。
ObjectOutputStream out = new ObjectOutputStream(new OutputStream());
out.writeObject()
反序列化同理:
ObjectInputStream in= new ObjectInputStream(new InputStream());
in.readObject()
序列化的控制
預設的序列化機制並不難操作,然而,如果有特殊需求呢?例如:出於安全考慮,你不希望物件的某一部分被序列化;或者一個物件被還原後,子物件需要重建,從而不必將子物件序列化。
為此,你可以通過實現Externalizable介面來對序列化過程進行控制,此介面繼承了Serializable介面,同時新增了writeExternal()和readExternal()方法。這兩個方法會在序列化和反序列化過程中自動呼叫,以便執行一些特殊操作。
transient(瞬時)關鍵字
當一個類中的某個欄位是敏感資訊(例如:密碼),我們不希望將其序列化。因為一經序列化,人們可以通過其他方式來訪問到它。
解決這個問題:
方法一:
1.將類實現為Externalizable。
2.將不希望被自動序列化的欄位用transient修飾。例如 private transient String password;
方法二:
1.實現Serializable介面。
2.新增writeObject(),readObject()方法。這樣一旦序列化或反序列化,就會自動呼叫這兩個方法。
這兩個方法必須有準確的方法特徵簽名:
private void writeObject(java.io.ObjectOutputStream out)
throws IOException
private void readObject(java.io.ObjectInputStream in)
throws IOException, ClassNotFoundException;
XML
物件序列化的一個重要限制是:這只是Java的解決方案,只有Java程式才能反序列化這種物件。
一種更具互操作性的解決方案是將資料轉換成XML格式。這使得資料能被跨平臺和語言使用。