You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java中如何实现限制行数的自定义Circular OutputStream?

实现限制最大行数的环形OutputStream

要做这个满了就覆盖旧内容的输出流,核心是把字节流拆成行,用固定大小的队列存行,满了就踢掉最旧的,具体可以这么搞:

核心逻辑

  1. 因为OutputStream是字节级操作,得先处理换行符——不管是Unix的\n还是Windows的\r\n,都要正确识别成一行的结束。
  2. 用一个队列存每一行的内容,队列大小设为你要的最大行数,每次新增行前检查,满了就删掉队首的旧行。
  3. 自定义OutputStream,重写write方法,实时缓存还没到换行的内容,碰到换行就把整行丢进队列,同时处理满员逻辑。

代码示例(Java)

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.LinkedList;
import java.util.Queue;

public class CircularLineOutputStream extends OutputStream {
    private final int maxLines;
    private final Queue<String> lineQueue;
    private final ByteArrayOutputStream currentLineBuffer;
    private boolean lastCharWasCR; // 处理\r\n的情况

    public CircularLineOutputStream(int maxLines) {
        if (maxLines <= 0) {
            throw new IllegalArgumentException("最大行数必须大于0");
        }
        this.maxLines = maxLines;
        this.lineQueue = new LinkedList<>();
        this.currentLineBuffer = new ByteArrayOutputStream();
        this.lastCharWasCR = false;
    }

    @Override
    public void write(int b) throws IOException {
        // 单独处理\r,避免和\n重复触发换行
        if (b == '\r') {
            lastCharWasCR = true;
            return;
        }
        // 碰到\n就把当前缓存的行加入队列
        if (b == '\n') {
            addCurrentLineToQueue();
            lastCharWasCR = false;
            return;
        }
        // 如果上一个字符是\r但当前不是\n,把\r补回缓存
        if (lastCharWasCR) {
            currentLineBuffer.write('\r');
            lastCharWasCR = false;
        }
        currentLineBuffer.write(b);
    }

    @Override
    public void flush() throws IOException {
        // 把没写完的半行也加入队列
        if (currentLineBuffer.size() > 0) {
            addCurrentLineToQueue();
        }
        super.flush();
    }

    @Override
    public void close() throws IOException {
        flush();
        currentLineBuffer.close();
        super.close();
    }

    private void addCurrentLineToQueue() {
        String line = currentLineBuffer.toString();
        currentLineBuffer.reset();
        // 队列满了就移除最旧的行
        if (lineQueue.size() >= maxLines) {
            lineQueue.poll();
        }
        lineQueue.add(line);
    }

    // 获取所有缓存的行,转成字节数组
    public byte[] toByteArray() {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try {
            for (String line : lineQueue) {
                baos.write(line.getBytes());
                baos.write('\n'); // 统一用\n换行,要保留原格式的话可以改逻辑
            }
            // 加上没触发换行的半行内容
            if (currentLineBuffer.size() > 0) {
                baos.write(currentLineBuffer.toByteArray());
            }
        } catch (IOException e) {
            throw new RuntimeException(e); // 内存操作不会抛IO异常,直接转运行时异常
        }
        return baos.toByteArray();
    }

    @Override
    public String toString() {
        return new String(toByteArray());
    }
}

怎么用

  1. 初始化的时候指定最大行数:CircularLineOutputStream cos = new CircularLineOutputStream(5);(最多存5行)
  2. 像普通OutputStream一样写内容:cos.write("测试第一行\n".getBytes());
  3. 要取内容的时候直接调用cos.toString()或者cos.toByteArray(),满了之后新行会自动覆盖最旧的
  4. 注意:如果写的内容没换行,在调用flush()或者close()之前,这部分内容会留在临时缓存里,不会进入队列;调用flush()后才会把它当成一行加入队列。

可以优化的地方

  • 现在默认用平台编码,你可以加个构造函数传字符集(比如Charset UTF_8),避免乱码
  • 如果要直接写文件,不用内存缓存的话,可以在每次加入新行时,直接把覆盖后的内容写入目标OutputStream
  • 要是处理超大内容怕内存爆,可以把队列换成磁盘上的环形文件,但复杂度会高一些

内容的提问来源于stack exchange,提问作者Алексей

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 11:27:05