Thinking in java讀書筆記-I/O部分(一):File類的用法

一:File類的常規用法
File(文件)類,這個名字具有一定的誤導性,我們可能認爲它是一個文件,其實它並非如此。其實它既可以代表一個文件的名稱,又可以代表一個目錄下一組文件的名稱。如果它是一個文件集,那麼可以對其採用list()方法,從而返回一個字符數組。

package test;
import java.io.File;

public class FileTest {
    public static void main(String args[]){
        File f_1 = new File(".\\src\\test\\CategoryDao.java");
        File f_2 = new File(".\\src\\test");
        System.out.println(f_1.list() == null);
        System.out.println(f_2.list() == null);
        System.out.println("..............");
        for(String name : f_2.list()){
            System.out.println(name);
        }
        System.out.println("..............");
        System.out.println(f_1.getName());
        System.out.println("..............");
        System.out.println(f_2.getName());
        System.out.println(f_2.getParent());
    }
}

此時實際的目錄結構爲:
這裏寫圖片描述
輸出結果爲:

.
true
false
…………..
CategoryDao.java
DirList.java
FileTest.java
…………..
CategoryDao.java
…………..
test
.\src

由此可以得到幾點信息:

  • File file = new File(“.”);,這裏指代的是當前項目的目錄(System.getProperty(“user.dir”) 返回的目錄地址)。在本示例中是指E:\workspace\test目錄。

  • 對於一個指代目錄的的File對象,對其調用list()方法返回不爲null;對於一個指代特定文件(如CategoryDao.java) ,其調用list()方法返回null。

我們可以依據上面的測試得到以下結論:這個File類其實更應該理解爲文件路徑。

接下來我們從此章中第一個示例出發進行分析(依據我的文件目錄結作了一丟丟的改變):

package test;
import java.io.*;
import java.util.Arrays;
import java.util.regex.Pattern;

public class DirList {
    public static void main(String args[]){
        File path = new File(".\\src\\test");
        String[] list;
        if(args.length == 0){
            list = path.list();
        }else{
            list = path.list(new DirList().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);
        }

        public boolean accept(File dir, String name){
            return pattern.matcher(name).matches();
        }
    }
}

經過上面的分析,這個程序其實已經有一部分很好理解了。最不好理解的應該是DirFilter的部分了。

先從java環境下的正則表達式入手:

概念

正則表達式是對字符串操作的一種邏輯公式,就是用事先定義好的一些特定字符、及這些特定字符的組合,組成一個“規則字符串”,這個“規則字符串”用來表達對字符串的一種過濾邏輯。

java中的正則表達式及用法:

正則表達式java api主要封裝在java.util.regex的包中, 其主要包括以下三個類:
Pattern 類:
pattern 對象是一個正則表達式的編譯表示。Pattern 類沒有公共構造方法。要創建一個 Pattern 對象,你必須首先調用其公共靜態編譯方法,它返回一個 Pattern 對象。該方法接受一個正則表達式作爲它的第一個參數。
Matcher 類:
Matcher 對象是對輸入字符串進行解釋和匹配操作的引擎。與Pattern 類一樣,Matcher 也沒有公共構造方法。你需要調用 Pattern 對象的 matcher 方法來獲得一個 Matcher 對象。
PatternSyntaxException:
PatternSyntaxException 是一個非強制異常類,它表示一個正則表達式模式中的語法錯誤。

正則表達式主要是用來匹配的。現列出幾種常見的匹配格式:

