Java 7 中 NIO.2 的使用——文件递归操作

  众所周知,递归编程是一项有争议的技术,因为它需要大量的内存,但是它能简化一些编程任务。基本上,一个递归操作都是程序调用自己传递参数修改的值或者参数传递到当前的程序循环中。递归编程通常用来计算阶乘斐波那契数列,回文,谢尔宾斯基地毯等问题。下面的代码演示了用递归实现的阶乘。

/**
   * Calculate the factorial of n (n! = 1 * 2 * 3 * … * n).
   *
   * @param n the number to calculate the factorial of.
   * @return n! - the factorial of n.
   */
 static int fact(int n) {
 
   // Base Case: 
   // If n <= 1 then n! = 1.
    if (n <= 1) {
        return 1;
    }
    // Recursive Case:  
    // If n > 1 then n! = n * (n-1)!
    else {
         return n * fact(n-1);
         }
    }

  许多文件的操作需要访问文件树结构下的文件,只用递归操作再合适不过。NIO.2 在接口中封装了遍历文件树的过程。这个接口叫FileVisitor,在java.nio.file包中。

  这一节我们从FileVisitor学起,一旦你熟悉了FileVisitor,你就能开发一系列执行操作包括文件树的游历,文件的查找,删除,拷贝和移动。

  -----------------------------------------------------------------------------------------------------------------------

  FileVisitor接口

  如前面提到的,FileVisitor接口提供了对文件树递归遍历的支持。接口中的方法可以在访问文件的时候,目录访问之前,目录访问之后,发生失败后采取控制,一旦你获得控制,你就能决定如何访问文件,通过FileVisitResult枚举决定当访问下一个访问结果时如何处理。这个枚举有四个枚举常量:

  • FileVisitResult.CONTINUE: 这个访问结果表明,遍历过程应该继续。根据FileVisitor的方法返回类型可以翻译成不同的行为。
  • FileVisitResult.SKIP_SIBLINGS: 这个访问结果表明,遍历过程应该继续没有访问这个文件或目录的兄弟姐妹节点。
  • FileVisitResult.SKIP_SUBTREE: 这次访问结果表明,遍历过程应该继续除了这个目录中剩下其他的条目。
  • FileVisitResult.TERMINATE: 这次访问结果表明,遍历过程应该终止。

这些枚举的常亮可以用下面的代码进行遍历。

for (FileVisitResult constant : FileVisitResult.values())
     System.out.println(constant);

 下面的章节来讨论实现FileVisitor接口的方法如何控制遍历过程。

FileVisitor.visitFile()方法

  这个方法用在一个在目录中的文件时被调用。通常,这个方法返回CONTINUE 或 TERMINATE。 例如:当寻找一个文件时,方法应该返回CONTINUE知道文件找到,和文件找到后返回TERMINATE。

  当方法调用后,它接收一个file的引用一节文件的一些基本属性。如果发生IO异常,它会抛出IOException异常。次方法的签名如下:

FileVisitResult visitFile(T file, BasicFileAttributes attrs) throws IOException

FileVisitor.preVisitDirectory()方法

  此方法在开始访问以为目录的条目开始之前被调用。如果方法返回CONTINUE 则 目录中的条目将被访问。如果方法返回SKIP_SUBTREE则目录里的条目不被访问。当然你也可以访问文件或是目录的兄弟节点如果方法返回SKIP_SIBLINGS。方法签名为:

FileVisitResult preVisitDirectory(T dir, BasicFileAttributes attrs) throws IOException

FileVisitor.postVisitDirectory()方法

  当整个目录里的所有条目访问以后或访问突然结束时此方法被调用。当这个方法被调用后,它会持有目录和IOException的引用——如果没有异常发生则会返回null,或发生一个异常返回对应的错误。下面是此方法的签名:

FileVisitResult postVisitDirectory(T dir, IOException exc) throws IOException

FileVisitor.visitFileFailed()方法

  因为各种不同的原因导致文件不能访问时此方法被调用,例如文件的属性不允许读取或目录不允许打开。方法签名如下:

FileVisitResult visitFileFailed(T file, IOException exc) throws IOException

