java.io.BufferedWriter 在写入过程中节流或完全停止,有人知道为什么吗?
java.io.BufferedWriter throttles or completely stopped during the process of writing, anyone know why?
正如问题中所述,我在使用 java.io.BufferedWriter
写入时遇到了奇怪的写入速度限制(甚至完全暂停)。
我的程序正在尝试从一堆 .csv 文件中读取,并将它们重新打包到另一堆按第一列分组的 .csv 文件。
例如,如果我在 .csv 文件中有一行 Tom, 86, 87, 88
,则此行将写入名为 Tom.csv
.
的 .csv 文件中
我使用 HashMap<String, BufferedWriter>
来缓存写入器,这样程序只需打开/关闭写入器一次。
(为了调试我特意把文件列表和流程逻辑分开了)
代码:
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();
}
}
}
输出(对于这种情况,程序在行 bw.write(dataLine);
期间被冻结):
- 我使用Intellij-IDEA作为编辑器,使用调试模式执行程序。
Connected to the target VM, address: '127.0.0.1:25111', transport: 'socket'
[Info] Current file "..\scan-dir\L2_options_20150102.csv"
[Progress] 8166463/109787564
如果有人知道原因/对此有解决方案,那就太好了:(
谢谢!
打开许多文件可能会给磁盘操作系统、文件句柄数量(有限!)和“移动写头”带来沉重负担。
关于static
代码显示经验(也涉及Java),可能也来自像C 这样的语言。因为static
的使用不常见。您可以在 main
中执行 new Main().executeMyThings();
并在其他地方删除 static
。
采取的措施
不使用文本而是二进制数据,not
Writer/Reader,但是OutputStream/InputStream
。这可以防止 Unicode 来回转换。当 Windows UTF-8 文件位于 Linux.
上时,您还有数据丢失的风险
使用 ArrayList
而不是 LinkedList
因为对于许多项目来说它可能表现得更好。
您可能想要收集文件路径而不是 BufferedWriters。每个 BufferedWriter 不仅是一个 OS 资源,而且还维护着一个缓冲区(内存)。它甚至可能性能更高,写入标题行,关闭并以追加模式重新打开它。标题可以写成 Files.writeString
.
System.out 是昂贵的。 ConsoleLogger 可能更安全,并发性更安全,
但成本也很高。
readByteSize 缺少换行符,Windows 个文件有 2 个。
可以使用 larger/smaller 缓冲区大小创建 BufferedWriter。
for (Path path : ds) {
SCANNED_FILE_LIST.add(path.toAbsolutePath());
}
可能会更好:
ds.forEach(path -> SCANNED_FILE_LIST.add(path.toAbsolutePath());
正如问题中所述,我在使用 java.io.BufferedWriter
写入时遇到了奇怪的写入速度限制(甚至完全暂停)。
我的程序正在尝试从一堆 .csv 文件中读取,并将它们重新打包到另一堆按第一列分组的 .csv 文件。
例如,如果我在 .csv 文件中有一行 Tom, 86, 87, 88
,则此行将写入名为 Tom.csv
.
我使用 HashMap<String, BufferedWriter>
来缓存写入器,这样程序只需打开/关闭写入器一次。
(为了调试我特意把文件列表和流程逻辑分开了)
代码:
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();
}
}
}
输出(对于这种情况,程序在行 bw.write(dataLine);
期间被冻结):
- 我使用Intellij-IDEA作为编辑器,使用调试模式执行程序。
Connected to the target VM, address: '127.0.0.1:25111', transport: 'socket'
[Info] Current file "..\scan-dir\L2_options_20150102.csv"
[Progress] 8166463/109787564
如果有人知道原因/对此有解决方案,那就太好了:( 谢谢!
打开许多文件可能会给磁盘操作系统、文件句柄数量(有限!)和“移动写头”带来沉重负担。
关于static
代码显示经验(也涉及Java),可能也来自像C 这样的语言。因为static
的使用不常见。您可以在 main
中执行 new Main().executeMyThings();
并在其他地方删除 static
。
采取的措施
不使用文本而是二进制数据,
上时,您还有数据丢失的风险not
Writer/Reader,但是OutputStream/InputStream
。这可以防止 Unicode 来回转换。当 Windows UTF-8 文件位于 Linux.使用
ArrayList
而不是LinkedList
因为对于许多项目来说它可能表现得更好。您可能想要收集文件路径而不是 BufferedWriters。每个 BufferedWriter 不仅是一个 OS 资源,而且还维护着一个缓冲区(内存)。它甚至可能性能更高,写入标题行,关闭并以追加模式重新打开它。标题可以写成
Files.writeString
.System.out 是昂贵的。 ConsoleLogger 可能更安全,并发性更安全, 但成本也很高。
readByteSize 缺少换行符,Windows 个文件有 2 个。
可以使用 larger/smaller 缓冲区大小创建 BufferedWriter。
for (Path path : ds) { SCANNED_FILE_LIST.add(path.toAbsolutePath()); }
可能会更好:
ds.forEach(path -> SCANNED_FILE_LIST.add(path.toAbsolutePath());