mirror of
https://gitee.com/dromara/hutool.git
synced 2025-04-24 18:04:54 +08:00
add InputStreamResource ReaderInputStream WriterOutputStream
This commit is contained in:
parent
412ec8ae14
commit
7daeb183b5
@ -13,11 +13,14 @@
|
||||
package org.dromara.hutool.core.io.resource;
|
||||
|
||||
import org.dromara.hutool.core.io.IORuntimeException;
|
||||
import org.dromara.hutool.core.io.stream.ReaderInputStream;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.Reader;
|
||||
import java.io.Serializable;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/**
|
||||
* 基于{@link InputStream}的资源获取器<br>
|
||||
@ -32,6 +35,16 @@ public class InputStreamResource implements Resource, Serializable {
|
||||
private final InputStream in;
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param reader {@link Reader}
|
||||
* @param charset 编码
|
||||
*/
|
||||
public InputStreamResource(final Reader reader, final Charset charset) {
|
||||
this(new ReaderInputStream(reader, charset));
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
|
@ -0,0 +1,171 @@
|
||||
/*
|
||||
* Copyright (c) 2024. looly(loolly@aliyun.com)
|
||||
* Hutool is licensed under Mulan PSL v2.
|
||||
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
* You may obtain a copy of Mulan PSL v2 at:
|
||||
* https://license.coscl.org.cn/MulanPSL2
|
||||
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
* See the Mulan PSL v2 for more details.
|
||||
*/
|
||||
|
||||
package org.dromara.hutool.core.io.stream;
|
||||
|
||||
import org.dromara.hutool.core.io.IoUtil;
|
||||
import org.dromara.hutool.core.lang.Assert;
|
||||
import org.dromara.hutool.core.util.CharsetUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.Reader;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.CharsetEncoder;
|
||||
import java.nio.charset.CoderResult;
|
||||
import java.nio.charset.CodingErrorAction;
|
||||
|
||||
/**
|
||||
* {@link Reader}作为{@link InputStream}使用的实现。<br>
|
||||
* 参考:Apache Commons IO
|
||||
*
|
||||
* @author commons-io
|
||||
*/
|
||||
public class ReaderInputStream extends InputStream {
|
||||
private final static int DEFAULT_BUFFER_SIZE = IoUtil.DEFAULT_BUFFER_SIZE;
|
||||
|
||||
private final Reader reader;
|
||||
// 用于将字符转换为字节的CharsetEncoder
|
||||
private final CharsetEncoder encoder;
|
||||
private final CharBuffer encoderIn;
|
||||
private final ByteBuffer encoderOut;
|
||||
private CoderResult lastCoderResult;
|
||||
private boolean endOfInput;
|
||||
|
||||
/**
|
||||
* 构造,使用指定的字符集和默认缓冲区大小
|
||||
*
|
||||
* @param reader 提供字符数据的Reader
|
||||
* @param charset 字符集,用于创建CharsetEncoder
|
||||
*/
|
||||
public ReaderInputStream(final Reader reader, final Charset charset) {
|
||||
this(reader, charset, DEFAULT_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造,使用指定的字符集和缓冲区大小
|
||||
*
|
||||
* @param reader 提供字符数据的Reader
|
||||
* @param charset 字符集,用于创建CharsetEncoder
|
||||
* @param bufferSize 缓冲区大小
|
||||
*/
|
||||
public ReaderInputStream(final Reader reader, final Charset charset, final int bufferSize) {
|
||||
this(reader, CharsetUtil.newEncoder(charset, CodingErrorAction.REPLACE), bufferSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造,使用默认的缓冲区大小
|
||||
*
|
||||
* @param reader 提供字符数据的Reader
|
||||
* @param encoder 用于编码的CharsetEncoder
|
||||
*/
|
||||
public ReaderInputStream(final Reader reader, final CharsetEncoder encoder) {
|
||||
this(reader, encoder, DEFAULT_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造,允许指定缓冲区大小。
|
||||
*
|
||||
* @param reader 提供字符数据的Reader
|
||||
* @param encoder 用于编码的CharsetEncoder
|
||||
* @param bufferSize 缓冲区大小
|
||||
*/
|
||||
public ReaderInputStream(final Reader reader, final CharsetEncoder encoder, final int bufferSize) {
|
||||
this.reader = reader;
|
||||
this.encoder = encoder;
|
||||
|
||||
encoderIn = CharBuffer.allocate(bufferSize);
|
||||
encoderIn.flip();
|
||||
encoderOut = ByteBuffer.allocate(bufferSize);
|
||||
encoderOut.flip();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(final byte[] b, int off, int len) throws IOException {
|
||||
Assert.notNull(b, "Byte array must not be null");
|
||||
if ((len < 0) || (off < 0) || (off + len > b.length)) {
|
||||
throw new IndexOutOfBoundsException("Array Size=" + b.length + ", offset=" + off + ", length=" + len);
|
||||
}
|
||||
|
||||
int read = 0;
|
||||
if (len == 0) {
|
||||
return 0;
|
||||
}
|
||||
while (len > 0) {
|
||||
if (encoderOut.hasRemaining()) {
|
||||
final int c = Math.min(encoderOut.remaining(), len);
|
||||
encoderOut.get(b, off, c);
|
||||
off += c;
|
||||
len -= c;
|
||||
read += c;
|
||||
} else {
|
||||
fillBuffer();
|
||||
if ((endOfInput) && (!encoderOut.hasRemaining())) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return (read == 0) && (endOfInput) ? -1 : read;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
do {
|
||||
if (encoderOut.hasRemaining()) {
|
||||
return encoderOut.get() & 0xFF;
|
||||
}
|
||||
fillBuffer();
|
||||
} while ((!endOfInput) || (encoderOut.hasRemaining()));
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
reader.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* 填充缓冲区。
|
||||
* 此方法用于从输入源读取数据,并将其编码后存储到输出缓冲区中。
|
||||
* 它处理输入数据,直到达到输入的末尾或者编码过程中遇到需要停止的条件。
|
||||
* 在这个过程中,它会更新编码器的状态以及输入输出缓冲区的状态。
|
||||
*
|
||||
* @throws IOException 如果在读取输入数据时发生IO异常。
|
||||
*/
|
||||
private void fillBuffer() throws IOException {
|
||||
// 如果输入未结束,并且上一次的编码结果是正常的(没有溢出或错误),则尝试读取更多数据
|
||||
if ((!endOfInput) && ((lastCoderResult == null) || (lastCoderResult.isUnderflow()))) {
|
||||
encoderIn.compact(); // 准备好输入缓冲区,以便接收新的数据
|
||||
final int position = encoderIn.position(); // 记录当前读取位置
|
||||
|
||||
// 从reader中读取数据到encoderIn缓冲区
|
||||
final int c = reader.read(encoderIn.array(), position, encoderIn.remaining());
|
||||
if (c == -1) // 如果读取到输入末尾
|
||||
endOfInput = true;
|
||||
else {
|
||||
// 更新读取位置,准备处理下一批数据
|
||||
encoderIn.position(position + c);
|
||||
}
|
||||
encoderIn.flip(); // 反转输入缓冲区,使其准备好进行编码
|
||||
}
|
||||
|
||||
// 准备输出缓冲区,以便接收编码后的数据
|
||||
encoderOut.compact();
|
||||
// 执行编码操作,将输入缓冲区的数据编码到输出缓冲区
|
||||
lastCoderResult = encoder.encode(encoderIn, encoderOut, endOfInput);
|
||||
// 反转输出缓冲区,使其准备好被写入到最终目的地
|
||||
encoderOut.flip();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Copyright (c) 2024. looly(loolly@aliyun.com)
|
||||
* Hutool is licensed under Mulan PSL v2.
|
||||
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
* You may obtain a copy of Mulan PSL v2 at:
|
||||
* https://license.coscl.org.cn/MulanPSL2
|
||||
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
* See the Mulan PSL v2 for more details.
|
||||
*/
|
||||
|
||||
package org.dromara.hutool.core.io.stream;
|
||||
|
||||
import org.dromara.hutool.core.io.IoUtil;
|
||||
import org.dromara.hutool.core.util.CharsetUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Writer;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.CharsetDecoder;
|
||||
import java.nio.charset.CoderResult;
|
||||
import java.nio.charset.CodingErrorAction;
|
||||
|
||||
/**
|
||||
* 通过一个 Writer和一个CharsetDecoder实现将字节数据输出为字符数据。可以通过不同的构造函数配置缓冲区大小和是否立即写入。
|
||||
* 来自:https://github.com/subchen/jetbrick-commons/blob/master/src/main/java/jetbrick/io/stream/WriterOutputStream.java
|
||||
*
|
||||
* @since 6.0.0
|
||||
* @author subchen
|
||||
*/
|
||||
public class WriterOutputStream extends OutputStream {
|
||||
private static final int DEFAULT_BUFFER_SIZE = IoUtil.DEFAULT_BUFFER_SIZE;
|
||||
|
||||
private final Writer writer;
|
||||
private final CharsetDecoder decoder;
|
||||
private final boolean writeImmediately;
|
||||
private final ByteBuffer decoderIn;
|
||||
private final CharBuffer decoderOut;
|
||||
|
||||
/**
|
||||
* 构造函数,使用指定字符集和默认配置。
|
||||
*
|
||||
* @param writer 目标 Writer,用于写入字符数据
|
||||
* @param charset 字符集,用于编码字节数据
|
||||
*/
|
||||
public WriterOutputStream(final Writer writer, final Charset charset) {
|
||||
this(writer, charset, DEFAULT_BUFFER_SIZE, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造函数,使用指定字符集、默认缓冲区大小和不立即写入配置。
|
||||
*
|
||||
* @param writer 目标 Writer,用于写入字符数据
|
||||
* @param charset 字符集,用于编码字节数据
|
||||
* @param bufferSize 缓冲区大小,用于控制字符数据的临时存储量
|
||||
* @param writeImmediately 是否立即写入,如果为 true,则不使用内部缓冲区,每个字节立即被解码并写入
|
||||
*/
|
||||
public WriterOutputStream(final Writer writer, final Charset charset, final int bufferSize, final boolean writeImmediately) {
|
||||
this(writer, CharsetUtil.newDecoder(charset, CodingErrorAction.REPLACE), bufferSize, writeImmediately);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造,使用默认缓冲区大小和不立即写入配置。
|
||||
*
|
||||
* @param writer 目标 Writer,用于写入字符数据
|
||||
* @param decoder 字符集解码器,用于将字节数据解码为字符数据
|
||||
*/
|
||||
public WriterOutputStream(final Writer writer, final CharsetDecoder decoder) {
|
||||
this(writer, decoder, DEFAULT_BUFFER_SIZE, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造,允许自定义缓冲区大小和是否立即写入的配置。
|
||||
*
|
||||
* @param writer 目标 Writer,用于写入字符数据
|
||||
* @param decoder 字符集解码器,用于将字节数据解码为字符数据
|
||||
* @param bufferSize 缓冲区大小,用于控制字符数据的临时存储量
|
||||
* @param writeImmediately 是否立即写入,如果为 true,则不使用内部缓冲区,每个字节立即被解码并写入
|
||||
*/
|
||||
public WriterOutputStream(final Writer writer, final CharsetDecoder decoder, final int bufferSize, final boolean writeImmediately) {
|
||||
this.writer = writer;
|
||||
this.decoder = decoder;
|
||||
this.writeImmediately = writeImmediately;
|
||||
this.decoderOut = CharBuffer.allocate(bufferSize);
|
||||
this.decoderIn = ByteBuffer.allocate(128);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(final byte[] b, int off, int len) throws IOException {
|
||||
while (len > 0) {
|
||||
final int c = Math.min(len, decoderIn.remaining());
|
||||
decoderIn.put(b, off, c);
|
||||
processInput(false);
|
||||
len -= c;
|
||||
off += c;
|
||||
}
|
||||
if (writeImmediately) flushOutput();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(final byte[] b) throws IOException {
|
||||
write(b, 0, b.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(final int b) throws IOException {
|
||||
write(new byte[]{(byte) b}, 0, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() throws IOException {
|
||||
flushOutput();
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
processInput(true);
|
||||
flushOutput();
|
||||
writer.close();
|
||||
}
|
||||
|
||||
private void processInput(final boolean endOfInput) throws IOException {
|
||||
decoderIn.flip();
|
||||
CoderResult coderResult;
|
||||
while (true) {
|
||||
coderResult = decoder.decode(decoderIn, decoderOut, endOfInput);
|
||||
if (!coderResult.isOverflow()) break;
|
||||
flushOutput();
|
||||
}
|
||||
if (!coderResult.isUnderflow()) {
|
||||
throw new IOException("Unexpected coder result");
|
||||
}
|
||||
|
||||
decoderIn.compact();
|
||||
}
|
||||
|
||||
private void flushOutput() throws IOException {
|
||||
if (decoderOut.position() > 0) {
|
||||
writer.write(decoderOut.array(), 0, decoderOut.position());
|
||||
decoderOut.rewind();
|
||||
}
|
||||
}
|
||||
}
|
@ -14,13 +14,12 @@ package org.dromara.hutool.core.util;
|
||||
|
||||
import org.dromara.hutool.core.io.CharsetDetector;
|
||||
import org.dromara.hutool.core.io.file.FileUtil;
|
||||
import org.dromara.hutool.core.lang.Assert;
|
||||
import org.dromara.hutool.core.text.StrUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.charset.UnsupportedCharsetException;
|
||||
import java.nio.charset.*;
|
||||
|
||||
/**
|
||||
* 字符集工具类
|
||||
@ -236,4 +235,36 @@ public class CharsetUtil {
|
||||
public static Charset detect(final int bufferSize, final InputStream in, final Charset... charsets) {
|
||||
return CharsetDetector.detect(bufferSize, in, charsets);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个新的CharsetEncoder实例,配置指定的字符集和错误处理策略。
|
||||
*
|
||||
* @param charset 指定的字符集,不允许为null。
|
||||
* @param action 对于不合法的字符或无法映射的字符的处理策略,不允许为null。
|
||||
* @return 配置好的CharsetEncoder实例。
|
||||
* @since 6.0.0
|
||||
*/
|
||||
public static CharsetEncoder newEncoder(final Charset charset, final CodingErrorAction action) {
|
||||
return Assert.notNull(charset)
|
||||
.newEncoder()
|
||||
.onMalformedInput(action)
|
||||
.onUnmappableCharacter(action);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个新的CharsetDecoder实例,配置指定的字符集和错误处理行为。
|
||||
*
|
||||
* @param charset 指定的字符集,不允许为null。
|
||||
* @param action 当遇到不合法的字符编码或不可映射字符时采取的行动,例如忽略、替换等。
|
||||
* @return 配置好的CharsetDecoder实例,用于解码字符。
|
||||
* @since 6.0.0
|
||||
*/
|
||||
public static CharsetDecoder newDecoder(final Charset charset, final CodingErrorAction action) {
|
||||
return Assert.notNull(charset)
|
||||
.newDecoder()
|
||||
.onMalformedInput(action)
|
||||
.onUnmappableCharacter(action)
|
||||
// 设置遇到无法解码的字符时的替换字符串。
|
||||
.replaceWith("?");
|
||||
}
|
||||
}
|
||||
|
@ -13,20 +13,17 @@
|
||||
package org.dromara.hutool.http.client.body;
|
||||
|
||||
import org.dromara.hutool.core.convert.Convert;
|
||||
import org.dromara.hutool.core.io.file.FileUtil;
|
||||
import org.dromara.hutool.core.io.IORuntimeException;
|
||||
import org.dromara.hutool.core.io.IoUtil;
|
||||
import org.dromara.hutool.core.io.resource.HttpResource;
|
||||
import org.dromara.hutool.core.io.resource.MultiResource;
|
||||
import org.dromara.hutool.core.io.resource.Resource;
|
||||
import org.dromara.hutool.core.io.resource.StringResource;
|
||||
import org.dromara.hutool.core.io.file.FileUtil;
|
||||
import org.dromara.hutool.core.io.resource.*;
|
||||
import org.dromara.hutool.core.text.StrUtil;
|
||||
import org.dromara.hutool.http.HttpGlobalConfig;
|
||||
import org.dromara.hutool.http.meta.ContentType;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.*;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
* Multipart/form-data输出流封装<br>
|
||||
@ -110,6 +107,16 @@ public class MultipartOutputStream extends OutputStream {
|
||||
|
||||
if (value instanceof Resource) {
|
||||
appendResource(formFieldName, (Resource) value);
|
||||
}else if(value instanceof File) {
|
||||
appendResource(formFieldName, new FileResource((File) value));
|
||||
}else if(value instanceof Path) {
|
||||
appendResource(formFieldName, new FileResource((Path) value));
|
||||
} else if(value instanceof byte[]) {
|
||||
appendResource(formFieldName, new BytesResource((byte[]) value));
|
||||
} else if(value instanceof InputStream) {
|
||||
appendResource(formFieldName, new InputStreamResource((InputStream) value));
|
||||
} else if(value instanceof Reader) {
|
||||
appendResource(formFieldName, new InputStreamResource((Reader) value, this.charset));
|
||||
} else {
|
||||
appendResource(formFieldName,
|
||||
new StringResource(Convert.toStr(value), null, this.charset));
|
||||
|
Loading…
Reference in New Issue
Block a user