diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/io/IoUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/io/IoUtil.java
index 1af33f6ca..b098e630f 100644
--- a/hutool-core/src/main/java/org/dromara/hutool/core/io/IoUtil.java
+++ b/hutool-core/src/main/java/org/dromara/hutool/core/io/IoUtil.java
@@ -19,6 +19,7 @@ package org.dromara.hutool.core.io;
import org.dromara.hutool.core.codec.binary.HexUtil;
import org.dromara.hutool.core.collection.iter.LineIter;
import org.dromara.hutool.core.exception.HutoolException;
+import org.dromara.hutool.core.func.SerConsumer;
import org.dromara.hutool.core.io.copy.FileChannelCopier;
import org.dromara.hutool.core.io.copy.ReaderWriterCopier;
import org.dromara.hutool.core.io.copy.StreamCopier;
@@ -26,38 +27,18 @@ import org.dromara.hutool.core.io.stream.FastByteArrayOutputStream;
import org.dromara.hutool.core.io.stream.StreamReader;
import org.dromara.hutool.core.io.stream.StreamWriter;
import org.dromara.hutool.core.lang.Assert;
-import org.dromara.hutool.core.func.SerConsumer;
import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.core.util.ByteUtil;
import org.dromara.hutool.core.util.CharsetUtil;
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.Closeable;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.Flushable;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.PushbackInputStream;
-import java.io.PushbackReader;
-import java.io.Reader;
-import java.io.UnsupportedEncodingException;
-import java.io.Writer;
+import java.io.*;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Objects;
+import java.util.function.Predicate;
/**
* IO工具类
@@ -513,6 +494,30 @@ public class IoUtil extends NioUtil {
}
}
+ /**
+ * 从流中读取内容,直到遇到给定token
+ *
+ * @param in 输入流
+ * @param token 停止的字符
+ * @return 输出流
+ * @throws IORuntimeException IO异常
+ */
+ public static FastByteArrayOutputStream readToToken(final InputStream in, final int token) throws IORuntimeException {
+ return readTo(in, (c) -> c == token);
+ }
+
+ /**
+ * 从流中读取内容,直到遇到给定token满足{@link Predicate#test(Object)}
+ *
+ * @param in 输入流
+ * @param predicate 读取结束条件, {@link Predicate#test(Object)}返回true表示结束
+ * @return 输出流
+ * @throws IORuntimeException IO异常
+ */
+ public static FastByteArrayOutputStream readTo(final InputStream in, final Predicate predicate) {
+ return StreamReader.of(in, false).readTo(predicate);
+ }
+
// endregion ----- read
// region ----- toStream
diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/io/buffer/FastByteBuffer.java b/hutool-core/src/main/java/org/dromara/hutool/core/io/buffer/FastByteBuffer.java
index 210ff2281..ab65f1eb1 100644
--- a/hutool-core/src/main/java/org/dromara/hutool/core/io/buffer/FastByteBuffer.java
+++ b/hutool-core/src/main/java/org/dromara/hutool/core/io/buffer/FastByteBuffer.java
@@ -17,6 +17,7 @@
package org.dromara.hutool.core.io.buffer;
import org.dromara.hutool.core.io.IoUtil;
+import org.dromara.hutool.core.lang.Assert;
/**
* 代码移植自blade
@@ -246,30 +247,6 @@ public class FastByteBuffer {
buffersCount = 0;
}
- /**
- * 返回快速缓冲中的数据
- *
- * @return 快速缓冲中的数据
- */
- public byte[] toArray() {
- int pos = 0;
- final byte[] array = new byte[size];
-
- if (currentBufferIndex == -1) {
- return array;
- }
-
- for (int i = 0; i < currentBufferIndex; i++) {
- final int len = buffers[i].length;
- System.arraycopy(buffers[i], 0, array, pos, len);
- pos += len;
- }
-
- System.arraycopy(buffers[currentBufferIndex], 0, array, pos, offset);
-
- return array;
- }
-
/**
* 返回快速缓冲中的数据,如果缓冲区中的数据长度固定,则直接返回原始数组
* 注意此方法共享数组,不能修改数组内容!
@@ -287,6 +264,15 @@ public class FastByteBuffer {
return toArray();
}
+ /**
+ * 返回快速缓冲中的数据
+ *
+ * @return 快速缓冲中的数据
+ */
+ public byte[] toArray() {
+ return toArray(0, this.size);
+ }
+
/**
* 返回快速缓冲中的数据
*
@@ -294,14 +280,19 @@ public class FastByteBuffer {
* @param len 逻辑字节长
* @return 快速缓冲中的数据
*/
- public byte[] toArray(int start, final int len) {
+ public byte[] toArray(int start, int len) {
+ Assert.isTrue(start >= 0, "Start must be greater than zero!");
+ Assert.isTrue(len >= 0, "Length must be greater than zero!");
+
+ if(start >= this.size || len == 0){
+ return new byte[0];
+ }
+ if(len > (this.size - start)){
+ len = this.size - start;
+ }
int remaining = len;
int pos = 0;
- final byte[] array = new byte[len];
-
- if (len == 0) {
- return array;
- }
+ final byte[] result = new byte[len];
int i = 0;
while (start >= buffers[i].length) {
@@ -310,18 +301,17 @@ public class FastByteBuffer {
}
while (i < buffersCount) {
- final byte[] buf = buffers[i];
- final int c = Math.min(buf.length - start, remaining);
- System.arraycopy(buf, start, array, pos, c);
- pos += c;
- remaining -= c;
+ final int bufLen = Math.min(buffers[i].length - start, remaining);
+ System.arraycopy(buffers[i], start, result, pos, bufLen);
+ pos += bufLen;
+ remaining -= bufLen;
if (remaining == 0) {
break;
}
start = 0;
i++;
}
- return array;
+ return result;
}
/**
diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/io/stream/FastByteArrayOutputStream.java b/hutool-core/src/main/java/org/dromara/hutool/core/io/stream/FastByteArrayOutputStream.java
index ee94c06dd..56bc559e3 100644
--- a/hutool-core/src/main/java/org/dromara/hutool/core/io/stream/FastByteArrayOutputStream.java
+++ b/hutool-core/src/main/java/org/dromara/hutool/core/io/stream/FastByteArrayOutputStream.java
@@ -145,6 +145,17 @@ public class FastByteArrayOutputStream extends OutputStream {
return buffer.toArray();
}
+ /**
+ * 转为Byte数组
+ *
+ * @param start 起始位置(包含)
+ * @param len 长度
+ * @return Byte数组
+ */
+ public byte[] toByteArray(final int start, final int len) {
+ return buffer.toArray(start, len);
+ }
+
/**
* 转为Byte数组,如果缓冲区中的数据长度固定,则直接返回原始数组
* 注意此方法共享数组,不能修改数组内容!
@@ -171,4 +182,12 @@ public class FastByteArrayOutputStream extends OutputStream {
ObjUtil.defaultIfNull(charset, CharsetUtil::defaultCharset));
}
+ /**
+ * 获取指定位置的字节
+ * @param index 位置
+ * @return 字节
+ */
+ public byte get(final int index){
+ return buffer.get(index);
+ }
}
diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/io/stream/LineInputStream.java b/hutool-core/src/main/java/org/dromara/hutool/core/io/stream/LineInputStream.java
new file mode 100644
index 000000000..ffd33a02c
--- /dev/null
+++ b/hutool-core/src/main/java/org/dromara/hutool/core/io/stream/LineInputStream.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) 2024 Hutool Team and hutool.cn
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.dromara.hutool.core.io.stream;
+
+import org.dromara.hutool.core.collection.iter.ComputeIter;
+import org.dromara.hutool.core.io.IORuntimeException;
+import org.dromara.hutool.core.io.buffer.FastByteBuffer;
+import org.dromara.hutool.core.text.CharUtil;
+import org.dromara.hutool.core.text.StrUtil;
+import org.dromara.hutool.core.util.ObjUtil;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.Charset;
+import java.util.Iterator;
+
+/**
+ * 行读取器,类似于BufferedInputStream,支持多行转义,规则如下:
+ *
+ * - 支持'\n'和'\r\n'两种换行符,不支持'\r'换行符
+ * - 如果想读取转义符,必须定义为'\\'
+ * - 多行转义后的换行符和空格都会被忽略
+ *
+ *
+ * 例子:
+ *
+ * a=1\
+ * 2
+ *
+ * 读出后就是{@code a=12}
+ *
+ * @author looly
+ * @since 6.0.0
+ */
+public class LineInputStream extends FilterInputStream implements Iterable {
+
+ /**
+ * 构造
+ *
+ * @param in 输入流
+ */
+ public LineInputStream(final InputStream in) {
+ super(in);
+ }
+
+ /**
+ * 读取一行
+ *
+ * @param charset 编码
+ * @return 行
+ * @throws IORuntimeException IO异常
+ */
+ public String readLine(final Charset charset) throws IORuntimeException {
+ return StrUtil.str(readLine(), charset);
+ }
+
+ /**
+ * 读取一行
+ *
+ * @return 内容
+ * @throws IORuntimeException IO异常
+ */
+ public byte[] readLine() throws IORuntimeException {
+ try {
+ return _readLine();
+ } catch (final IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public Iterator iterator() {
+ return new ComputeIter() {
+ @Override
+ protected byte[] computeNext() {
+ return readLine();
+ }
+ };
+ }
+
+ /**
+ * 读取一行
+ *
+ * @return 内容
+ * @throws IOException IO异常
+ */
+ private byte[] _readLine() throws IOException {
+ FastByteBuffer out = null;
+ // 换行符前是否为转义符
+ boolean precedingBackslash = false;
+ int c;
+ while ((c = read()) > 0) {
+ if(null == out){
+ out = new FastByteBuffer();
+ }
+ if (CharUtil.BACKSLASH == c) {
+ // 转义符转义,行尾需要使用'\'时,使用转义符转义,即`\\`
+ if (!precedingBackslash) {
+ // 转义符,添加标识,但是不加入字符
+ precedingBackslash = true;
+ continue;
+ } else {
+ precedingBackslash = false;
+ }
+ } else {
+ if (precedingBackslash) {
+ // 转义模式下,跳过转义符后的所有空白符
+ if (CharUtil.isBlankChar(c)) {
+ continue;
+ }
+ // 遇到普通字符,关闭转义
+ precedingBackslash = false;
+ } else if (CharUtil.LF == c) {
+ // 非转义状态下,表示行的结束
+ // 如果换行符是`\r\n`,删除末尾的`\r`
+ final int lastIndex = out.size() - 1;
+ if (lastIndex >= 0 && CharUtil.CR == out.get(lastIndex)) {
+ return out.toArray(0, lastIndex);
+ }
+ break;
+ }
+ }
+
+ out.append((byte) c);
+ }
+
+ return ObjUtil.apply(out, FastByteBuffer::toArray);
+ }
+}
diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/io/LineInputStreamTest.java b/hutool-core/src/test/java/org/dromara/hutool/core/io/LineInputStreamTest.java
new file mode 100644
index 000000000..caae2e174
--- /dev/null
+++ b/hutool-core/src/test/java/org/dromara/hutool/core/io/LineInputStreamTest.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2024 Hutool Team and hutool.cn
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.dromara.hutool.core.io;
+
+import org.dromara.hutool.core.io.stream.LineInputStream;
+import org.dromara.hutool.core.util.CharsetUtil;
+import org.junit.jupiter.api.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class LineInputStreamTest {
+ @Test
+ public void LineInputStream_ValidInput_ShouldReadLinesCorrectly() {
+ final String data = "first line\nsecond line\nthird line\n";
+ final InputStream in = new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8));
+ final LineInputStream lineInputStream = new LineInputStream(in);
+ assertEquals("first line", lineInputStream.readLine(CharsetUtil.UTF_8));
+ assertEquals("second line", lineInputStream.readLine(CharsetUtil.UTF_8));
+ assertEquals("third line", lineInputStream.readLine(CharsetUtil.UTF_8));
+ assertNull(lineInputStream.readLine()); // No more lines
+ }
+
+ @Test
+ public void LineInputStream_EmptyInput_ShouldReturnNull() {
+ final String emptyData = "";
+ final InputStream emptyIn = new ByteArrayInputStream(emptyData.getBytes(StandardCharsets.UTF_8));
+ final LineInputStream lineInputStream = new LineInputStream(emptyIn);
+ assertNull(lineInputStream.readLine()); // No lines in input
+ }
+
+ @Test
+ public void LineInputStream_NoNewLineAtEnd_ShouldHandleLastLine() {
+ final String noNewLineData = "first line\n第二 line\nthird 行";
+ final InputStream noNewLineReader = new ByteArrayInputStream(noNewLineData.getBytes(StandardCharsets.UTF_8));
+ final LineInputStream lineInputStream = new LineInputStream(noNewLineReader);
+ assertEquals("first line", lineInputStream.readLine(CharsetUtil.UTF_8));
+ assertEquals("第二 line", lineInputStream.readLine(CharsetUtil.UTF_8));
+ assertEquals("third 行", lineInputStream.readLine(CharsetUtil.UTF_8));
+ assertNull(lineInputStream.readLine()); // No more lines
+ }
+}
+
diff --git a/hutool-core/src/test/resources/issueI5DO8E.xml b/hutool-core/src/test/resources/issueI5DO8E.xml
index e9436fdd7..e9d5227c5 100644
--- a/hutool-core/src/test/resources/issueI5DO8E.xml
+++ b/hutool-core/src/test/resources/issueI5DO8E.xml
@@ -1,21 +1,5 @@
-
-
diff --git a/hutool-core/src/test/resources/multi_line.properties b/hutool-core/src/test/resources/multi_line.properties
index 3c90d8b45..432d0997a 100755
--- a/hutool-core/src/test/resources/multi_line.properties
+++ b/hutool-core/src/test/resources/multi_line.properties
@@ -1,19 +1,3 @@
-#
-# Copyright (c) 2024 Hutool Team and hutool.cn
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
test1
test2=a\
bc\
diff --git a/hutool-core/src/test/resources/multi_line_crlf.properties b/hutool-core/src/test/resources/multi_line_crlf.properties
index c4b45ee7f..12b8b1c9a 100755
--- a/hutool-core/src/test/resources/multi_line_crlf.properties
+++ b/hutool-core/src/test/resources/multi_line_crlf.properties
@@ -1,19 +1,3 @@
-#
-# Copyright (c) 2024 Hutool Team and hutool.cn
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
test1
test2=a\
bc\
diff --git a/hutool-core/src/test/resources/test.properties b/hutool-core/src/test/resources/test.properties
index 58a11d797..a446e547f 100644
--- a/hutool-core/src/test/resources/test.properties
+++ b/hutool-core/src/test/resources/test.properties
@@ -1,19 +1,3 @@
-#
-# Copyright (c) 2024 Hutool Team and hutool.cn
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
#--------------------------------------------
# 配置文件测试
#--------------------------------------------
diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/client/body/MultipartBody.java b/hutool-http/src/main/java/org/dromara/hutool/http/client/body/MultipartBody.java
index 44c69e8df..aa0f112f2 100644
--- a/hutool-http/src/main/java/org/dromara/hutool/http/client/body/MultipartBody.java
+++ b/hutool-http/src/main/java/org/dromara/hutool/http/client/body/MultipartBody.java
@@ -28,7 +28,7 @@ import java.util.Map;
/**
* Multipart/form-data数据的请求体封装
- * 遵循RFC2388规范
+ * 遵循RFC2387规范,见:https://www.rfc-editor.org/rfc/rfc2387
*
* @author looly
* @since 5.3.5
diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/client/body/MultipartOutputStream.java b/hutool-http/src/main/java/org/dromara/hutool/http/client/body/MultipartOutputStream.java
index ba58c3cd7..4062cc3b4 100644
--- a/hutool-http/src/main/java/org/dromara/hutool/http/client/body/MultipartOutputStream.java
+++ b/hutool-http/src/main/java/org/dromara/hutool/http/client/body/MultipartOutputStream.java
@@ -31,7 +31,7 @@ import java.nio.file.Path;
/**
* Multipart/form-data输出流封装
- * 遵循RFC2388规范
+ * 遵循RFC2387规范,见:https://www.rfc-editor.org/rfc/rfc2387
*
* @author looly
* @since 5.7.17
@@ -97,6 +97,7 @@ public class MultipartOutputStream extends OutputStream {
* @return this
* @throws IORuntimeException IO异常
*/
+ @SuppressWarnings("resource")
public MultipartOutputStream write(final String formFieldName, final Object value) throws IORuntimeException {
// 多资源
if (value instanceof MultiResource) {