Java程式設計思想 第十八章:Java I/O
- I/O源端與之通訊的接收端:檔案、控制檯、網路連結等。
- 通訊方式:順序、隨機存取、緩衝、二進位制、按字元、按行、按字等。
1. File類
File(檔案)既能代表一個特定檔名稱,又能代表一個目錄下的一組檔案的名稱。如果是檔案集,可以對此集合呼叫list()方法,返回一個字元陣列。
1.1 目錄列表器
public class DirList {
public static void main(String[] args) {
File path = new File("./src/io");
String[] list;
if (args.length == 0) {
list = path.list();
} else {
list = path.list(new DirFilter(args[0]));
}
Arrays.sort(list, String.CASE_INSENSITIVE_ORDER);
for (String dirItem : list) {
System.out.println(dirItem);
}
}
}
class DirFilter implements FilenameFilter {
private Pattern pattern;
public DirFilter(String regex) {
pattern = Pattern.compile(regex);
}
@Override
public boolean accept(File dir, String name) {
return pattern.matcher(name).matches();
}
} /*
DirectoryDemo.java
DirList.java
DirList2.java
DirList3.java
*/
DirFilter這個類存在的唯一原因就是accept()方法。建立這個類的目的在於把accept()方法提供給list()使用,使list()可以回撥accept(),進而以決定哪些檔案包含在列表中。因此,這種結構也常常成為回撥。
accept()會使用一個正則表示式的matcher物件,來檢視此正則表示式regex是否匹配這個檔案的名字。通過accept,list()方法會返回一個數組。
1.2 目錄實用工具
public final class Directory {
public Directory() {
}
public static File[] local(File dir, final String regex) {
return dir.listFiles(new FilenameFilter() {
private Pattern pattern = Pattern.compile(regex);
public boolean accept(File dir, String name) {
return this.pattern.matcher((new File(name)).getName()).matches();
}
});
}
public static File[] local(String path, String regex) {
return local(new File(path), regex);
}
public static Directory.TreeInfo walk(String start, String regex) {
return recurseDirs(new File(start), regex);
}
public static Directory.TreeInfo walk(File start, String regex) {
return recurseDirs(start, regex);
}
public static Directory.TreeInfo walk(File start) {
return recurseDirs(start, ".*");
}
public static Directory.TreeInfo walk(String start) {
return recurseDirs(new File(start), ".*");
}
static Directory.TreeInfo recurseDirs(File startDir, String regex) {
Directory.TreeInfo result = new Directory.TreeInfo();
File[] var6;
int var5 = (var6 = startDir.listFiles()).length;
for(int var4 = 0; var4 < var5; ++var4) {
File item = var6[var4];
if(item.isDirectory()) {
result.dirs.add(item);
result.addAll(recurseDirs(item, regex));
} else if(item.getName().matches(regex)) {
result.files.add(item);
}
}
return result;
}
public static void main(String[] args) {
if(args.length == 0) {
System.out.println(walk("."));
} else {
String[] var4 = args;
int var3 = args.length;
for(int var2 = 0; var2 < var3; ++var2) {
String arg = var4[var2];
System.out.println(walk(arg));
}
}
}
public static class TreeInfo implements Iterable<File> {
public List<File> files = new ArrayList();
public List<File> dirs = new ArrayList();
public TreeInfo() {
}
public Iterator<File> iterator() {
return this.files.iterator();
}
void addAll(Directory.TreeInfo other) {
this.files.addAll(other.files);
this.dirs.addAll(other.dirs);
}
public String toString() {
return "dirs: " + PPrint.pformat(this.dirs) + "\n\nfiles: " + PPrint.pformat(this.files);
}
}
}
- local()方法使用被稱為listFile()的File.list()的變體來產生File陣列。
- walk()方法將開始目錄的名字轉換為File物件,然後呼叫recurseDirs(),該方法將遞迴地遍歷目錄,並在每次遞迴中都手機更多的資訊。為了區分普通檔案和目錄,返回值實際上是一個物件元組——一個List持有所有普通檔案,另一個持有目錄。
- TreeInfo.toString()方法使用了一個靈巧印表機類,一個可以新增新行並縮排所有元素的工具:
1.3 目錄的檢查以及建立
目錄的檢查:
- f.getAbsolutePath() 返回File的絕對路徑
- f.canRead() 判斷File是否可讀
- f.canWrite() 判斷File是否可寫
- f.getName() 獲取檔案的名字
- f.getParent() 獲取父目錄檔案路徑
- f.getPath() 獲取File路徑
- f.length() 獲取File長度
- f.lastModified() 獲取檔案上次被修改時間
- f.exists 判斷檔案是否存在
檔案的建立
- File file=new File() 建立檔案物件
- file.createNewFile() 建立檔案
- file.mkdir() 建立目錄
- file.delete() 刪除目錄
2. 輸入和輸出
程式語言的I/O庫經常使用流,它代表任何有能力產生資料的資料來源物件或者有能力接受資料的接收端物件。
Java類庫中的I/O類分成輸入和輸出兩部分。通過繼承,任何自Inputstream或Reader派生而來的類都含有名為read的基本方法,用於讀取單個位元組或者位元組陣列。同樣的OutputStream或者Writer派生出來的類都含有名為write的基本方法,用於寫單個位元組或者位元組陣列。但是不會用到,它們之所以存在是因為別的類可以使用它們以便提供更有用的介面。因此,很少使用單一的類來建立流物件,而是通過疊合多個物件來提供所期望的功能。
2.1 InputStream型別
InputStream的作用是用來表示從不同資料來源產生輸出的類,這些資料來源包括:
- 位元組陣列
- String物件
- 檔案
- 管道,工作方式與實際管道相似,即,從一段輸入,從另一端輸出
- 一個由其他種類的流組成的序列,以便可以將它們收集合併到一個流內
- 其他資料來源,如Internet連結等
每一種資料來源都有相應的InputStream子類。另外,FilterInputStream也屬於一種InputStream,為裝飾器類提供的基類,其中,裝飾器類可以把屬性或有用的介面與輸入流連線在一起。
2.2 OutputStream型別
該類別的類決定了輸出所要去往的目標:位元組陣列、檔案或管道
另外,FilterOutputStream為裝飾器類提供了一個基類,裝飾器類把屬性或者有用的介面與輸出流連線了起來:
3. 新增屬性和有用的介面
- Java I/O類庫裡存在filter(過濾器)類的原因所在抽象類filter是所有裝飾器類的基類。
- FilterInputStream和FilterOutputStream是用來提供裝飾類器介面以及控制特定輸入流和輸出流的兩個類。FilterInputStream和FilterOutputStream分別自I/O類庫中的基類InputStream和OutputStream派生而來,這兩個類是裝飾器的必要條件。
裝飾者設計模式:動態的將功能附加到物件上,在物件擴充套件方面,它比繼承更加有彈性。
3.1 通過FilterInputStream從InputStream讀取資料
FilterInputStream類能夠完成完全不同的事情,其中,DateInputStream允許讀取不同的基本型別資料以及String物件。
其他FilterInputStream類則在內部修改InputStream的行為方式:是否緩衝,是否保留它所讀過的行(允許查詢行數或設定行數),以及是否把單一字元推回輸入流。
3.2 通過FilterOutputStream向OutputStream匯入
4. Reader和Writer
InputStream和OutputStream在以面向位元組形式的IO中可以提供極有價值的功能,Reader和Writer(Java 1.1對基礎IO流類庫進行了重大修改,可能會以為是用來替換InputStream和OutputStream的)則提供相容Unicode和麵向字元的IO功能。
- Java 1.1向InputStream和OutputStream繼承層次中添加了一些新類,所以這兩個類不會被取代
- 有時必須把來自於位元組層次結構中的類和字元層次中的類結合起來。為了實現這個目的,要用到介面卡類:InputStreamReader可以吧InputStream轉換為Reader,而OutputStreamWriter可以吧OutputStream轉換為Writer。
介面卡設計模式:將一個類的介面轉換成客戶端希望的另一個介面。
設計Reader和Writer繼承層次結構只要是為了國際化。老的IO流繼承層次結構僅支援8位位元組流,並且不能很好地處理16位的Unicode字元。所以Reader和Writer繼承層次結構就是為了在所有IO操作中都支援Unicode。
4.1 資料的來源和去處
##4.2 更改流的行為
對於InputStream和OutputStream來說,有裝飾器子類來修改流以滿足需要。Reader和Writer的類繼承層次結構繼續沿用相同的思想——但不完全相同。
無論何時使用readLine(),都不應該使用DataInputStream,而應該使用BufferedReader。
為了更容易地過渡到使用PrintWriter,它提供了一個既接受Writer物件又能接受任何OutputStream物件的構造器。PrintWriter的格式化介面實際上與PrintStream相同。
5. 自我獨立的類:RandomAccessFile
RandomAccessFile適用於由大小已知的記錄組成的檔案,所以可以使用seek()將記錄從一處轉移到另一處,然後讀取或者修改記錄。檔案中記錄的大小不一定都相同,只要能夠確定那些記錄有多大以及它們在檔案中的位置即可。
RandomAccessFile實現了DataInput和DataOutput介面,它是一個完全獨立的類,從頭開始編寫其所有的方法(大多數都是本地的)。這麼做是因為RandomccessFile擁有和別的I/O型別本質不同的行為,因為可以在一個檔案內向前和向後移動。在任何情況下,它都是自我獨立的,直接從Object派生而來。
方法getFilePointer()用於查詢當前所處的檔案位置,seek()用於在檔案內移至新的位置,length()用於判斷檔案的最大尺寸。另外,其構造器還需要第二個引數(和C中的fopen()相同)用來指示我們只是“隨機讀”®還是“既讀又寫”(rw)。
6. I/O流的典型使用方式
6.1 緩衝輸入檔案
使用以String或File物件作為檔名的FileInputReader。為了提高速度,對檔案進行緩衝,將所產生的引用傳給一個BufferedReader構造器。
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 + "\n");
}
// 呼叫close()關閉檔案
in.close();
return sb.toString();
}
public static void main(String[] args) throws IOException {
System.out.print(read("BufferedInputFile.java"));
}
}
BufferedReader in = new BufferedReader(new FileReader(filename));
6.2 從記憶體輸入
從BufferedInputFile.read()讀入的String結果被用來建立一個StringReader。然後呼叫read()每次讀取一個字元,併發送到控制檯。
public class MemoryInput {
public static void main(String[] args) throws IOException {
StringReader in = new StringReader(BufferedInputFile.read("MemoryInput.java"));
int c;
while ((c = in.read()) != -1) {
System.out.print((char) c);
}
}
}
6.3 格式化的記憶體輸入
要讀取格式化資料,可以使用DataInputStream,它是面向位元組的IO類,因此必須用InputStream而不是Reader。
ublic class FormattedMemoryInput {
public static void main(String[] args) throws IOException {
try {
DataInputStream in = new DataInputStream(new ByteArrayInputStream(BufferedInputFile.read("E:\\JAVA\\IdeaProjects\\untitled\\src\\io\\FormattedMemoryInput.java").getBytes()));
while (true) {
System.out.print((char) in.readByte());
}
} catch (EOFException e) {
System.err.println("End of stream");
}
}
}
DataInputStream in = new DataInputStream(new ByteArrayInputStream);
DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(“TestEOF.java”)));
6.4 基本檔案輸出
FileWriter物件可以向檔案寫入資料。通常會用BufferedWriter將其包裝起來用以緩衝輸出。
ublic class BasicFileOutput {
static String file = "BasicFileOutput.out";
public static void main(String[] args) throws IOException {
BufferedReader in = new BufferedReader(new StringReader(
BufferedInputFile.read("BasicFileOutput.java")));
PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(file)));
int lineCount = 1;
String s;
while ((s = in.readLine()) != null) {
out.println(lineCount++ + ": " + s);
}
out.close();
System.out.println(BufferedInputFile.read(file));
}
PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(file)));
6.5 儲存和恢復資料
PrintWriter可以對資料進行格式化,以便閱讀。但是為了輸出可供另一個流恢復的資料,需要用DataOutputStream寫入資料,並用DataInputStream恢復資料。當然,這些流可以使用任何形式,下面示例使用的是一個檔案,並且對於讀和寫進行了緩衝處理。注意DataOutputStream和DataInputStream是面向位元組的,因此要使用InputStream和OutputStream。
public class StoringAndRecoveringData {
public static void main(String[] args) throws IOException {
DataOutputStream out = new DataOutputStream(
new BufferedOutputStream(
new FileOutputStream("Data.txt")));
out.writeDouble(3.14159);
out.writeUTF("That was pi");
out.writeDouble(1.41413);
out.writeUTF("Square root of 2");
out.close();
DataInputStream in = new DataInputStream(
new BufferedInputStream(
new FileInputStream("Data.txt")));
System.out.println(in.readDouble());
System.out.println(in.readUTF());
System.out.println(in.readDouble());
System.out.println(in.readUTF());
}
}
DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(“Data.txt”)));
DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(“Data.txt”)));
6.6 讀寫隨機訪問檔案
public class UsingRandomAccessFile {
static String file = "rtest.dat";
static void display() throws IOException {
RandomAccessFile rf = new RandomAccessFile(file, "r");
for (int i = 0; i < 7; i++) {
System.out.println("Value " + i + ": " + rf.readDouble());
}
System.out.println(rf.readUTF());
rf.close();
}
public static void main(String[] args) throws IOException {
RandomAccessFile rf = new RandomAccessFile(file, "rw");
for (int i = 0; i < 7; i++) {
rf.writeDouble(i * 1.414);
}
rf.writeUTF("The end of the file");
rf.close();
display();
rf = new RandomAccessFile(file, "rw");
rf.seek(5 * 8);
rf.writeDouble(47.0001);
rf.close();
display();
}
}
RandomAccessFile rf = new RandomAccessFile(file, “rw”);
7. 檔案讀寫的使用工具
讀取檔案:
BufferedReader in = new BufferedReader(new FileReader((new File(fileName)).getAbsoluteFile()));
寫入檔案:
PrintWriter out = new PrintWriter((new File(fileName)).getAbsoluteFile());
8. 標準IO
標準IO源自於Unix的“程式所使用的單一資訊流”這一概念。程式的所有輸入都可以來自於標準輸入,所有輸出都可以傳送到標準輸出。
8.1 從標準輸入中讀取
- 標準輸入:System.in未加工的InputStream
- 標準輸出:System.out PrintStream物件
- 標準錯誤:System.err PrintStream物件
通常會用readLine()一次一行讀取輸入,將System.in包裝城BufferedReader來使用,這要求必須用InputStreamReader把Sytem.in轉換成Reader。System,in通常應該對它進行緩衝。
8.2 將System.out轉換成PrintWriter
public class ChangeSystemOut {
public static void main(String[] args) {
PrintWriter out = new PrintWriter(System.out, true);
out.println("Hello, world");
}
} /*
Hello, world
*/
8.3 標準IO重定向
Java的System類提供了靜態方法嗲用,以允許對標準輸入輸出和錯誤IO流進行重定向:
- setIn(InputStream)
- setOut(PrintStream)
- setErr(PrintStream)
9.程序控制
Java內部執行其他作業系統的程式,並且控制這些程式輸入輸出,Java類庫提供了執行這些操作的類。
想執行一個程式,向OSException.command()傳遞一個command字串,