写入 java 时限制文件大小

Limit file size while writing in java

我需要将文件大小限制为 1 GB,同时最好使用 BufferedWriter

是否可以使用 BufferedWriter 或者我必须使用其他库?

喜欢

try (BufferedWriter writer = Files.newBufferedWriter(path)) {   
    //...
    writer.write(lines.stream());
} 

IIUC,方法多种多样

  1. 继续在卡盘中写入数据并刷新它,并在每次刷新后继续检查文件大小。
  2. 使用 log4j(或某些日志记录框架),它可以让我们在特定大小、时间或其他触发点后滚动到新文件。
  3. 虽然 BufferedReader 很棒,但 java 中有一些新的 API 可以使其更快。 Fastest way to write huge data in text file Java

您可以随时编写自己的 OutputStream 来限制写入的 字节 的数量。

以下假定您希望在超出大小时抛出异常。

public final class LimitedOutputStream extends FilterOutputStream {
    private final long maxBytes;
    private long       bytesWritten;
    public LimitedOutputStream(OutputStream out, long maxBytes) {
        super(out);
        this.maxBytes = maxBytes;
    }
    @Override
    public void write(int b) throws IOException {
        ensureCapacity(1);
        super.write(b);
    }
    @Override
    public void write(byte[] b) throws IOException {
        ensureCapacity(b.length);
        super.write(b);
    }
    @Override
    public void write(byte[] b, int off, int len) throws IOException {
        ensureCapacity(len);
        super.write(b, off, len);
    }
    private void ensureCapacity(int len) throws IOException {
        long newBytesWritten = this.bytesWritten + len;
        if (newBytesWritten > this.maxBytes)
            throw new IOException("File size exceeded: " + newBytesWritten + " > " + this.maxBytes);
        this.bytesWritten = newBytesWritten;
    }
}

您现在当然必须手动设置 Writer/OutputStream 链。

final long SIZE_1GB = 1073741824L;
try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(
        new LimitedOutputStream(Files.newOutputStream(path), SIZE_1GB),
        StandardCharsets.UTF_8))) {
    //
}

在您写行的情况下,将字节精确到 1 GB 是非常困难的。每行可能包含未知数量的字节。我假设您想在文件中逐行写入数据。

但是,您可以在将行写入文件之前检查行有多少字节,另一种方法是在写入每一行后检查文件大小。

下面的基本例子每次写一行。这里 这只是一个测试! 文本以 UTF-8 编码在文件中占用 21 个字节。最终在 49 次写入后达到 1029 字节并停止写入。

public class Test {

    private static final int ONE_KB = 1024;

    public static void main(String[] args) {
        File file = new File("D:/test.txt");

        try (BufferedWriter writer = Files.newBufferedWriter(file.toPath())) {
            while (file.length() < ONE_KB) {
                writer.write("This is just a test !");
                writer.flush();
            }
            System.out.println("1 KB Data is written to the file.!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

如您所见,我们已经超出了 1KB 的限制,因为上面的程序写入了 1029 字节且不少于 1024 字节。

第二种方法是在将字节写入文件之前根据特定编码检查字节。

public class Test {

    private static final int ONE_KB = 1024;

    public static void main(String[] args) throws UnsupportedEncodingException {
        File file = new File("D:/test.txt");
        String data = "This is just a test !";
        int dataLength = data.getBytes("UTF-8").length;

        try (BufferedWriter writer = Files.newBufferedWriter(file.toPath())) {
            while (file.length() + dataLength < ONE_KB) {
                writer.write(data);
                writer.flush();
            }
            System.out.println("1 KB Data written to the file.!");
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

}   

在这种方法中,我们在将字节长度写入文件之前检查字节长度。所以,它将写入 1008 字节,然后停止写入。

两种方法都有问题,

  • 写入并检查:您可能会得到一些额外的字节,文件大小可能会超过限制
  • 检查并写入:如果下一行包含大量数据,您的字节数可能会少于限制。你应该小心编码。

但是,还有其他方法可以使用某些第三方库(例如 apache io 进行此验证,我发现它比传统的 java 方法更麻烦。

int maxSize = 1_000_000_000;
Charset charset = StandardCharsets.UTF_F);

int size = 0;
int lineCount = 0;
while (lineCount < lines.length) {
     long size2 = size + (lines[lineCount] + "\r\n").getBytes(charset).length;
     if (size2 > maxSize) {
         break;
     }
     size = size2;
     ++lineCount;
}

List<String> linesToWrite = lines.substring(0, lineCount);
Path path = Paths.get("D:/test.txt");
Files.write(path, linesToWrite , charset);

或仅解码一次时速度更快:

int lineCount = 0;
try (FileChannel channel = new RandomAccessFile("D:/test.txt", "w").getChannel()) {
    ByteBuffer buf = channel.map(FileChannel.MapMode.WRITE, 0, maxSize);
    lineCount = lines.length;
    for (int i = 0; i < lines.length; i++) {
        bytes[] line = (lines.get(i) + "\r\n").getBytes(charset);
        if (line.length > buffer.remaining()) {
            lineCount = i;
            break;
        }
        buffer.put(line);
    }
}