不把String直接转成byte数组的前提下,将String包装成InputStream,适合处理大字符串

一般而言,用以下代码将String转成InputStream。

    public static InputStream wrapToInputStream(String input, Charset charset) {
        byte[] bytes = input.getBytes(charset);
        return new ByteArrayInputStream(bytes);
    }

但这个方法会生成完整的byte数组。

为了避免转换过程产生完整的byte数组,可以用下面这个类。

import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;

/**
 * 把 CharSequence 包装成 InputStream, 流式读取
 * 避免String.getBytes产生数据拷贝, 适合处理大字符串
 *
 * @author: zyp
 * @since: 2021/12/14 下午1:33
 */
public class InpuStreamFromString extends InputStream {
    private final CharSequence charSequence;
    private int pos;
    private Charset charset;
    private int maxBytesPerChar;
    private CharsetEncoder charsetEncoder;
    private CharBuffer charBuffer;
    private ByteBuffer byteBuffer;
    private boolean flush;

    public InpuStreamFromString(CharSequence charSequence, Charset charset) {
        this.charSequence = charSequence;
        this.charset = charset;
        this.charsetEncoder = charset.newEncoder();
//        读取字符的字符缓冲区
        this.charBuffer = CharBuffer.allocate(2);
        this.maxBytesPerChar = (int) Math.ceil(this.charsetEncoder.maxBytesPerChar());
//        字节缓冲器, 将字符缓冲区的字符编码成byte放在这里
//        可能有emoji, 占用两个char, 所以需要2倍空间
        this.byteBuffer = ByteBuffer.allocate(maxBytesPerChar << 1);
        this.byteBuffer.flip();
    }

    /**
     * 处理一个字符
     */
    private boolean decodeOneChar() throws IOException {
        if (pos >= charSequence.length()) {
//           某些编码在结尾会有特殊数据, 需要flush一下
            if (!flush) {
                charsetEncoder.flush(this.byteBuffer);
                this.byteBuffer.flip();
                flush = true;
                return true;
            }
            return false;
        }
        char char1 = charSequence.charAt(pos++);
        this.charBuffer.put(char1);
        if (Character.isHighSurrogate(char1)) {
            char char2 = charSequence.charAt(pos);
            if (Character.isLowSurrogate(char2)) {
//                处理emoji
                this.charBuffer.put(char2);
                pos++;
            }
        }
        this.charBuffer.flip();
//        编码到字符缓冲区中
        charsetEncoder.encode(this.charBuffer, this.byteBuffer, true);
        this.charBuffer.clear();
        this.byteBuffer.flip();
        return true;
    }

    @Override
    public int read(byte[] b) throws IOException {
        return read(b, 0, b.length);
    }

    @Override
    public int read(byte[] b, int off, int len) throws IOException {
//        先读缓存
        if (len <= 0) {
            return 0;
        }
        int read = 0;
        while (len > read) {
//            先把剩余的数据读了
            read += readBuffer(byteBuffer, b, off + read, len - read);
            if (byteBuffer.hasRemaining()) {
                break; // 读够了
            }
            byteBuffer.clear();
//            读新的数据
            if (!decodeOneChar()) {
//                读完了
                byteBuffer.flip();
                return read == 0 ? -1 : read;
            }
        }
        return read;
    }

    private int readBuffer(ByteBuffer buf, byte[] b, int off, int len) {
        int leftLen = Math.min(buf.remaining(), len);
        int readLen = leftLen;
        while (leftLen-- > 0) {
            b[off++] = buf.get();
        }
        return readLen;
    }

    @Override
    public long skip(long n) throws IOException {
        throw new IOException("skip not supportƒed");
    }

    @Override
    public int read() throws IOException {
        if (byteBuffer.hasRemaining()) {
            return byteBuffer.get();
        }
        byteBuffer.clear();
        if (!decodeOneChar()) {
//                读完了
            return -1;
        }
        return byteBuffer.get();
    }


}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值