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

Java如何截取InputStream子流实现超长字节流的省略号截断处理?

实现方案

InputStream本身不预存长度信息,没有通用方法可以在读取前拿到总大小,以下是两种落地性最高的实现方案:

方案1:全量缓存后裁剪(实现最简单,适合流大小可控场景)

直接把整个InputStream读取为完整字节数组,再根据阈值判断裁剪逻辑,代码实现最简洁,不容易出bug。

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

public byte[] ellipsis(InputStream is) throws IOException {
    final int THRESHOLD = 10_000;
    final int TAIL_KEEP_SIZE = 1000;
    // 省略号字节可根据实际业务编码调整,此处用UTF-8编码的英文三个点
    final byte[] ELLIPSIS_BYTES = "...".getBytes(StandardCharsets.UTF_8);

    // 1. 全量读取流到字节数组
    try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
        byte[] buffer = new byte[4096];
        int read;
        while ((read = is.read(buffer)) != -1) {
            baos.write(buffer, 0, read);
        }
        byte[] fullData = baos.toByteArray();
        
        // 2. 未超过阈值直接返回
        if (fullData.length <= THRESHOLD) {
            return fullData;
        }
        
        // 3. 超过阈值计算裁剪区间,避免裁剪后比原内容更长的边界问题
        int headKeepSize = (int) (fullData.length * 2.0 / 3);
        if (headKeepSize + TAIL_KEEP_SIZE + ELLIPSIS_BYTES.length >= fullData.length) {
            return fullData;
        }
        
        // 4. 拼接结果
        ByteArrayOutputStream resultBaos = new ByteArrayOutputStream();
        resultBaos.write(fullData, 0, headKeepSize);
        resultBaos.write(ELLIPSIS_BYTES);
        resultBaos.write(fullData, fullData.length - TAIL_KEEP_SIZE, TAIL_KEEP_SIZE);
        return resultBaos.toByteArray();
    }
}

注意:该方案需要将整个流内容加载到内存,如果业务场景会遇到GB级别的大InputStream,会引发OOM风险,建议用方案2。

方案2:流式滚动缓存(内存占用固定,适合大流量场景)

不需要全量缓存整个流,只需要缓存头部2/3阈值对应的内容,再用固定大小的环形缓冲区存储流末尾的1000字节,同时统计流总长度,内存占用稳定在阈值+1000字节左右,适合大文件、大流量场景。

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

public byte[] ellipsis(InputStream is) throws IOException {
    final int THRESHOLD = 10_000;
    final int TAIL_KEEP_SIZE = 1000;
    final byte[] ELLIPSIS_BYTES = "...".getBytes(StandardCharsets.UTF_8);
    final int HEAD_MAX_SIZE = (int) (THRESHOLD * 2.0 / 3); // 最多缓存的头部长度

    ByteArrayOutputStream headCache = new ByteArrayOutputStream();
    byte[] tailCache = new byte[TAIL_KEEP_SIZE];
    int tailPos = 0;
    long totalLength = 0;
    byte[] buffer = new byte[4096];
    int read;

    while ((read = is.read(buffer)) != -1) {
        totalLength += read;
        // 没超过头部最大长度时写入头部缓存
        int headWriteLen = Math.min(read, HEAD_MAX_SIZE - headCache.size());
        if (headWriteLen > 0) {
            headCache.write(buffer, 0, headWriteLen);
        }
        // 写入尾部环形缓存
        for (int i = 0; i < read; i++) {
            tailCache[tailPos % TAIL_KEEP_SIZE] = buffer[i];
            tailPos++;
        }
    }

    // 未超过阈值,合并头缓存和有效尾部内容返回
    if (totalLength <= THRESHOLD) {
        ByteArrayOutputStream full = new ByteArrayOutputStream();
        headCache.writeTo(full);
        if (totalLength > HEAD_MAX_SIZE) {
            int validTailLen = (int) Math.min(totalLength - HEAD_MAX_SIZE, TAIL_KEEP_SIZE);
            int start = (tailPos - validTailLen) % TAIL_KEEP_SIZE;
            start = start < 0 ? start + TAIL_KEEP_SIZE : start;
            if (start + validTailLen <= TAIL_KEEP_SIZE) {
                full.write(tailCache, start, validTailLen);
            } else {
                full.write(tailCache, start, TAIL_KEEP_SIZE - start);
                full.write(tailCache, 0, validTailLen - (TAIL_KEEP_SIZE - start));
            }
        }
        return full.toByteArray();
    }

    // 超过阈值,拼接头、省略号、尾部
    ByteArrayOutputStream result = new ByteArrayOutputStream();
    headCache.writeTo(result);
    result.write(ELLIPSIS_BYTES);
    int start = (tailPos - TAIL_KEEP_SIZE) % TAIL_KEEP_SIZE;
    start = start < 0 ? start + TAIL_KEEP_SIZE : start;
    if (start + TAIL_KEEP_SIZE <= TAIL_KEEP_SIZE) {
        result.write(tailCache, start, TAIL_KEEP_SIZE);
    } else {
        result.write(tailCache, start, TAIL_KEEP_SIZE - start);
        result.write(tailCache, 0, TAIL_KEEP_SIZE - (TAIL_KEEP_SIZE - start));
    }
    return result.toByteArray();
}
可选工具库简化实现
  • 可以用Apache Commons IO的CircularByteBuffer替代自行实现的尾部环形缓存,减少代码出错概率
  • 可以用BoundedInputStream限制最大读取长度,避免异常超大流占用过多资源
注意事项
  • 如果处理的是文本内容,裁剪时需要注意不要截断多字节编码字符(比如UTF-8中文占3字节),否则会出现乱码,可以先解码为字符串再裁剪,最后转回字节数组
  • 所有InputStream操作建议包裹在try-with-resources中,自动关闭流资源

内容的提问来源于stack exchange,提问作者All_Safe

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.26 10:54:03