\
將下一字符標記爲特殊字符、文本、反向引用或八進制轉義符。例如,”n”匹配字符”n”。”\n”匹配換行符。序列”\\”匹配”\”,”\(“匹配”(“。

^
匹配輸入字符串開始的位置。如果設置了 RegExp 對象的 Multiline 屬性,^ 還會與”\n”或”\r”之後的位置匹配。

*
零次或多次匹配前面的字符或子表達式。例如,zo* 匹配”z”和”zoo”。* 等效於 {0,}。

+
一次或多次匹配前面的字符或子表達式。例如,”zo+”與”zo”和”zoo”匹配,但與”z”不匹配。+ 等效於 {1,}。

?
零次或一次匹配前面的字符或子表達式。例如,”do(es)?”匹配”do”或”does”中的”do”。? 等效於 {0,1}。

{n}
n 是非負整數。正好匹配 n 次。例如,”o{2}”與”Bob”中的”o”不匹配,但與”food”中的兩個”o”匹配。

[a-z]
字符範圍。匹配指定範圍內的任何字符。例如,”[a-z]”匹配”a”到”z”範圍內的任何小寫字母。

\s
匹配任何空白字符,包括空格、製表符、換頁符等。與 [ \f\n\r\t\v] 等效。

更多的就不一一列舉了。需要用的時候再去查就ok了。

class DirFilter implements FilenameFilter{
        private Pattern pattern;
        public DirFilter(String regex){
            pattern = Pattern.compile(regex);
        }

        public boolean accept(File dir, String name){
            return pattern.matcher(name).matches();
        }
    }

這裏的類DirFilter對象 在初始化的時候就生成了一個Pattern對象。

pattern = Pattern.compile(regex);

爲了更好的理解這一段流程,可以看一下File.list(FilenameFilter filter)函數的源碼:

    public String[] list(FilenameFilter filter) {
        String names[] = list();
        if ((names == null) || (filter == null)) {
            return names;
        }
        List<String> v = new ArrayList<>();
        for (int i = 0 ; i < names.length ; i++) {
            if (filter.accept(this, names[i])) {
                v.add(names[i]);
            }
        }
        return v.toArray(new String[v.size()]);
    }

由此可知,這個DirFilter將File.list()返回的字符串列中的項與正則表達式進行匹配,如果匹配成功,則返回。
因此可以這樣進行測試:
(1)匹配以java結尾的文件
這裏寫圖片描述
執行結果爲:

CategoryDao.java
DirList.java
FileTest.java

(2)匹配以t.java結尾的文件
這裏寫圖片描述

執行結果爲:

DirList.java
FileTest.java

因此達到了所需要的效果。

由File.list(FilenameFilter filter)方法源碼可知,File類爲此方法註冊了一個FilenameFilter接口,並在內部調用了FilenameFilter的accept()方法。所以DirFilter存在的目的就是爲了創建表示正則匹配的accept()方法並提供給list()使用。

這種先註冊一個接口,再實例化調用的結構可以稱之爲回調。或者更具體的說,這是一個策略模式的例子。

爲了保證本博客的結構性。這一設計模式就不在此處細講了。以後會專門開一篇博客講解這部分的內容。

爲了得到結構性更好的代碼(不定義新的類,以防擾亂視線),通常我們會使用匿名內部類來完成一樣的功能,代碼如下:

package test;
import java.io.*;
import java.util.Arrays;
import java.util.regex.Pattern;

public class DirList2 {
    public static void main(String args[]){
        File path = new File(".\\src\\test");
        String[] list;
        if(args.length == 0){
            list = path.list();
        }else{
            list = path.list(new FilenameFilter(){
                private Pattern pattern = Pattern.compile(args[0]);
                public boolean accept(File dir, String name){
                    return pattern.matcher(name).matches();
                }
            });
        }
        Arrays.sort(list, String.CASE_INSENSITIVE_ORDER);
        for(String dirItem:list){
            System.out.println(dirItem);
        }

    }

}

二:File類作爲工具類生成一個目錄下的結構:

package test;

import java.util.regex.*;
import java.io.*;
import java.util.*;

public final class 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 die, String name){
                return pattern.matcher(new File(name).getName()).matches();
            }
        });
    }

    public static File[] local(String path, final String regex){
            return local(new File(path), regex);
    }

    public static class TreeInfo implements Iterable<File>{
        public List<File> files = new ArrayList<File>();
        public List<File> dirs = new ArrayList<File>();

        public Iterator<File> iterator(){
            return files.iterator();
        }

        void addAll(TreeInfo other){
            files.addAll(other.files);
            dirs.addAll(other.dirs);
        }

        public String toString(){
            return "dirs: " + PPrint.pformat(dirs) + "\n\nfiles: " + PPrint.pformat(files);
        }
    }

    public static TreeInfo walk(String start, String regex){//Begin recursion
        return recurseDirs(new File(start), regex);
    }

    public static TreeInfo walk(File start, String regex){
        return recurseDirs(start, regex);
    }

    public static TreeInfo walk(File start){
        return recurseDirs(start, ".*");
    }

    public static TreeInfo walk(String start){
        return recurseDirs(new File(start), ".*");
    }

    static TreeInfo recurseDirs(File startDir, String regex){
        TreeInfo result = new TreeInfo();
        for(File item: startDir.listFiles()){
            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{
            for(String arg:args){
                System.out.println(walk(arg));
            }
        }
    }

}

