add LineInputStream

This commit is contained in:
Looly 2024-09-08 01:13:00 +08:00
parent 1fa1b5f173
commit e1648198e4
11 changed files with 279 additions and 124 deletions

View File

@ -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工具类<br>
@ -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<Integer> predicate) {
return StreamReader.of(in, false).readTo(predicate);
}
// endregion ----- read
// region ----- toStream

View File

@ -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;
/**
* 代码移植自<a href="https://github.com/biezhi/blade">blade</a><br>
@ -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;
}
/**
* 返回快速缓冲中的数据如果缓冲区中的数据长度固定则直接返回原始数组<br>
* 注意此方法共享数组不能修改数组内容
@ -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;
}
/**

View File

@ -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数组如果缓冲区中的数据长度固定则直接返回原始数组<br>
* 注意此方法共享数组不能修改数组内容
@ -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);
}
}

View File

@ -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支持多行转义规则如下<br>
* <ul>
* <li>支持'\n''\r\n'两种换行符不支持'\r'换行符</li>
* <li>如果想读取转义符必须定义为'\\'</li>
* <li>多行转义后的换行符和空格都会被忽略</li>
* </ul>
* <p>
* 例子
* <pre>
* a=1\
* 2
* </pre>
* 读出后就是{@code a=12}
*
* @author looly
* @since 6.0.0
*/
public class LineInputStream extends FilterInputStream implements Iterable<byte[]> {
/**
* 构造
*
* @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<byte[]> iterator() {
return new ComputeIter<byte[]>() {
@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);
}
}

View File

@ -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
}
}

View File

@ -1,21 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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 xmlns="http://www.idpf.org/2007/opf"
xmlns:dc="http://purl.org/dc/elements/1.1/"
unique-identifier="bookId" version="2.0">

View File

@ -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\

View File

@ -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\

View File

@ -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.
#
#--------------------------------------------
# 配置文件测试
#--------------------------------------------

View File

@ -28,7 +28,7 @@ import java.util.Map;
/**
* Multipart/form-data数据的请求体封装<br>
* 遵循RFC2388规范
* 遵循RFC2387规范https://www.rfc-editor.org/rfc/rfc2387
*
* @author looly
* @since 5.3.5

View File

@ -31,7 +31,7 @@ import java.nio.file.Path;
/**
* Multipart/form-data输出流封装<br>
* 遵循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) {