1. 命令行程序
利用main函数可以接受参数 String[] args 这一特点。
public static void main(String[] args)
2. 任务描述
写一个命令行程序,能够
1. 读取一个目录下的所有文件和子目录
2. 在每个文件中寻找一段文本,支持正则查找
3. 把所有符合条件的文件添加到一个ZIP压缩包。
3. 逐步分析
3.1
首先,我们建立一个FileSearchApp 的java class, 根据任务描述,直觉上应该有储存文件地址的String path, 储存正则表达式的String regex,储存压缩文件名的String zipFileName, 储存符合条件的文件的路径列表 List<File> zippFiles. 注【1】
public class FileSearchApp {
private String path;
private String regex;
private String zipFileName;
private List<File> zipFiles = new ArrayList<>();
public static void main(String[] args) {
}
然后说明用法 "USAGE: FileSearch App path [regex] [zipfile]",接受三个参数,分别为path, 正则表达式,和压缩文件名
public static void main(String[] args) {
FileSearchApp app = new FileSearchApp();
switch(Math.min(args.length,3)){
case 0:
System.out.println("USAGE: FileSearch App path [regex] [zipfile]");
return;
case 3:
app.setZipFileName(args[2]);
case 2:
app.setRegex(args[1]);
case 1:
app.setPath(args[0]);
}
try {
app.walkDirectory(app.getPath());
} catch (Exception e) {
e.printStackTrace();
}
}
这里walkDirectory的定义如下:注【2】
public void walkDirectory(String path) throws IOException {
Files.walk(Paths.get(path)).forEach(f-> processFile(f.toFile()));
}
对一个String path, 使用Paths.get() 转化为Path 对象,然后通过Files.walk() 以Path为文件树的根节点,产生一个Stream,注【3】 对每一个Path f转化为File后, 进行processFile 的操作。
注释
注【1】
Java文件类以抽象的方式代表文件名和目录路径名。该类主要用于文件和目录的创建、文件的查找和文件的删除等。
File对象代表磁盘中实际存在的文件和目录。通过以下构造方法创建一个File对象。
通过给定的父抽象路径名和子路径名字符串创建一个新的File实例。
File(File parent, String child);
通过将给定路径名字符串转换成抽象路径名来创建一个新 File 实例。
File(String pathname)
根据 parent 路径名字符串和 child 路径名字符串创建一个新 File 实例。
File(String parent, String child)
通过将给定的 file: URI 转换成一个抽象路径名来创建一个新的 File 实例。
File(URI uri)
本次用到的方法:
1 | public String getName() 返回由此抽象路径名表示的文件或目录的名称。 |
4 | public String getPath() 将此抽象路径名转换为一个路径名字符串。 |
6 | public String getAbsolutePath() 返回抽象路径名的绝对路径名字符串。 |
10 | public boolean isDirectory() 测试此抽象路径名表示的文件是否是一个目录。 |
11 | public boolean isFile() 测试此抽象路径名表示的文件是否是一个标准文件。 |
注【2】
Java Paths
This class consists exclusively of static methods that return a Path
by converting a path string or URI
.
static Path | get(String first, String... more) Converts a path string, or a sequence of strings that when joined form a path string, to a |
File.walk() 定义如下
static Stream<Path> | walk(Path start, FileVisitOption... options) Return a |
注【3】
Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。
Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。
Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。
元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。
3.2
使用Intellij Idea, [Alt]+[Insert], 自动生成private变量的getter和setter method.
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public String getRegex() {
return regex;
}
public void setRegex(String regex) {
this.regex = regex;
this.pattern = Pattern.compile(regex);
}
public String getZipFileName() {
return zipFileName;
}
public void setZipFileName(String zipFileName) {
this.zipFileName = zipFileName;
}
3.3
ProcessFile 定义
如果在文件中找到了包含目标文本,searchFile()返回true,调用addFileToZip(),将file添加到zipFiles.
Pattern, 使用Pattern可以避免重复编译正则表达式。
private Pattern pattern;
Java 正则表达式和 Perl 的是最为相似的。
java.util.regex 包主要包括以下三个类:
- Pattern 类:
pattern 对象是一个正则表达式的编译表示。Pattern 类没有公共构造方法。要创建一个 Pattern 对象,你必须首先调用其公共静态编译方法,它返回一个 Pattern 对象。该方法接受一个正则表达式作为它的第一个参数。
- Matcher 类:
Matcher 对象是对输入字符串进行解释和匹配操作的引擎。与Pattern 类一样,Matcher 也没有公共构造方法。你需要调用 Pattern 对象的 matcher 方法来获得一个 Matcher 对象。
public void processFile(File file){
try{
if(searchFile(file)){
addFileToZip(file);
}
} catch (IOException|UncheckedIOException e) {
System.out.println("Error processing file: "+ file + " "+ e);
}
}
public boolean searchFile(File file) throws IOException{
// Files.lines() actually use UTF-8 by defalut
return Files.lines(file.toPath(), StandardCharsets.UTF_8)
.anyMatch( t-> searchText(t));
}
public void addFileToZip(File file){
if(getZipFileName()!=null){
zipFiles.add(file);
}
}
public boolean searchText(String text){
return (this.getRegex()==null) ? true :
this.pattern.matcher(text).find();
//use this.pattern.matcher(text).matches() to full match
}
public void zipFiles() throws IOException{
try(ZipOutputStream out =
new ZipOutputStream(new FileOutputStream(getZipFileName()))){
File baseDir = new File(getPath());
for(File file: zipFiles){
//filename must be a relative path, not an absolute one
String fileName = getRelativeFilename(file, baseDir);
// 1. Create a ZipOutputStream
ZipEntry zipEntry = new ZipEntry(fileName);
zipEntry.setTime(file.lastModified());
out.putNextEntry(zipEntry);
// 2. Add files to the ZipOutputStream
Files.copy(file.toPath(), out);
// 3. Close the ZipOutputStream
out.closeEntry();
}
}
}
public String getRelativeFilename(File file, File baseDir){
String fileName = file.getAbsolutePath().substring(
baseDir.getAbsolutePath().length());
// the ZipEntry file name must use "/" ,not"\"
fileName = fileName.replace("\\","/");
while(fileName.startsWith("/")){
fileName = fileName.substring(1);
}
return fileName;
}
4. 完整代码
package com.company;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class FileSearchApp {
private String path;
private String regex;
private String zipFileName;
private Pattern pattern;
private List<File> zipFiles = new ArrayList<>();
public static void main(String[] args) {
FileSearchApp app = new FileSearchApp();
switch(Math.min(args.length,3)){
case 0:
System.out.println("USAGE: FileSearch App path [regex] [zipfile]");
return;
case 3:
app.setZipFileName(args[2]);
case 2:
app.setRegex(args[1]);
case 1:
app.setPath(args[0]);
}
try {
app.walkDirectory(app.getPath());
} catch (Exception e) {
e.printStackTrace();
}
}
public void walkDirectory(String path) throws IOException {
Files.walk(Paths.get(path)).forEach(f-> processFile(f.toFile()));
}
public boolean searchFile(File file) throws IOException{
// Files.lines() actually use UTF-8 by defalut
return Files.lines(file.toPath(), StandardCharsets.UTF_8)
.anyMatch( t-> searchText(t));
}
public boolean searchText(String text){
return (this.getRegex()==null) ? true :
this.pattern.matcher(text).find();
//use this.pattern.matcher(text).matches() to full match
}
public void addFileToZip(File file){
if(getZipFileName()!=null){
zipFiles.add(file);
}
}
public void zipFiles() throws IOException{
try(ZipOutputStream out =
new ZipOutputStream(new FileOutputStream(getZipFileName()))){
File baseDir = new File(getPath());
for(File file: zipFiles){
//filename must be a relative path, not an absolute one
String fileName = getRelativeFilename(file, baseDir);
// 1. Create a ZipOutputStream
ZipEntry zipEntry = new ZipEntry(fileName);
zipEntry.setTime(file.lastModified());
out.putNextEntry(zipEntry);
// 2. Add files to the ZipOutputStream
Files.copy(file.toPath(), out);
// 3. Close the ZipOutputStream
out.closeEntry();
}
}
}
public String getRelativeFilename(File file, File baseDir){
String fileName = file.getAbsolutePath().substring(
baseDir.getAbsolutePath().length());
// the ZipEntry file name must use "/" ,not"\"
fileName = fileName.replace("\\","/");
while(fileName.startsWith("/")){
fileName = fileName.substring(1);
}
return fileName;
}
public void processFile(File file){
try{
if(searchFile(file)){
addFileToZip(file);
}
} catch (IOException|UncheckedIOException e) {
System.out.println("Error processing file: "+ file + " "+ e);
}
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public String getRegex() {
return regex;
}
public void setRegex(String regex) {
this.regex = regex;
this.pattern = Pattern.compile(regex);
}
public String getZipFileName() {
return zipFileName;
}
public void setZipFileName(String zipFileName) {
this.zipFileName = zipFileName;
}
// Java 6 approach
/*
public void walkDirectoryJava6(String path) throws IOException{
File dir = new File(path);
File[] files = dir.listFiles();
for (File file: files
) {
if(file.isDirectory()){
walkDirectoryJava6(file.getAbsolutePath());
}else {
processFile(file);
}
}
}
*/
// Java 6 Approach
/*
public boolean searchFileJava6(File file) throws FileNotFoundException{
boolean found = false;
Scanner sc = null;
sc = new Scanner(file, "UTF-8");
while(sc.hasNextLine()){
found = searchText(sc.nextLine());
if(found) break;
}
sc.close();
return found;
}
*/
//
}