【问题标题】:Streaming files and moving them after read流式传输文件并在读取后移动它们
【发布时间】:2020-09-06 20:59:16
【问题描述】:

我想流式传输文件中包含的行,但在处理每个文件后将其移动到另一个文件夹。

目前的流程是这样的:

解释:

  1. 我创建了StreamFiles
  2. 我为每个人创建一个BufferedReader
  3. flatMapBufferedReader 的行Stream
  4. 我打印每一行。

代码(为简单起见省略了例外):

(1)    Stream.generate(localFileProvider::getNextFile)
(2)       .map(file -> new BufferedReader(new InputStreamReader(new FileInputStream(file))))
(3)       .flatMap(BufferedReader::lines)
(4)       .map(System.out::println)
          .MOVE_EACH_FILE_FROM_INPUT_FOLDER_TO_SOME_OTHER_FOLDER;

是否可以在每个文件被完全读取后移动它并继续处理流中的其他文件?

【问题讨论】:

  • 有趣的问题。尽管我担心一旦映射文件,您就会丢失有关文件的所有信息。这就像有一条水流,您将每一滴水映射到苏打水(通过添加二氧化碳),然后通过添加糖浆将每一滴苏打水映射到可乐,最后您将 System.our.println 放入玻璃杯中 - 但是一旦您喝了一杯可乐,你想把水拿回来。你不能在一个循环中按顺序处理它,而不是映射吗?即使它不那么性感。
  • 你能用.forEach关闭输入流和移动文件吗?
  • 流中的元素,我们只是一个一个地处理,我们无法删除它们,所以虽然删除流中的元素(不可能)从初始文件中删除行也是不可能的。

标签: java java-8 java-stream


【解决方案1】:

我只想创建两个方法:

public void processFile(File f);
public void moveFile(File f, File dstFolder);

然后在 lambda 中:

Stream.generate(localFileProvider::getNextFile).forEach(file->
   {
     processFile(file);
     moveFile(file, dstFolder);
   }
);

【讨论】:

    【解决方案2】:

    其实如果你能把逻辑分成不同的方法就很容易了

     public Path readFile(File eachFile) {
       BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
    
      //try-with-resources
     try (Stream<String> lines = reader.lines()) {
    lines.forEach(System.out::println);
    
     } 
    catch (IOException e) {
       e.printStackTrace();
     }
       return eachFile.toPath();
      }
    

    然后对每个文件调用这个方法

    (1)    Stream.generate(localFileProvider::getNextFile)
    (2)          .map(this::readFile) //process each file
    (3)          .forEach(path->Files.move(path,Paths.get("new path"))); //then move each file
    

    【讨论】:

      【解决方案3】:

      你可以chain a close action到一个流,如果flatMap会自动执行:

      Stream.generate(localFileProvider::getNextFile).takeWhile(Objects::nonNull)
      
          .flatMap(file -> {
              try {
                  Path p = file.toPath();
                  return Files.lines(p, Charset.defaultCharset()).onClose(() -> {
                      try { // move path/x/y/z to path/x/y/z.moved
                          Files.move(p, p.resolveSibling(p.getFileName()+".moved"));
                      } catch(IOException ex) { throw new UncheckedIOException(ex); }
                  });
              } catch(IOException ex) { throw new UncheckedIOException(ex); }
          })
      
          .forEach(System.out::println);
      

      the documentation of onClose 声明很重要:

      在流上调用close() 方法时运行关闭处理程序,并按照添加它们的顺序执行。

      因此,移动关闭处理程序在已经存在的关闭处理程序之后执行,该处理程序将关闭用于读取行的文件句柄。

      我使用Charset.defaultCharset() 来模仿问题代码的嵌套构造函数new InputStreamReader(new FileInputStream(file))) 的行为,但通常,您应该尽可能使用固定字符集,例如Files.lines 的默认UTF-8。

      【讨论】:

      • 我也是这么想的,但是想知道我们是否可以通过合同确定 lines 流在 文件最后一行的 println 之后关闭是否被调用。我真的不确定。 (问题是您可以检测到文件已被完全读取,但不能检测到其最后一行已被处理,或者您可以吗?)。这个问题是否是一个问题并不是很清楚。另外,如果println 抛出,文件是否移动了?我认为一般的解决方案应该必须将 Stream/Reader 状态与其最后一行的状态联系起来......
      • @GPI 问题说,“移动每个文件一旦它被完全读取”这并不要求到那时处理已经完成。我将其解释为“一旦我们确定它不会干扰阅读就移动”,这是一个典型的任务。虽然在这里,我们可以控制打开,但我们也可以在新位置打开之前移动文件……
      • 很公平。如果println(或者实际上是终端操作)在文件的“中间”(让我们假设一切都是串行的)失败怎么办?流不是关闭了,文件还是移动了?
      • @GPI 好吧,是的。问题没有说明这是否符合意图。如果没有,循环解决方案会简单得多(考虑到所需异常处理的噪音,甚至比现在更简单)。
      • @Solubris 我不能再肯定地说,有两种可能性。或者,为了简单起见,策略只是将整个 lambda 主体放入 try 块中,或者代码最初在 return 语句中重复包含表达式 file.toPath(),我使用了我的 IDE 的“提取到变量”选项.但是,正如你所说的那样,因为这条线不能抛出IOException,所以无论如何都没关系。
      【解决方案4】:

      可以这样做:

          files
              .map( file -> {
                  getBufferedReader( file ).lines()
                      .forEach( System.out::println );
                  return file;
              } )
              .forEach( this::moveFile );
      

      已检查异常和Reader.close的更新:

      诚然,这不会在 finally 块中运行 close(),所以这是一个缺点。本次更新的重点主要是说明在 Java 8 流中处理已检查异常的一种方式。

      假设您有以下可用的实用程序代码:

      private interface ThrowingFunction<I, O>
      {
          O apply( I input ) throws Exception;
      }
      
      private <I, O> Function<I, O> unchecked( ThrowingFunction<I, O> checked )
      {
          return i -> {
              try {
                  return checked.apply( i );
              }
              catch ( Exception e ) {
                  throw new RuntimeException();
              }
          };
      }
      
      private interface ThrowingConsumer<T>
      {
          void consume( T input ) throws Exception;
      }
      
      private <T> Consumer<T> unchecked( ThrowingConsumer<T> checked )
      {
          return t -> {
              try {
                  checked.consume( t );
              }
              catch ( Exception e ) {
                  throw new RuntimeException();
              }
          };
      }
      
      private BufferedReader getBufferedReader( File file ) throws FileNotFoundException
      {
          return new BufferedReader( new InputStreamReader( new FileInputStream( file )));
      }
      

      编写实际的代码就变成了:

          files
              .map( file -> {
                  Stream.of( file )
                      .map( unchecked( this::getBufferedReader ))
                      .map( reader -> {
                          reader.lines().forEach( System.out::println );
                          return reader;
                      } )
                      .forEach( unchecked( Reader::close ));
                  return file;
              } )
              .forEach( this::moveFile );
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2022-08-03
        • 2011-07-22
        • 2013-02-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-01-12
        • 2020-02-15
        相关资源
        最近更新 更多