This commit is contained in:
Looly
2026-02-03 13:28:16 +08:00
parent 8f8477d632
commit c1ed9fe040
3 changed files with 471 additions and 0 deletions

View File

@@ -0,0 +1,157 @@
/*
* Copyright (c) 2026 Hutool Team.
*
* 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 cn.hutool.v7.core.io;
import cn.hutool.v7.core.text.StrUtil;
import java.io.IOException;
import java.io.Reader;
/**
* 基于{@link char[]}的{@link Reader}实现,用于支持{@link char[]}的读取<br>
* 相比jdk的{@link java.io.CharArrayReader}非线程安全,速度更快。
*
* @author Looly
* @since 7.0.0
*/
public class FastCharArrayReader extends Reader {
protected char[] buf;
protected int pos;
protected int markedPos = 0;
protected int count;
/**
* 构造
*
* @param str 字符串
*/
public FastCharArrayReader(final CharSequence str){
this(StrUtil.toCharArray(str));
}
/**
* 构造
*
* @param buf 字符数组
*/
public FastCharArrayReader(final char[] buf) {
this.buf = buf;
this.pos = 0;
this.count = buf.length;
}
/**
* 构造
*
* @param buf 字符数组
* @param offset 起始位置
* @param length 长度
*/
public FastCharArrayReader(final char[] buf, final int offset, final int length) {
if ((offset < 0) || (offset > buf.length) || (length < 0) || ((offset + length) < 0)) {
throw new IllegalArgumentException();
}
this.buf = buf;
this.pos = offset;
this.count = Math.min(offset + length, buf.length);
this.markedPos = offset;
}
@Override
public int read() throws IOException {
ensureOpen();
if (pos >= count)
return -1;
else
return buf[pos++];
}
@Override
public int read(final char[] b, final int off, int len) throws IOException {
ensureOpen();
if ((off < 0) || (off > b.length) || (len < 0) || ((off + len) > b.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}
if (pos >= count) {
return -1;
}
if (pos + len > count) {
len = count - pos;
}
if (len <= 0) {
return 0;
}
System.arraycopy(buf, pos, b, off, len);
pos += len;
return len;
}
@Override
public long skip(long n) throws IOException {
ensureOpen();
if (pos + n > count) {
n = count - pos;
}
if (n < 0) {
return 0;
}
pos += n;
return n;
}
@Override
public boolean ready() throws IOException {
ensureOpen();
return (count - pos) > 0;
}
@Override
public boolean markSupported() {
return true;
}
@Override
public void mark(final int readAheadLimit) throws IOException {
ensureOpen();
markedPos = pos;
}
@Override
public void reset() throws IOException {
ensureOpen();
pos = markedPos;
}
@Override
public void close() {
buf = null;
}
/**
* 确保流打开
*
* @throws IOException 流关闭
*/
private void ensureOpen() throws IOException {
if (buf == null) {
throw new IOException("Stream closed");
}
}
}

View File

@@ -4242,6 +4242,45 @@ public class CharSequenceUtil extends StrValidator {
return (isCodePoint ? str.codePoints() : str.chars()).toArray();
}
/**
* 将字符串转换为字符数组
*
* @param cs 字符串
* @return 字符数组
* @since 7.0.0
*/
public static char[] toCharArray(final CharSequence cs) {
if (cs == null) {
return null;
}
final int length = cs.length();
if (length == 0) {
return new char[0];
}
// 额外优化:优先处理 String 实现类,使用原生 toCharArray() 更高效
if (cs instanceof String) {
return ((String) cs).toCharArray();
}
// 其他 CharSequence 实现类,采用手动遍历拷贝
final char[] resultArray = new char[length];
if (cs instanceof StringBuilder) {
// 复制全部字符从源索引0到length写入目标数组索引0开始
((StringBuilder) cs).getChars(0, length, resultArray, 0);
} else if (cs instanceof StringBuffer) {
// 复制全部字符从源索引0到length写入目标数组索引0开始
((StringBuffer) cs).getChars(0, length, resultArray, 0);
} else {
for (int i = 0; i < length; i++) {
resultArray[i] = cs.charAt(i);
}
}
return resultArray;
}
/**
* 遍历字符串的每个字符,并处理
*

View File

@@ -0,0 +1,275 @@
/*
* Copyright (c) 2026 Hutool Team.
*
* 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 cn.hutool.v7.core.io;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.DisplayName;
import java.util.Arrays;
import static org.junit.jupiter.api.Assertions.*;
/**
* FastCharArrayReader Unit Test Class
* 快速字符数组读取器单元测试类
*/
public class FastCharArrayReaderTest {
private char[] testArray;
private FastCharArrayReader reader;
@BeforeEach
void setUp() {
// Initialize test data before each test method
// 在每个测试方法前初始化测试数据
testArray = "Hello World".toCharArray();
reader = new FastCharArrayReader(testArray);
}
@Test
@DisplayName("Test constructor with valid character array")
void testConstructorWithValidArray() {
// Test normal construction with valid character array
// 测试使用有效字符数组的正常构造
assertNotNull(reader, "Reader should be created successfully");
// Create another instance to verify different arrays work
// 创建另一个实例验证不同数组都能工作
final char[] anotherArray = {'A', 'B', 'C'};
final FastCharArrayReader anotherReader = new FastCharArrayReader(anotherArray);
assertNotNull(anotherReader, "Another reader should be created successfully");
}
@Test
@DisplayName("Test constructor with empty array")
void testConstructorWithEmptyArray() {
// Test construction with empty array
// 测试空数组构造
final char[] emptyArray = {};
final FastCharArrayReader emptyReader = new FastCharArrayReader(emptyArray);
assertNotNull(emptyReader, "Reader with empty array should be created successfully");
}
@SuppressWarnings({"resource", "DataFlowIssue"})
@Test
@DisplayName("Test constructor with null array - should handle appropriately")
void testConstructorWithNullArray() {
// Test construction with null array - expect exception or proper handling
// 测试null数组构造 - 期望抛出异常或正确处理
assertThrows(NullPointerException.class, () -> {
new FastCharArrayReader((char[]) null);
}, "Constructor should throw NullPointerException for null input");
}
@Test
@DisplayName("Test single character reading functionality")
void testReadSingleCharacter() throws Exception {
// Test reading characters one by one
// 测试逐个读取字符
assertEquals('H', reader.read(), "First character should be 'H'");
assertEquals('e', reader.read(), "Second character should be 'e'");
assertEquals('l', reader.read(), "Third character should be 'l'");
assertEquals('l', reader.read(), "Fourth character should be 'l'");
assertEquals('o', reader.read(), "Fifth character should be 'o'");
}
@Test
@DisplayName("Test reading until end of stream")
void testReadUntilEnd() throws Exception {
// Read all characters and verify EOF (-1) is returned at the end
// 读取所有字符并验证在末尾返回EOF(-1)
for (int i = 0; i < testArray.length; i++) {
final int result = reader.read();
assertEquals(testArray[i], (char) result,
String.format("Character at position %d should match", i));
}
// After all characters are read, should return -1
// 所有字符读取完后应该返回-1
assertEquals(-1, reader.read(), "Should return -1 after reaching end of stream");
}
@Test
@DisplayName("Test batch reading with char array")
void testBatchReading() throws Exception {
// Test reading multiple characters into a buffer
// 测试将多个字符读入缓冲区
final char[] buffer = new char[5];
final int charsRead = reader.read(buffer, 0, buffer.length);
assertEquals(5, charsRead, "Should read 5 characters");
assertArrayEquals(new char[]{'H', 'e', 'l', 'l', 'o'},
buffer, "Buffer should contain first 5 characters");
}
@SuppressWarnings("ResultOfMethodCallIgnored")
@Test
@DisplayName("Test partial batch reading")
void testPartialBatchReading() throws Exception {
// Test reading when remaining characters are less than requested
// 测试剩余字符少于请求数量时的读取
// First, advance the reader position
for (int i = 0; i < 8; i++) {
reader.read(); // Skip first 8 characters
}
final char[] buffer = new char[5]; // Request 5 more but only 3 remain
final int charsRead = reader.read(buffer, 0, buffer.length);
assertEquals(3, charsRead, "Should read only 3 remaining characters");
assertArrayEquals(new char[]{'r', 'l', 'd', '\u0000', '\u0000'},
buffer, "Buffer should contain last 3 characters and null padding");
}
@Test
@DisplayName("Test reading with offset and length parameters")
void testReadingWithOffsetAndLength() throws Exception {
// Test reading with specific offset and length in buffer
// 测试带偏移量和长度参数的读取
final char[] buffer = new char[10];
// Fill buffer with initial values to verify they're overwritten correctly
// 用初始值填充缓冲区以验证它们被正确覆盖
Arrays.fill(buffer, 'X');
final int charsRead = reader.read(buffer, 2, 5); // Start at index 2, read 5 chars
assertEquals(5, charsRead, "Should read 5 characters");
assertEquals('X', buffer[0], "Position 0 should remain unchanged");
assertEquals('X', buffer[1], "Position 1 should remain unchanged");
assertEquals('H', buffer[2], "Position 2 should have first character");
assertEquals('o', buffer[6], "Position 6 should have fifth character");
assertEquals('X', buffer[7], "Position 7 should remain unchanged");
}
@Test
@DisplayName("Test boundary conditions for batch reading")
void testBatchReadingBoundaryConditions() throws Exception {
// Test various boundary conditions for batch reading
// 测试批量读取的各种边界条件
final char[] buffer = new char[testArray.length + 5]; // Larger buffer
final int charsRead = reader.read(buffer, 0, buffer.length);
assertEquals(testArray.length, charsRead,
"Should read exactly the number of available characters");
}
@Test
@DisplayName("Test invalid parameters for batch reading")
void testInvalidParametersForBatchReading() throws Exception {
// Test invalid parameters that should throw exceptions
// 测试应该抛出异常的无效参数
final char[] buffer = new char[5];
// Test negative offset
// 测试负偏移量
assertThrows(IndexOutOfBoundsException.class, () -> {
reader.read(buffer, -1, 3);
});
// Test negative length
// 测试负长度
assertThrows(IndexOutOfBoundsException.class, () -> {
reader.read(buffer, 0, -1);
});
// Test offset + length exceeding buffer size
// 测试偏移量+长度超出缓冲区大小
assertThrows(IndexOutOfBoundsException.class, () -> {
reader.read(buffer, 3, 10); // 3 + 10 > 5
});
}
@Test
@DisplayName("Test skip functionality")
void testSkipFunctionality() throws Exception {
// Test skipping characters
// 测试跳过字符功能
final long skipped = reader.skip(3);
assertEquals(3, skipped, "Should skip 3 characters");
assertEquals('l', reader.read(), "Next character after skipping 3 should be 'l' (position 4)");
}
@Test
@DisplayName("Test skip beyond available characters")
void testSkipBeyondAvailableCharacters() throws Exception {
// Test skipping more characters than available
// 测试跳过超过可用字符数的字符
final long skipped = reader.skip(100);
assertEquals(testArray.length, skipped,
"Should skip only as many characters as available");
assertEquals(-1, reader.read(), "Stream should be at end after skipping all");
}
@SuppressWarnings({"ResultOfMethodCallIgnored", "ConstantValue"})
@Test
@DisplayName("Test mark and reset functionality if supported")
void testMarkAndReset() throws Exception {
// Test mark/reset functionality if implemented
// 如果实现了则测试标记/重置功能
try {
// Check if mark is supported
// 检查是否支持标记
if (reader.markSupported()) {
reader.mark(0); // Mark current position
// Read some characters
// 读取一些字符
reader.read(); reader.read(); reader.read();
reader.reset(); // Reset to marked position
// Should be back at beginning
// 应该回到开始位置
assertEquals('H', reader.read(), "After reset should return to marked position");
} else {
// If not supported, verify appropriate behavior
// 如果不支持,验证适当行为
assertFalse(reader.markSupported(), "Mark should not be supported if not implemented");
}
} catch (final Exception e) {
// Handle cases where mark/reset might not be implemented
// 处理未实现标记/重置的情况
System.out.println("Mark/Reset functionality may not be implemented: " + e.getMessage());
}
}
@Test
@DisplayName("Test close functionality")
void testCloseFunctionality() throws Exception {
// Test closing the reader
// 测试关闭读取器
reader.close();
// After closing, further operations might throw exceptions
// 关闭后,进一步操作可能会抛出异常
// This depends on implementation - some readers throw IOException after close
// 这取决于实现 - 一些读取器在关闭后会抛出IOException
}
@Test
@DisplayName("Test concurrent access safety if applicable")
void testConcurrentAccessSafety() {
// If the implementation claims to be thread-safe, test concurrent access
// 如果实现声称是线程安全的,则测试并发访问
// For now, just document that this would be tested if thread-safety was claimed
// 目前,如果声明了线程安全性则会测试
// Thread safety testing would involve multiple threads accessing the same instance
// 线程安全性测试将涉及多线程访问同一实例
}
}