mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-05-09 23:51:34 +08:00
add LineInputStream
This commit is contained in:
parent
1fa1b5f173
commit
e1648198e4
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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">
|
||||
|
@ -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\
|
||||
|
@ -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\
|
||||
|
@ -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.
|
||||
#
|
||||
|
||||
#--------------------------------------------
|
||||
# 配置文件测试
|
||||
#--------------------------------------------
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user