----------------------------------------------------------------------------------------------------------------

 SimpleFileVisitor类

  实现FileVisitor接口必须要求实现所有的方法,如果你只需要实现其中一个或几个方法,就显得有些不合需要的。在这种情况下,继承SimpleFileVisitor类就显得简单很多,它实现了FileVisitor接口。

  例如,你想遍历所有的目录并列出所有的名字,你可以很容易地使用postVisitDirectory()visitFileFailed() 方法。

class ListTree extends SimpleFileVisitor<Path> {
 
    @Override
     public FileVisitResult postVisitDirectory(Path dir, IOException exc) {
 
        System.out.println("Visited directory: " + dir.toString());
 
        return FileVisitResult.CONTINUE;
     }
 
    @Override
     public FileVisitResult visitFileFailed(Path file, IOException exc) {
 
        System.out.println(exc);
 
        return FileVisitResult.CONTINUE;
     }
 } 

 开始递归过程

  一旦你创建了递归机制,就可以开始这个过程通过调用Files.walkFileTree()方法。最简单的Files.walkFileTree()的方法传递一个开始文件(通常是根),还有一个 文件访问者对象去调用每个文件。例如,下面的例子。

Path listDir = Paths.get("C:/rafaelnadal"); //define the starting file tree
 ListTree walk = new ListTree();             //instantiate the walk
 
try{
    Files.walkFileTree(listDir, walk);       //start the walk
    } catch(IOException e){
      System.err.println(e);
    }

  第二个重载的walkFileTree()方法第一个参数是开始的文件,然后是自定义的遍历的选项,目录层级的最大访问数,还有 walk 实例。

 

Path listDir = Paths.get("C:/rafaelnadal");              //define the starting file
 ListTree walk = new ListTree();                          //instantiate the walk
 EnumSet opts = EnumSet.of(FileVisitOption.FOLLOW_LINKS); //follow links
 
try{
    Files.walkFileTree(listDir, opts, Integer.MAX_VALUE, walk); //start the walk
    } catch(IOException e){
      System.err.println(e);
    }

-----------------------------------------------------------------------------------------------------------------------------------

 写一个文件搜索程序

  大多数操作系统会提供专用的工具用来搜索文件(例如,在Linux下可以使用find命令,同时windows也提供了文件搜索工具),从最简单的搜索,到高级搜索,所有的工具都是用同一种方式工作:你指定搜索条件然后等待工具返回匹配的文件。但是,如果你要使用编程方式完成这个功能,FileVisitor接口能帮助你实现遍历查找。无论你是通过名字,扩展名来查找文件还是通过正则表达式在文件内部搜索文本或符号,这些方法都是访问每个文件和执行一些检查来确定这个文件是否满足你的搜索条件。

  如果你基于FileVisitor来实现你的文件搜索工具,你需要注意下面几点:

  • visitFile()方法用在执行当前文件和你要搜索的文件比较的最合适的方法。在这一点上,你可以提取每个文件的名字,扩展名或者它的属性或打开文件用来读取。你可以使用文件名字,扩展名或是其他的方式来决定哪个文件是你要搜索的那个。有时你需要把这些信息混合到复杂的查询中。这个方法不能用来查找目录。
  • 如果你要查找目录,根据不同的情况,可以使用preVisitDirectory()或postVisitDirectory() 方法来实现比较。
  • 如果文件不能访问,则 visitFileFailed() 方法会返回 FileVisitResult.CONTINUE,因为这个问题不会决定整个搜索过程终止。
  • 如果你通过名字检索一个文件并且你知道在整个文件树中有一个对应名字的文件,这时你可以返回FileVisitResult.TERMINATE一旦你使用visistFile()找到了他。否则,将会返回FileVisitResult.CONTINUE。
  • 检索流程可以跟踪符号链接。在子目录树找到符号链接的目标文件之前就能跟踪符号链接就能检索到,这样看来是一个很好的主意。但不总是个好主意,例如,在删除文件的时候则不可用。

通过名字检索文件

  下面的程序通过名字对文件进行检索rafa_1.jpg在默认目录下,当找到后终止检索。

import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitOption;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.EnumSet;

class Search implements FileVisitor {

    private final Path searchedFile;
    public boolean found;

    public Search(Path searchedFile) {
        this.searchedFile = searchedFile;
        this.found = false;
    }

void search(Path file) throws IOException {
        Path name = file.getFileName();
        if (name != null && name.equals(searchedFile)) {
            System.out.println("Searched file was found: " + searchedFile + 
                                                   " in " + file.toRealPath().toString());
            found = true;
        }
    }