其中PPrint是自定義的用以格式化打印的工具類:

package test;
import java.util.*;
public class PPrint {
    public static String pformat(Collection<?> c){
        if(c.size() == 0){
            return "[]";
        }
        StringBuilder result = new StringBuilder("[");
        for(Object elem : c){
            if(c.size() != 1){
                result.append("\n ");
            }
                result.append(elem);
        }
        if(c.size() != 1){
            result.append("\n");
        }
        result.append("]");
        return result.toString();
    }

    public static void pprint(Collection<?> c){
        System.out.println(pformat(c));
    }

    public static void pprint(Object[] c){
        System.out.println(Arrays.asList(c));
    }
}

在本目錄下運行的結果爲:

dirs: [
 .\.settings
 .\bin
 .\bin\template
 .\bin\test
 .\src
 .\src\template
 .\src\test
]

files: [
 .\.classpath
 .\.project
 .\.settings\org.eclipse.jdt.core.prefs
 .\bin\test\CategoryDao.class
 .\bin\test\Directory$1.class
 .\bin\test\Directory$TreeInfo.class
 .\bin\test\Directory.class
 .\bin\test\DirList$DirFilter.class
 .\bin\test\DirList.class
 .\bin\test\DirList2$1.class
 .\bin\test\DirList2.class
 .\bin\test\DirList3$1.class
 .\bin\test\DirList3.class
 .\bin\test\FileTest.class
 .\bin\test\PPrint.class
 .\src\test\CategoryDao.java
 .\src\test\Directory.java
 .\src\test\DirList.java
 .\src\test\DirList2.java
 .\src\test\DirList3.java
 .\src\test\FileTest.java
 .\src\test\PPrint.java
]

因此可以看出,此工具類Directory成功實現了打印本工作目錄下所有目錄和文件的功能。

上述工具類重要的部分爲:

public static class TreeInfo implements Iterable<File>{
        public List<File> files = new ArrayList<File>();
        public List<File> dirs = new ArrayList<File>();

        public Iterator<File> iterator(){
            return files.iterator();
        }

        void addAll(TreeInfo other){
            files.addAll(other.files);
            dirs.addAll(other.dirs);
        }

        public String toString(){
            return "dirs: " + PPrint.pformat(dirs) + "\n\nfiles: " + PPrint.pformat(files);
        }
    }

    static TreeInfo recurseDirs(File startDir, String regex){
        TreeInfo result = new TreeInfo();
        for(File item: startDir.listFiles()){
            if(item.isDirectory()){
                result.dirs.add(item);
                result.addAll(recurseDirs(item, regex));//如果File是目錄,則遞歸調用
            }else{
                if(item.getName().matches(regex)){
                    result.files.add(item);
                }
            }
        }
        return result;
    }

因此此工具類可以將目錄下的目錄和文件全部打印出來。

三:File對象創建新的文件

package test;

import java.io.File;
import java.io.IOException;

public class NewFileTest {
    public static void main(String args[]) throws IOException{
        File f = new File(".\\src\\test\\Director.java");

        System.out.println(f.createNewFile());


        System.out.println("f is a directory" + f.isFile());
    }
}

經過檢查可以發現在.\src\test目錄下生成了Director.java文件。

注意,如果.\src\test\目錄不存在,會報錯IOException。這時候需要調用File.mkdirs()來創建父目錄。這裏就不贅述了。

本來想花一天看完I/O部分的全部內容的,結果發現才大致弄清楚了File類的內容。感覺《Thinking in java》第四版還是沒有第二版好懂啊。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章