Java:同时写入和读取文件

时间:2019-01-06 17:29:33

标签: java

这实际上是一个设计问题。而且我不确定在这里读写文件是否是理想的解决方案。尽管如此,我将在下面概述我要执行的操作: 我有以下静态方法,一旦调用reqStreamingData的{​​{1}}方法,它将开始以150毫秒的速率不断地从客户端服务器检索数据。

obj

现在,我不熟悉多线程。我担心的是

  1. 请求数据线程和数据处理线程正在同时读写文件,但速率不同 不知道数据处理线程是否会延迟请求数据线程 由于数据处理比请求数据线程有更多的计算繁重的任务要执行,因此数量很多。但是,鉴于它们是2个单独的线程,在这里会发生任何错误或异常吗?
  2. 我不太支持同时写入和读取同一文件的想法,但是由于我必须使用R实时处理和存储R的数据帧中的数据,所以我真的想不出其他方法来处理这个。有更好的选择吗?
  3. 有没有更好的设计来解决这个问题?

我知道这是一个漫长的问题。如果您需要更多信息,请告诉我。

1 个答案:

答案 0 :(得分:2)

可以将这些行(CSV或任何其他文本)写入一个临时文件。当准备好处理时,仅在临时文件被新文件替换时,才需要进行同步。这样可以确保生产者永远不会同时写入消费者正在处理的文件。

完成后,生产者将继续在新文件中添加行。使用者刷新并关闭旧文件,然后按照R应用程序的预期将其移动到文件中。

为进一步阐明这种方法,下面是一个示例实现:

public static void main(String[] args) throws IOException {
    // in this sample these dirs are supposed to exist
    final String workingDirectory = "./data/tmp";
    final String outputDirectory = "./data/csv";

    final String outputFilename = "r.out";
    final int addIntervalSeconds = 1;
    final int drainIntervalSeconds = 5;

    final FileBasedTextBatch batch = new FileBasedTextBatch(Paths.get(workingDirectory));
    final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);

    final ScheduledFuture<?> producer = executor.scheduleAtFixedRate(
        () -> batch.add(
            // adding formatted date/time to imitate another CSV line
            LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME)
        ),
        0, addIntervalSeconds, TimeUnit.SECONDS);

    final ScheduledFuture<?> consumer = executor.scheduleAtFixedRate(
        () -> batch.drainTo(Paths.get(outputDirectory, outputFilename)),
        0, drainIntervalSeconds, TimeUnit.SECONDS);

    try {
        // awaiting some limited time for demonstration 
        producer.get(30, TimeUnit.SECONDS);
    }
    catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
    catch (ExecutionException e) {
        System.err.println("Producer failed: " + e);
    }
    catch (TimeoutException e) {
        System.out.println("Finishing producer/consumer...");
        producer.cancel(true);
        consumer.cancel(true);
    }
    executor.shutdown();
}

static class FileBasedTextBatch {
    private final Object lock = new Object();
    private final Path workingDir;
    private Output output;

    public FileBasedTextBatch(Path workingDir) throws IOException {
        this.workingDir = workingDir;
        output = new Output(this.workingDir);
    }

    /**
     * Adds another line of text to the batch.
     */
    public void add(String textLine) {
        synchronized (lock) {
            output.writer.println(textLine);
        }
    }

    /**
     * Moves currently collected batch to the file at the specified path.
     * The file will be overwritten if exists.
     */
    public void drainTo(Path targetPath) {
        try {
            final long startNanos = System.nanoTime();
            final Output output = getAndSwapOutput();
            final long elapsedMillis =
                TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNanos);
            System.out.printf("Replaced the output in %d millis%n", elapsedMillis);
            output.close();
            Files.move(
                output.file,
                targetPath,
                StandardCopyOption.ATOMIC_MOVE,
                StandardCopyOption.REPLACE_EXISTING
            );
        }
        catch (IOException e) {
            System.err.println("Failed to drain: " + e);
            throw new IllegalStateException(e);
        }
    }

    /**
     * Replaces the current output with the new one, returning the old one.
     * The method is supposed to execute very quickly to avoid delaying the producer thread.
     */
    private Output getAndSwapOutput() throws IOException {
        synchronized (lock) {
            final Output prev = this.output;
            this.output = new Output(this.workingDir);
            return prev;
        }
    }
}

static class Output {
    final Path file;
    final PrintWriter writer;

    Output(Path workingDir) throws IOException {
        // performs very well on local filesystems when working directory is empty;
        // if too slow, maybe replaced with UUID based name generation
        this.file = Files.createTempFile(workingDir, "csv", ".tmp");
        this.writer = new PrintWriter(Files.newBufferedWriter(this.file));
    }

    void close() {
        if (this.writer != null)
            this.writer.flush();
            this.writer.close();
    }
}