Java中如何实现限制行数的自定义Circular OutputStream?
实现限制最大行数的环形OutputStream
要做这个满了就覆盖旧内容的输出流,核心是把字节流拆成行,用固定大小的队列存行,满了就踢掉最旧的,具体可以这么搞:
核心逻辑
- 因为OutputStream是字节级操作,得先处理换行符——不管是Unix的
\n还是Windows的\r\n,都要正确识别成一行的结束。 - 用一个队列存每一行的内容,队列大小设为你要的最大行数,每次新增行前检查,满了就删掉队首的旧行。
- 自定义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()); } }
怎么用
- 初始化的时候指定最大行数:
CircularLineOutputStream cos = new CircularLineOutputStream(5);(最多存5行) - 像普通OutputStream一样写内容:
cos.write("测试第一行\n".getBytes()); - 要取内容的时候直接调用
cos.toString()或者cos.toByteArray(),满了之后新行会自动覆盖最旧的 - 注意:如果写的内容没换行,在调用
flush()或者close()之前,这部分内容会留在临时缓存里,不会进入队列;调用flush()后才会把它当成一行加入队列。
可以优化的地方
- 现在默认用平台编码,你可以加个构造函数传字符集(比如
Charset UTF_8),避免乱码 - 如果要直接写文件,不用内存缓存的话,可以在每次加入新行时,直接把覆盖后的内容写入目标OutputStream
- 要是处理超大内容怕内存爆,可以把队列换成磁盘上的环形文件,但复杂度会高一些
内容的提问来源于stack exchange,提问作者Алексей
相关产品推荐
相关产品推荐