    @Override
    public FileVisitResult postVisitDirectory(Object dir, IOException exc) 
                                              throws IOException {
        System.out.println("Visited: " + (Path) dir);
        return FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult preVisitDirectory(Object dir, BasicFileAttributes attrs) 
                                             throws IOException {
        return FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult visitFile(Object file, BasicFileAttributes attrs) 
                                     throws IOException {
        search((Path) file);
        if (!found) {
            return FileVisitResult.CONTINUE;
        } else {
            return FileVisitResult.TERMINATE;
        }
    }

    @Override
    public FileVisitResult visitFileFailed(Object file, IOException exc) 
                                           throws IOException {
        //report an error if necessary
        return FileVisitResult.CONTINUE;
    }
}

class Main {

    public static void main(String[] args) throws IOException {

        Path searchFile = Paths.get("rafa_1.jpg");
        Search walk = new Search(searchFile);
        EnumSet opts = EnumSet.of(FileVisitOption.FOLLOW_LINKS);

        Iterable<Path> dirs = FileSystems.getDefault().getRootDirectories();
        for (Path root : dirs) {
            if (!walk.found) {
                Files.walkFileTree(root, opts, Integer.MAX_VALUE, walk);
            }
        }

        if (!walk.found) {
            System.out.println("The file " + searchFile + " was not found!");
        }
    }
}

 使用模式匹配查找文件

  有时候你可能只知道要检索文件的部分信息,例如只知道名字或是扩展名,基于这些信息的片段,你可以正则表达式。这个检索将会根据匹配的表达式检索到满足条件的文件位置,或许你要查找的文件正在其中。

  下面的代码用来在C:\rafaelnadal目录下查找所有以jpg为扩展名的文件。这个文件会遍历整个目录。

import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitOption;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.EnumSet;


class Search implements FileVisitor {

    private final PathMatcher matcher;

    public Search(String glob) {
        matcher = FileSystems.getDefault().getPathMatcher("glob:" + glob);        
    }

    void search(Path file) throws IOException {
        Path name = file.getFileName();
        if (name != null && matcher.matches(name)) {
            System.out.println("Searched file was found: " + name + 
                                                     " in " + file.toRealPath().toString());
        }
    }

    @Override
    public FileVisitResult postVisitDirectory(Object dir, IOException exc) 
                                                                      throws IOException {
        System.out.println("Visited: " + (Path) dir);
        return FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult preVisitDirectory(Object dir, BasicFileAttributes attrs) 
                                                                      throws IOException {
        return FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult visitFile(Object file, BasicFileAttributes attrs) 
                                                                      throws IOException {
        search((Path) file);
        return FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult visitFileFailed(Object file, IOException exc) 
                                                                      throws IOException {
        //report an error if necessary
        return FileVisitResult.CONTINUE;
    }
}

class Main {

    public static void main(String[] args) throws IOException {

        String glob = "*.jpg";
        Path fileTree = Paths.get("C:/rafaelnadal/");
        Search walk = new Search(glob);
        EnumSet opts = EnumSet.of(FileVisitOption.FOLLOW_LINKS);

        Files.walkFileTree(fileTree, opts, Integer.MAX_VALUE, walk);

    }
}

   如果你还知道要查找的文件其他信息,你可以创建一个复杂的检索。例如,除了知道文件的名字和类型,你还知道文件大概小于多少KB,或许是文件的创建日期,修改日期,是否是隐藏文件或只读,谁拥有它。下面的代码演示查找所有扩展名为jpg并且文件小于100KB的文件。

 

import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitOption;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.EnumSet;

class Search implements FileVisitor {

    private final PathMatcher matcher;
    private final long accepted_size;

    public Search(String glob, long accepted_size) {
        matcher = FileSystems.getDefault().getPathMatcher("glob:" + glob);
        this.accepted_size = accepted_size;        
    }

    void search(Path file) throws IOException {
        Path name = file.getFileName();
        long size = (Long) Files.getAttribute(file, "basic:size");

        if (name != null && matcher.matches(name) && size <= accepted_size) {
            System.out.println("Searched file was found: " + name + " in " +
                                  file.toRealPath().toString() + " size (bytes):" + size);
        }
    }

    @Override
    public FileVisitResult postVisitDirectory(Object dir, IOException exc) 
                                                                      throws IOException {
        System.out.println("Visited: " + (Path) dir);
        return FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult preVisitDirectory(Object dir, BasicFileAttributes attrs) 
                                                                      throws IOException { 
        return FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult visitFile(Object file, BasicFileAttributes attrs)
                                                                      throws IOException {
        search((Path) file);
        return FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult visitFileFailed(Object file, IOException exc) 
                                                                      throws IOException {
        //report an error if necessary
        return FileVisitResult.CONTINUE;
    }
}

class Main {

    public static void main(String[] args) throws IOException {

        String glob = "*.jpg";
        long size = 102400; //100 kilobytes in bytes
        Path fileTree = Paths.get("C:/rafaelnadal/");
        Search walk = new Search(glob, size);
        EnumSet opts = EnumSet.of(FileVisitOption.FOLLOW_LINKS);

        Files.walkFileTree(fileTree, opts, Integer.MAX_VALUE, walk);       
    }
}

 根据文件里的内容查找文件

  其中一种比较高级的搜索文件的方式是个根据文件中的内容。你可以传递一个字符序列或是一段句子,然后返回这包含这些内容的文件。这种方式也也是最耗时的,因为他要进入每个文件里面,这就意味着要先打开文件,读取,最后关闭它。此外,这里有很多文件格式来支持文本,例如,pdf,Microsoft word, Excel,powerpoint,简单文本, XML, HTML, XHTML等等。每一种格式的读取方式不尽相同。

  接下来我们会开发一个程序用你来根据传递的字符串内容来搜索文件,这个传递的字符串里的序列以逗号分隔, 例如:Rafael Nadal,tennis,winner of Roland Garros,BNP Paribas tournament draws。 使用StringTokenizer类,以逗号为分隔符,下面的例子就是提取每个单词或句子放入ArrayList中。

…
 String words="Rafael Nadal,tennis,winner of Roland Garros,BNP Paribas tournament draws";
 ArrayList<String> wordsarray = new ArrayList<>();
 …
 StringTokenizer st = new StringTokenizer(words, ",");
 while (st.hasMoreTokens()) {
        wordsarray.add(st.nextToken());
 }

  下面的代码循环ArrayList然后比较从访问的文件中提取的比较每个单词和句子。searchText()方法以提取的文本作为参数。

//search text
 private boolean searchText(String text) {
 
   boolean flag = false;
    for (int j = 0; j < wordsarray.size(); j++) {
         if ((text.toLowerCase()).contains(wordsarray.get(j).toLowerCase())) {
             flag = true;
             break;
        }
    }
    return flag;
 }

  下面的例子根据不同的格式来搜索文本。不要重新造轮子,所以我们使用第三方的类库来实现这些功能。

 在PDF文件中搜索

  对于读取PDF文件,我们将使用两个非常流行的开源类库:iText(http://itextpdf.com/) 和 Apache PDFBox(http://pdfbox.apache.org/)。

 基于iText文档,下面的方法用来从pdf中提取文本。第一步包括创建一个PdfReader访问文件,接着通过提取出PDF的页数,从每个页面中提取文本,提取的文本传递给searchText()方法。如果在提取的文本中找到一token。则停止在当前文件检索,该文件被认为是一个有效的搜索结果,及其存储路径和名称我们可以打印出来后,整个搜索结束。

//search in PDF files using iText library
 boolean searchInPDF_iText(String file) {
 
   PdfReader reader = null;
    boolean flag = false;
 
   try {
        reader = new PdfReader(file);
        int n = reader.getNumberOfPages();
 
       OUTERMOST:
        for (int i = 1; i <= n; i++) {
          String str = PdfTextExtractor.getTextFromPage(reader, i);
 
         flag = searchText(str);
          if (flag) {
                break OUTERMOST;
          }
        }
 
   } catch (Exception e) {
       } finally {
           if (reader != null) {
               reader.close();
             }
             return flag;
       }
 }

   如果你对PDFBox更加熟悉,你可以使用下面的方法。通过PDFParser 类创建PDF文件,然后提取出文件的页数,并完成通过searchText()方法提取每一页的文本。

 

boolean searchInPDF_PDFBox(String file) {
 
   PDFParser parser = null;
    String parsedText = null;
    PDFTextStripper pdfStripper = null;
    PDDocument pdDoc = null;
    COSDocument cosDoc = null;
    boolean flag = false;
    int page = 0;
 
   File pdf = new File(file);
 
   try {
        parser = new PDFParser(new FileInputStream(pdf));
        parser.parse();
 
       cosDoc = parser.getDocument();
        pdfStripper = new PDFTextStripper();
        pdDoc = new PDDocument(cosDoc);
 
       OUTERMOST:
        while (page < pdDoc.getNumberOfPages()) {
             page++;
             pdfStripper.setStartPage(page);
             pdfStripper.setEndPage(page + 1);
             parsedText = pdfStripper.getText(pdDoc);
 
            flag = searchText(parsedText);
             if (flag) {
                  break OUTERMOST;
             }
        }
    } catch (Exception e) {
    } finally {
           try {
              if (cosDoc != null) {
                   cosDoc.close();
              }
              if (pdDoc != null) {
                   pdDoc.close();
              }
           } catch (Exception e) {}
    return flag;
    }
 }

 在Microsoft的Word,Excel和PowerPoint中查找

  微软的办公套装软件文件可以使用Apache POI(http://poi.apache.org/)类库来操作。下面的例子使用版本3.7,基于开发指南,从word中查找字符串的例子。

boolean searchInWord(String file) {
 
   POIFSFileSystem fs = null;
    boolean flag = false;
 
   try {
        fs = new POIFSFileSystem(new FileInputStream(file));
 
       HWPFDocument doc = new HWPFDocument(fs);
        WordExtractor we = new WordExtractor(doc);
        String[] paragraphs = we.getParagraphText();
 
       OUTERMOST:
        for (int i = 0; i < paragraphs.length; i++) {
 
             flag = searchText(paragraphs[i]);
              if (flag) {
                    break OUTERMOST;
              }
        }
 
     } catch (Exception e) {
      } finally {
             return flag;
      }
 }

  我们从下面的例子中从Excel文本里提取文本。通过HSSFWorkbook创建excel文档,先遍历每个sheet页,然后是逐行,最后每个单元格。

boolean searchInExcel(String file) {
 
   Row row;
    Cell cell;
    String text;
    boolean flag = false;
    InputStream xls = null;
 
   try {
        xls = new FileInputStream(file);
        HSSFWorkbook wb = new HSSFWorkbook(xls);
 
       int sheets = wb.getNumberOfSheets();
 
       OUTERMOST:
        for (int i = 0; i < sheets; i++) {
             HSSFSheet sheet = wb.getSheetAt(i);
 
            Iterator<Row> row_iterator = sheet.rowIterator();
             while (row_iterator.hasNext()) {
                 row = (Row) row_iterator.next();
                 Iterator<Cell> cell_iterator = row.cellIterator();
                 while (cell_iterator.hasNext()) {
                     cell = cell_iterator.next();
                     int type = cell.getCellType();
                     if (type == HSSFCell.CELL_TYPE_STRING) {
                           text = cell.getStringCellValue();
                           flag = searchText(text);
                           if (flag) {
                                 break OUTERMOST;
                           }
                     }
                 }
             }
        }
 
   } catch (IOException e) {
    } finally {
          try {
              if (xls != null) {
                     xls.close();
              }
          } catch (IOException e) {}
    return flag;
    }
 }

  最后我们可以使用下面的代码从PowerPoint中提取文本。

boolean searchInPPT(String file) {
 
   boolean flag = false;
    InputStream fis = null;
    String text;
 
   try {
        fis = new FileInputStream(new File(file));
        POIFSFileSystem fs = new POIFSFileSystem(fis);
        HSLFSlideShow show = new HSLFSlideShow(fs);
 
       SlideShow ss = new SlideShow(show);
        Slide[] slides = ss.getSlides();
 
       OUTERMOST:
        for (int i = 0; i < slides.length; i++) {
 
          TextRun[] runs = slides[i].getTextRuns();
           for (int j = 0; j < runs.length; j++) {
              TextRun run = runs[j];
              if (run.getRunType() == TextHeaderAtom.TITLE_TYPE) {
                  text = run.getText();
              } else {
                  text = run.getRunType() + " " + run.getText();
              }
 
             flag = searchText(text);
              if (flag) {
                     break OUTERMOST;
              }
           }
 
       Notes notes = slides[i].getNotesSheet();
        if (notes != null) {
            runs = notes.getTextRuns();
            for (int j = 0; j < runs.length; j++) {
                 text = runs[j].getText();
                 flag = searchText(text);
                 if (flag) {
                       break OUTERMOST;
                 }
            }
        }
      }
    } catch (IOException e) {
    } finally {
          try {
            if (fis != null) {
                  fis.close();
              }
           } catch (IOException e) {}
    return flag;
    }
 } 

   此外还有很多其他第三方优秀的类库,上面的例子在处理一些检索时不一定是最高效的。或许可以使用Apache Lucene(http://lucene.apache.org/java/docs/index.html)来获取很高的方式来处理。

 在文本文件中搜索

  文本文件(.txt, .html, .xml 等)不需要第三方的类库,它们完全可以使用NOI.2 的技术来实现:

boolean searchInText(Path file) {
 
   boolean flag = false;
    Charset charset = Charset.forName("UTF-8");
    try (BufferedReader reader = Files.newBufferedReader(file, charset)) {
         String line = null;
 
        OUTERMOST:
         while ((line = reader.readLine()) != null) {
               flag = searchText(line);
               if (flag) {
                    break OUTERMOST;
               }
         }
 
    } catch (IOException e) {
     } finally {
           return flag;
     }
 }

写一个完整的检索程序

import com.itextpdf.text.pdf.PdfReader;
 import com.itextpdf.text.pdf.parser.PdfTextExtractor;
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.charset.Charset;
 import java.nio.file.FileSystems;
 import java.nio.file.FileVisitOption;
 import java.nio.file.FileVisitResult;
 import java.nio.file.FileVisitor;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.attribute.BasicFileAttributes;
 import java.util.ArrayList;
 import java.util.EnumSet;
 import java.util.Iterator;
 import java.util.StringTokenizer;
 import org.apache.pdfbox.cos.COSDocument;
 import org.apache.pdfbox.pdfparser.PDFParser;
 import org.apache.pdfbox.pdmodel.PDDocument;
 import org.apache.pdfbox.util.PDFTextStripper;
 import org.apache.poi.hslf.HSLFSlideShow;
 import org.apache.poi.hslf.model.Notes;
 import org.apache.poi.hslf.model.Slide;
 import org.apache.poi.hslf.model.TextRun;
 import org.apache.poi.hslf.record.TextHeaderAtom;
 import org.apache.poi.hslf.usermodel.SlideShow;
 import org.apache.poi.hssf.usermodel.HSSFCell;
 import org.apache.poi.hssf.usermodel.HSSFSheet;
 import org.apache.poi.hssf.usermodel.HSSFWorkbook;
 import org.apache.poi.hwpf.HWPFDocument;
 import org.apache.poi.hwpf.extractor.WordExtractor;
 import org.apache.poi.poifs.filesystem.POIFSFileSystem;
 import org.apache.poi.ss.usermodel.Cell;
 import org.apache.poi.ss.usermodel.Row;
 
class Search implements FileVisitor {
 
    ArrayList<String> wordsarray = new ArrayList<>();
     ArrayList<String> documents = new ArrayList<>();
     boolean found = false;
 
    public Search(String words) {
         wordsarray.clear();
         documents.clear();
 
        StringTokenizer st = new StringTokenizer(words, ",");
         while (st.hasMoreTokens()) {
             wordsarray.add(st.nextToken().trim());
         }
     }
 
    void search(Path file) throws IOException {
 
        found = false;
 
        String name = file.getFileName().toString();
         int mid = name.lastIndexOf(".");
         String ext = name.substring(mid + 1, name.length());
 
        if (ext.equalsIgnoreCase("pdf")) {
             found = searchInPDF_iText(file.toString());
             if (!found) {
                 found = searchInPDF_PDFBox(file.toString());
             }
         }
 
        if (ext.equalsIgnoreCase("doc") || ext.equalsIgnoreCase("docx")) {
             found = searchInWord(file.toString());
         }
 
        if (ext.equalsIgnoreCase("ppt")) {
             searchInPPT(file.toString());
         }
 
        if (ext.equalsIgnoreCase("xls")) {
             searchInExcel(file.toString());
         }
 
        if ((ext.equalsIgnoreCase("txt")) || (ext.equalsIgnoreCase("xml") 
                                          || ext.equalsIgnoreCase("html"))
                 || ext.equalsIgnoreCase("htm") || ext.equalsIgnoreCase("xhtml")
                                                || ext.equalsIgnoreCase("rtf")) {
             searchInText(file);
         }
 
        if (found) {
             documents.add(file.toString());
         }
     }
 
    //search in text files
     boolean searchInText(Path file) {
 
        boolean flag = false;
         Charset charset = Charset.forName("UTF-8");
         try (BufferedReader reader = Files.newBufferedReader(file, charset)) {
             String line = null;
 
            OUTERMOST:
             while ((line = reader.readLine()) != null) {
                 flag = searchText(line);
                 if (flag) {
                     break OUTERMOST;
                 }
             }
 
        } catch (IOException e) {
         } finally {
             return flag;
         }
     }
 
    //search in Excel files
     boolean searchInExcel(String file) {
 
        Row row;
         Cell cell;
         String text;
         boolean flag = false;
         InputStream xls = null;
 
        try {
             xls = new FileInputStream(file);
             HSSFWorkbook wb = new HSSFWorkbook(xls);
 
            int sheets = wb.getNumberOfSheets();
 
            OUTERMOST:
             for (int i = 0; i < sheets; i++) {
                 HSSFSheet sheet = wb.getSheetAt(i);
 
                Iterator<Row> row_iterator = sheet.rowIterator();
                 while (row_iterator.hasNext()) {
                     row = (Row) row_iterator.next();
                     Iterator<Cell> cell_iterator = row.cellIterator();
                     while (cell_iterator.hasNext()) {
                         cell = cell_iterator.next();
                         int type = cell.getCellType();
                         if (type == HSSFCell.CELL_TYPE_STRING) {
                             text = cell.getStringCellValue();
                             flag = searchText(text);
                             if (flag) {
                                 break OUTERMOST;
                             }
                         }
                     }
                 }
             }
 
        } catch (IOException e) {
         } finally {
             try {
                 if (xls != null) {
                     xls.close();
                 }
             } catch (IOException e) {
             }
             return flag;
         }
     }
 
    //search in PowerPoint files
     boolean searchInPPT(String file) {
 
        boolean flag = false;
         InputStream fis = null;
         String text;
 
        try {
             fis = new FileInputStream(new File(file));
             POIFSFileSystem fs = new POIFSFileSystem(fis);
             HSLFSlideShow show = new HSLFSlideShow(fs);
 
            SlideShow ss = new SlideShow(show);
             Slide[] slides = ss.getSlides();
 
            OUTERMOST:
             for (int i = 0; i < slides.length; i++) {
 
                TextRun[] runs = slides[i].getTextRuns();
                 for (int j = 0; j < runs.length; j++) {
                     TextRun run = runs[j];
                     if (run.getRunType() == TextHeaderAtom.TITLE_TYPE) {
                         text = run.getText();
                     } else {
                         text = run.getRunType() + " " + run.getText();
                     }
 
                    flag = searchText(text);
                     if (flag) {
                         break OUTERMOST;
                     }
 
                }
 
                Notes notes = slides[i].getNotesSheet();
                 if (notes != null) {
                     runs = notes.getTextRuns();
                     for (int j = 0; j < runs.length; j++) {
                         text = runs[j].getText();
                         flag = searchText(text);
                         if (flag) {
                             break OUTERMOST;
                         }
                     }
                 }
             }
 
        } catch (IOException e) {
         } finally {
             try {
                 if (fis != null) {
                     fis.close();
                 }
             } catch (IOException e) {
             }
             return flag;
         }
 
    }
 
    //search in Word files
     boolean searchInWord(String file) {
 
        POIFSFileSystem fs = null;
         boolean flag = false;
 
        try {
             fs = new POIFSFileSystem(new FileInputStream(file));
 
            HWPFDocument doc = new HWPFDocument(fs);
             WordExtractor we = new WordExtractor(doc);
             String[] paragraphs = we.getParagraphText();
 
            OUTERMOST:
             for (int i = 0; i < paragraphs.length; i++) {
 
                flag = searchText(paragraphs[i]);
                 if (flag) {
                     break OUTERMOST;
                 }
             }
 
        } catch (Exception e) {
         } finally {
             return flag;
         }
     }
 
    //search in PDF files using PDFBox library
     boolean searchInPDF_PDFBox(String file) {
 
        PDFParser parser = null;
         String parsedText = null;
         PDFTextStripper pdfStripper = null;
         PDDocument pdDoc = null;
         COSDocument cosDoc = null;
         boolean flag = false;
         int page = 0;
 
        File pdf = new File(file);
 
        try {
             parser = new PDFParser(new FileInputStream(pdf));
             parser.parse();
 
            cosDoc = parser.getDocument();
             pdfStripper = new PDFTextStripper();
             pdDoc = new PDDocument(cosDoc);
 
            OUTERMOST:
             while (page < pdDoc.getNumberOfPages()) {
                 page++;
                 pdfStripper.setStartPage(page);
                 pdfStripper.setEndPage(page + 1);
                 parsedText = pdfStripper.getText(pdDoc);
 
                flag = searchText(parsedText);
                 if (flag) {
                     break OUTERMOST;
                 }
             }
 
        } catch (Exception e) {
         } finally {
             try {
                 if (cosDoc != null) {
                     cosDoc.close();
                 }
                 if (pdDoc != null) {
                     pdDoc.close();
                 }
             } catch (Exception e) {
             }
             return flag;
         }
     }
 
    //search in PDF files using iText library
     boolean searchInPDF_iText(String file) {
 
        PdfReader reader = null;
         boolean flag = false;
 
        try {
             reader = new PdfReader(file);
             int n = reader.getNumberOfPages();
 
            OUTERMOST:
             for (int i = 1; i <= n; i++) {
                 String str = PdfTextExtractor.getTextFromPage(reader, i);
 
                flag = searchText(str);
                 if (flag) {
                     break OUTERMOST;
                 }
             }
 
        } catch (Exception e) {
         } finally {
             if (reader != null) {
                 reader.close();
             }
             return flag;
         }
 
    }
 
    //search text
     private boolean searchText(String text) {
 
        boolean flag = false;
         for (int j = 0; j < wordsarray.size(); j++) {
             if ((text.toLowerCase()).contains(wordsarray.get(j).toLowerCase())) {
                 flag = true;
                 break;
             }
         }
 
        return flag;
     }
 
    @Override
     public FileVisitResult postVisitDirectory(Object dir, IOException exc) 
                                                                      throws IOException {
         System.out.println("Visited: " + (Path) dir);
         return FileVisitResult.CONTINUE;
     }
 
    @Override
     public FileVisitResult preVisitDirectory(Object dir, BasicFileAttributes attrs) 
                                                                      throws IOException {
         return FileVisitResult.CONTINUE;
     }
 
    @Override
     public FileVisitResult visitFile(Object file, BasicFileAttributes attrs) 
                                                                      throws IOException {
         search((Path) file);
         return FileVisitResult.CONTINUE;
     }
 
    @Override
     public FileVisitResult visitFileFailed(Object file, IOException exc) 
                                                                      throws IOException {
         //report an error if necessary
 
        return FileVisitResult.CONTINUE;
     }
 }
 
class Main {
 
public static void main(String[] args) throws IOException {
 
 String words = "Rafael Nadal, tennis, winner of Roland Garros, BNP Paribas tournament draws";
  Search walk = new Search(words);
  EnumSet opts = EnumSet.of(FileVisitOption.FOLLOW_LINKS);
 
 Iterable<Path> dirs = FileSystems.getDefault().getRootDirectories();
  for (Path root : dirs) {
       Files.walkFileTree(root, opts, Integer.MAX_VALUE, walk);
  }
         
  System.out.println("____________________________________________________________");
  for(String path_string: walk.documents){
      System.out.println(path_string);
  }
  System.out.println("____________________________________________________________");
                
 }
 }

   需要注意的是,有些时候程序处理的很慢,这主要取决于文件大小,要检查的文件的个数,目录的层级深度。此外你可以继续完善这个程序,例如支持更多地格式,增加进度条来显示处理的状态,使用多线程加快查找速度。

 

posted @ 2015-10-26 09:50  林本托  阅读(1611)  评论(0编辑  收藏  举报