在编写过程中,java.io.BufferedWriter会节流或完全停止,有人知道原因吗?

在编写过程中,java.io.BufferedWriter会节流或完全停止,有人知道原因吗?,java,intellij-idea,io,Java,Intellij Idea,Io,如问题中所述,在使用java.io.BufferedWriter进行写入时,我遇到了奇怪的写入速度限制(甚至完全暂停) 我的程序试图从一堆.csv文件中读取数据,然后将它们重新打包到按第一列分组的另一堆.csv文件中 例如,如果.csv文件Tom,86,87,88中有一行,这一行将写入名为Tom.csv的.csv文件中 我使用HashMap缓存写入程序,这样程序只需打开/关闭写入程序一次 (我故意将文件列表和流程逻辑拆分以进行调试) 守则: package dev.repackcsv; imp

如问题中所述,在使用
java.io.BufferedWriter
进行写入时,我遇到了奇怪的写入速度限制(甚至完全暂停)

我的程序试图从一堆.csv文件中读取数据,然后将它们重新打包到按第一列分组的另一堆.csv文件中

例如,如果.csv文件
Tom,86,87,88
中有一行,这一行将写入名为
Tom.csv
的.csv文件中

我使用
HashMap
缓存写入程序,这样程序只需打开/关闭写入程序一次

(我故意将文件列表和流程逻辑拆分以进行调试)

守则:

package dev.repackcsv;

import java.io.*;
import java.nio.file.*;
import java.util.*;

public class Main {

    private static final Path USER_DIR;
    private static final Path PROP_FILE;
    private static final Properties PROPERTIES;

    private static final Path SCAN_DIR;
    private static final Path OUTPUT_DIR;

    private static final List<Path> SCANNED_FILE_LIST;
    private static final Map<String, BufferedWriter> OUTPUT_FILE_MAP;

    private static void loadProperties() {
        try (InputStream propFileInputStream = Files.newInputStream(PROP_FILE)) {
            PROPERTIES.load(propFileInputStream);
        } catch (IOException e) {
            System.err.println("[Error] Failed to load properties from \"application.properties\"");
            System.exit(1);
        }
    }

    private static String getProperty(String propertyName) {
        String property = PROPERTIES.getProperty(propertyName);
        if (property == null) {
            System.err.println("[Error] Undefined property: " + propertyName);
            System.exit(1);
        }
        return property;
    }

    static {
        USER_DIR = Paths.get(System.getProperty("user.dir"));
        PROP_FILE = USER_DIR.resolve("application.properties");
        if (!Files.exists(PROP_FILE)) {
            System.err.println("[Error] \"application.properties\" file does not exist.");
            System.exit(1);
        }

        PROPERTIES = new Properties();
        loadProperties();
        SCAN_DIR = Paths.get(getProperty("scan.dir")).toAbsolutePath();
        if (!Files.exists(SCAN_DIR)) {
            System.err.println("[Error] Scan directory does not exist");
            System.exit(1);
        }
        OUTPUT_DIR = Paths.get(getProperty("output.dir")).toAbsolutePath();
        if (!Files.exists(OUTPUT_DIR)) {
            System.err.println("[Error] Output directory does not exist");
            System.exit(1);
        }

        SCANNED_FILE_LIST = new LinkedList<>();
        OUTPUT_FILE_MAP = new HashMap<>();
    }

    private static void loadScannedFileList()
            throws IOException {
        try (DirectoryStream<Path> ds = Files.newDirectoryStream(SCAN_DIR)) {
            for (Path path : ds) {
                SCANNED_FILE_LIST.add(path.toAbsolutePath());
            }
        }
    }

    private static BufferedWriter getOutputFileBufferedWriter(String key, String headLine) throws IOException {
        if (OUTPUT_FILE_MAP.containsKey(key)) {
            return OUTPUT_FILE_MAP.get(key);
        } else {
            Path outputFile = OUTPUT_DIR.resolve(key + ".csv");
            boolean isNewFile = false;
            if (!Files.exists(outputFile)) {
                Files.createFile(outputFile);
                isNewFile = true;
            }
            BufferedWriter bw = Files.newBufferedWriter(outputFile);
            if (isNewFile) {
                bw.write(headLine);
                bw.newLine();
                bw.flush();
            }
            OUTPUT_FILE_MAP.put(key, bw);
            return bw;
        }
    }

    private static void processScannedCSV(Path csvFile)
            throws IOException {
        System.out.printf("[Info] Current file \"%s\"%n", csvFile);
        long fileSize = Files.size(csvFile);
        try (BufferedReader br = new BufferedReader(new InputStreamReader(Files.newInputStream(csvFile)))) {
            String headLine = br.readLine();
            if (headLine == null) { return; }
            String dataLine;
            long readByteSize = 0;
            while ((dataLine = br.readLine()) != null) {
                int firstCommaIndex = dataLine.indexOf(',');
                if (firstCommaIndex == -1) { continue; }
                BufferedWriter bw = getOutputFileBufferedWriter(dataLine.substring(0, firstCommaIndex), headLine);
                bw.write(dataLine);
                bw.newLine();
                readByteSize += dataLine.getBytes().length;
                System.out.print("\r[Progress] " + readByteSize + '/' + fileSize);
            }
        }
        System.out.print("\r");
    }

    private static void processScannedFiles()
            throws IOException {
        for (Path file : SCANNED_FILE_LIST) {
            if (!Files.exists(file)) {
                System.out.printf("[WARN] Scanned file \"%s\" does not exist, skipping...%n", file);
                continue;
            }
            if (!file.toString().endsWith(".csv")) { continue; }
            processScannedCSV(file);
        }
    }

    public static void main(String[] args)
            throws IOException {
        loadScannedFileList();
        processScannedFiles();
        for (BufferedWriter bw : OUTPUT_FILE_MAP.values()) {
            bw.flush();
            bw.close();
        }
    }

}

如果有人知道原因/有解决方案,那就太好了:(
谢谢!

打开许多文件对磁盘操作系统来说可能是一个沉重的负担,文件句柄的数量(有限!)和“移动写入头”应该可以同时执行

关于
静态
s

代码显示了经验(同样与Java有关),可能也来自C语言。因为使用
static
是不寻常的。您可以在
main
中执行
new main().executeMyThings();
并将
static
放到其他地方

要采取的措施

  • 不要使用文本而是二进制数据,
    不要使用编写器/读取器,而要使用
    输出流/输入流
    。这会阻止Unicode的来回转换。在Linux上运行Windows UTF-8文件时,还可能会丢失数据

  • 使用
    ArrayList
    而不是
    LinkedList
    ,因为对于许多项目,它可能表现得更好

  • 您可能希望收集文件路径,而不是BufferedWriter。每个BufferedWriter不仅是一个操作系统资源,而且维护一个缓冲区(内存)。它甚至可能更高效,可以写入头行,在追加模式下关闭并重新打开它。标题可以使用
    文件写入。writeString

  • System.out的成本很高。控制台记录器可能更安全,使用并发会更安全, 但成本也很高

  • readByteSize忽略换行符,对于Windows文件为2

  • 可以使用较大/较小的缓冲区大小创建BufferedWriter

          for (Path path : ds) {
              SCANNED_FILE_LIST.add(path.toAbsolutePath());
          }
    
最好是:

        ds.forEach(path -> SCANNED_FILE_LIST.add(path.toAbsolutePath());
        ds.forEach(path -> SCANNED_FILE_LIST.add(path.toAbsolutePath());