一: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》第四版還是沒有第二版好懂啊。