diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/io/buffer/FastBuffer.java b/hutool-core/src/main/java/org/dromara/hutool/core/io/buffer/FastBuffer.java
new file mode 100644
index 000000000..d9fbe4e9c
--- /dev/null
+++ b/hutool-core/src/main/java/org/dromara/hutool/core/io/buffer/FastBuffer.java
@@ -0,0 +1,115 @@
+/*
+ * 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.buffer;
+
+import org.dromara.hutool.core.io.IoUtil;
+
+/**
+ * 快速缓冲抽象类,用于快速读取、写入数据到缓冲区,减少内存复制
+ * 相对于普通Buffer,使用二维数组扩展长度,减少内存复制,提升性能
+ *
+ * @author Looly
+ */
+public abstract class FastBuffer {
+
+ /**
+ * 一个缓冲区的最小字节数
+ */
+ protected final int minChunkLen;
+
+ /**
+ * 缓冲数
+ */
+ protected int buffersCount;
+ /**
+ * 当前缓冲索引
+ */
+ protected int currentBufferIndex = -1;
+ /**
+ * 当前缓冲偏移量
+ */
+ protected int offset;
+ /**
+ * 缓冲字节数
+ */
+ protected int size;
+
+ /**
+ * 构造
+ *
+ * @param size 一个缓冲区的最小字节数
+ */
+ public FastBuffer(int size) {
+ if (size <= 0) {
+ size = IoUtil.DEFAULT_BUFFER_SIZE;
+ }
+ this.minChunkLen = Math.abs(size);
+ }
+
+ /**
+ * 当前缓冲位于缓冲区的索引位
+ *
+ * @return {@link #currentBufferIndex}
+ */
+ public int index() {
+ return currentBufferIndex;
+ }
+
+ /**
+ * 获取当前缓冲偏移量
+ *
+ * @return 当前缓冲偏移量
+ */
+ public int offset() {
+ return offset;
+ }
+
+ /**
+ * 复位缓冲
+ */
+ public void reset() {
+ size = 0;
+ offset = 0;
+ currentBufferIndex = -1;
+ buffersCount = 0;
+ }
+
+ /**
+ * 获取缓冲总长度
+ *
+ * @return 缓冲总长度
+ */
+ public int length() {
+ return size;
+ }
+
+ /**
+ * 是否为空
+ *
+ * @return 是否为空
+ */
+ public boolean isEmpty() {
+ return size == 0;
+ }
+
+ /**
+ * 分配下一个缓冲区,不会小于1024
+ *
+ * @param newSize 理想缓冲区字节数
+ */
+ abstract protected void needNewBuffer(final int newSize);
+}
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 ab65f1eb1..6e488f516 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
@@ -26,37 +26,16 @@ import org.dromara.hutool.core.lang.Assert;
* @author biezhi, looly
* @since 1.0
*/
-public class FastByteBuffer {
+public class FastByteBuffer extends FastBuffer {
/**
* 缓冲集
*/
private byte[][] buffers = new byte[16][];
- /**
- * 缓冲数
- */
- private int buffersCount;
- /**
- * 当前缓冲索引
- */
- private int currentBufferIndex = -1;
/**
* 当前缓冲
*/
private byte[] currentBuffer;
- /**
- * 当前缓冲偏移量
- */
- private int offset;
- /**
- * 缓冲字节数
- */
- private int size;
-
- /**
- * 一个缓冲区的最小字节数
- */
- private final int minChunkLen;
/**
* 构造
@@ -70,35 +49,8 @@ public class FastByteBuffer {
*
* @param size 一个缓冲区的最小字节数
*/
- public FastByteBuffer(int size) {
- if (size <= 0) {
- size = IoUtil.DEFAULT_BUFFER_SIZE;
- }
- this.minChunkLen = Math.abs(size);
- }
-
- /**
- * 分配下一个缓冲区,不会小于1024
- *
- * @param newSize 理想缓冲区字节数
- */
- private void needNewBuffer(final int newSize) {
- final int delta = newSize - size;
- final int newBufferSize = Math.max(minChunkLen, delta);
-
- currentBufferIndex++;
- currentBuffer = new byte[newBufferSize];
- offset = 0;
-
- // add buffer
- if (currentBufferIndex >= buffers.length) {
- final int newLen = buffers.length << 1;
- final byte[][] newBuffers = new byte[newLen][];
- System.arraycopy(buffers, 0, newBuffers, 0, buffers.length);
- buffers = newBuffers;
- }
- buffers[currentBufferIndex] = currentBuffer;
- buffersCount++;
+ public FastByteBuffer(final int size) {
+ super(size);
}
/**
@@ -190,42 +142,6 @@ public class FastByteBuffer {
return this;
}
- /**
- * 长度
- *
- * @return 长度
- */
- public int size() {
- return size;
- }
-
- /**
- * 是否为空
- *
- * @return 是否为空
- */
- public boolean isEmpty() {
- return size == 0;
- }
-
- /**
- * 当前缓冲位于缓冲区的索引位
- *
- * @return {@link #currentBufferIndex}
- */
- public int index() {
- return currentBufferIndex;
- }
-
- /**
- * 获取当前缓冲偏移量
- *
- * @return 当前缓冲偏移量
- */
- public int offset() {
- return offset;
- }
-
/**
* 根据索引位返回缓冲集中的缓冲
*
@@ -236,15 +152,10 @@ public class FastByteBuffer {
return buffers[index];
}
- /**
- * 复位缓冲
- */
+ @Override
public void reset() {
- size = 0;
- offset = 0;
- currentBufferIndex = -1;
+ super.reset();
currentBuffer = null;
- buffersCount = 0;
}
/**
@@ -254,9 +165,9 @@ public class FastByteBuffer {
* @return 快速缓冲中的数据
*/
public byte[] toArrayZeroCopyIfPossible() {
- if(1 == currentBufferIndex){
+ if (1 == currentBufferIndex) {
final int len = buffers[0].length;
- if(len == size){
+ if (len == size) {
return buffers[0];
}
}
@@ -284,10 +195,10 @@ public class FastByteBuffer {
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){
+ if (start >= this.size || len == 0) {
return new byte[0];
}
- if(len > (this.size - start)){
+ if (len > (this.size - start)) {
len = this.size - start;
}
int remaining = len;
@@ -301,8 +212,9 @@ public class FastByteBuffer {
}
while (i < buffersCount) {
- final int bufLen = Math.min(buffers[i].length - start, remaining);
- System.arraycopy(buffers[i], start, result, pos, bufLen);
+ final byte[] buf = buffers[i];
+ final int bufLen = Math.min(buf.length - start, remaining);
+ System.arraycopy(buf, start, result, pos, bufLen);
pos += bufLen;
remaining -= bufLen;
if (remaining == 0) {
@@ -335,4 +247,23 @@ public class FastByteBuffer {
}
}
+ @Override
+ protected void needNewBuffer(final int newSize) {
+ final int delta = newSize - size;
+ final int newBufferSize = Math.max(minChunkLen, delta);
+
+ currentBufferIndex++;
+ currentBuffer = new byte[newBufferSize];
+ offset = 0;
+
+ // add buffer
+ if (currentBufferIndex >= buffers.length) {
+ final int newLen = buffers.length << 1;
+ final byte[][] newBuffers = new byte[newLen][];
+ System.arraycopy(buffers, 0, newBuffers, 0, buffers.length);
+ buffers = newBuffers;
+ }
+ buffers[currentBufferIndex] = currentBuffer;
+ buffersCount++;
+ }
}
diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/io/buffer/FastCharBuffer.java b/hutool-core/src/main/java/org/dromara/hutool/core/io/buffer/FastCharBuffer.java
new file mode 100644
index 000000000..282ff7724
--- /dev/null
+++ b/hutool-core/src/main/java/org/dromara/hutool/core/io/buffer/FastCharBuffer.java
@@ -0,0 +1,346 @@
+/*
+ * Copyright (c) 2013-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.buffer;
+
+import org.dromara.hutool.core.io.IoUtil;
+import org.dromara.hutool.core.lang.Assert;
+
+/**
+ * 代码移植自jetbrick
+ * 快速字符缓冲,将数据存放在缓冲集中,取代以往的单一数组
+ *
+ * @author jetbrick, looly
+ */
+public class FastCharBuffer extends FastBuffer implements CharSequence, Appendable {
+
+ /**
+ * 缓冲集
+ */
+ private char[][] buffers = new char[16][];
+ /**
+ * 当前缓冲
+ */
+ private char[] currentBuffer;
+
+ /**
+ * 构造
+ */
+ public FastCharBuffer() {
+ this(IoUtil.DEFAULT_BUFFER_SIZE);
+ }
+
+ /**
+ * 构造
+ *
+ * @param size 一个缓冲区的最小字节数
+ */
+ public FastCharBuffer(final int size) {
+ super(size);
+ }
+
+ /**
+ * 向快速缓冲加入数据
+ *
+ * @param array 数据
+ * @param off 偏移量
+ * @param len 字节数
+ * @return 快速缓冲自身 @see FastByteBuffer
+ */
+ public FastCharBuffer append(final char[] array, final int off, final int len) {
+ final int end = off + len;
+ if ((off < 0) || (len < 0) || (end > array.length)) {
+ throw new IndexOutOfBoundsException();
+ }
+ if (len == 0) {
+ return this;
+ }
+ final int newSize = size + len;
+ int remaining = len;
+
+ if (currentBuffer != null) {
+ // first try to fill current buffer
+ final int part = Math.min(remaining, currentBuffer.length - offset);
+ System.arraycopy(array, end - remaining, currentBuffer, offset, part);
+ remaining -= part;
+ offset += part;
+ size += part;
+ }
+
+ if (remaining > 0) {
+ // still some data left
+ // ask for new buffer
+ needNewBuffer(newSize);
+
+ // then copy remaining
+ // but this time we are sure that it will fit
+ final int part = Math.min(remaining, currentBuffer.length - offset);
+ System.arraycopy(array, end - remaining, currentBuffer, offset, part);
+ offset += part;
+ size += part;
+ }
+
+ return this;
+ }
+
+ /**
+ * 向快速缓冲加入数据
+ *
+ * @param array 数据
+ * @return 快速缓冲自身 @see FastByteBuffer
+ */
+ public FastCharBuffer append(final char[] array) {
+ return append(array, 0, array.length);
+ }
+
+ /**
+ * 向快速缓冲加入一个字节
+ *
+ * @param element 一个字节的数据
+ * @return 快速缓冲自身 @see FastByteBuffer
+ */
+ public FastCharBuffer append(final char element) {
+ if ((currentBuffer == null) || (offset == currentBuffer.length)) {
+ needNewBuffer(size + 1);
+ }
+
+ currentBuffer[offset] = element;
+ offset++;
+ size++;
+
+ return this;
+ }
+
+ /**
+ * 将另一个快速缓冲加入到自身
+ *
+ * @param buff 快速缓冲
+ * @return 快速缓冲自身 @see FastByteBuffer
+ */
+ public FastCharBuffer append(final FastCharBuffer buff) {
+ if (buff.size == 0) {
+ return this;
+ }
+ for (int i = 0; i < buff.currentBufferIndex; i++) {
+ append(buff.buffers[i]);
+ }
+ append(buff.currentBuffer, 0, buff.offset);
+ return this;
+ }
+
+ /**
+ * 根据索引位返回缓冲集中的缓冲
+ *
+ * @param index 索引位
+ * @return 缓冲
+ */
+ public char[] array(final int index) {
+ return buffers[index];
+ }
+
+ @Override
+ public void reset() {
+ super.reset();
+ currentBuffer = null;
+ }
+
+ /**
+ * 返回快速缓冲中的数据,如果缓冲区中的数据长度固定,则直接返回原始数组
+ * 注意此方法共享数组,不能修改数组内容!
+ *
+ * @return 快速缓冲中的数据
+ */
+ public char[] toArrayZeroCopyIfPossible() {
+ if (1 == currentBufferIndex) {
+ final int len = buffers[0].length;
+ if (len == size) {
+ return buffers[0];
+ }
+ }
+
+ return toArray();
+ }
+
+ /**
+ * 返回快速缓冲中的数据
+ *
+ * @return 快速缓冲中的数据
+ */
+ public char[] toArray() {
+ return toArray(0, this.size);
+ }
+
+ /**
+ * 返回快速缓冲中的数据
+ *
+ * @param start 逻辑起始位置
+ * @param len 逻辑字节长
+ * @return 快速缓冲中的数据
+ */
+ public char[] 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 char[0];
+ }
+ if (len > (this.size - start)) {
+ len = this.size - start;
+ }
+ int remaining = len;
+ int pos = 0;
+ final char[] result = new char[len];
+
+ int i = 0;
+ while (start >= buffers[i].length) {
+ start -= buffers[i].length;
+ i++;
+ }
+
+ while (i < buffersCount) {
+ final char[] buf = buffers[i];
+ final int bufLen = Math.min(buf.length - start, remaining);
+ System.arraycopy(buf, start, result, pos, bufLen);
+ pos += bufLen;
+ remaining -= bufLen;
+ if (remaining == 0) {
+ break;
+ }
+ start = 0;
+ i++;
+ }
+ return result;
+ }
+
+ /**
+ * 根据索引位返回一个字节
+ *
+ * @param index 索引位
+ * @return 一个字节
+ */
+ public char get(int index) {
+ if ((index >= size) || (index < 0)) {
+ throw new IndexOutOfBoundsException();
+ }
+ int ndx = 0;
+ while (true) {
+ final char[] b = buffers[ndx];
+ if (index < b.length) {
+ return b[index];
+ }
+ ndx++;
+ index -= b.length;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return new String(toArray());
+ }
+
+ @Override
+ public char charAt(final int index) {
+ return get(index);
+ }
+
+ @Override
+ public CharSequence subSequence(final int start, final int end) {
+ final int len = end - start;
+ return new StringBuilder(len).append(toArray(start, len));
+ }
+
+ @Override
+ public FastCharBuffer append(final CharSequence csq) {
+ if(csq instanceof String){
+ return append((String)csq);
+ }
+ return append(csq, 0, csq.length());
+ }
+
+ /**
+ * Appends character sequence to buffer.
+ */
+ @Override
+ public FastCharBuffer append(final CharSequence csq, final int start, final int end) {
+ for (int i = start; i < end; i++) {
+ append(csq.charAt(i));
+ }
+ return this;
+ }
+
+ /**
+ * 追加字符串
+ *
+ * @param string String
+ * @return this
+ */
+ public FastCharBuffer append(final String string) {
+ final int len = string.length();
+ if (len == 0) {
+ return this;
+ }
+
+ final int newSize = size + len;
+ int remaining = len;
+ int start = 0;
+
+ if (currentBuffer != null) {
+ // first try to fill current buffer
+ final int part = Math.min(remaining, currentBuffer.length - offset);
+ string.getChars(0, part, currentBuffer, offset);
+ remaining -= part;
+ offset += part;
+ size += part;
+ start += part;
+ }
+
+ if (remaining > 0) {
+ // still some data left
+ // ask for new buffer
+ needNewBuffer(newSize);
+
+ // then copy remaining
+ // but this time we are sure that it will fit
+ final int part = Math.min(remaining, currentBuffer.length - offset);
+ string.getChars(start, start + part, currentBuffer, offset);
+ offset += part;
+ size += part;
+ }
+
+ return this;
+ }
+
+ @Override
+ protected void needNewBuffer(final int newSize) {
+ final int delta = newSize - size;
+ final int newBufferSize = Math.max(minChunkLen, delta);
+
+ currentBufferIndex++;
+ currentBuffer = new char[newBufferSize];
+ offset = 0;
+
+ // add buffer
+ if (currentBufferIndex >= buffers.length) {
+ final int newLen = buffers.length << 1;
+ final char[][] newBuffers = new char[newLen][];
+ System.arraycopy(buffers, 0, newBuffers, 0, buffers.length);
+ buffers = newBuffers;
+ }
+ buffers[currentBufferIndex] = currentBuffer;
+ buffersCount++;
+ }
+
+}
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 56bc559e3..7ffe1b64d 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
@@ -93,7 +93,7 @@ public class FastByteArrayOutputStream extends OutputStream {
* @return 长度
*/
public int size() {
- return buffer.size();
+ return buffer.length();
}
/**
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
index ffd33a02c..ce4558b24 100644
--- 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
@@ -128,7 +128,7 @@ public class LineInputStream extends FilterInputStream implements Iterable= 0 && CharUtil.CR == out.get(lastIndex)) {
return out.toArray(0, lastIndex);
}
diff --git a/hutool-json/src/test/java/org/dromara/hutool/json/jmh/JsonToStringJmh.java b/hutool-json/src/test/java/org/dromara/hutool/json/jmh/JsonToStringJmh.java
index 8fe971acb..e5ebaaffe 100644
--- a/hutool-json/src/test/java/org/dromara/hutool/json/jmh/JsonToStringJmh.java
+++ b/hutool-json/src/test/java/org/dromara/hutool/json/jmh/JsonToStringJmh.java
@@ -12,7 +12,7 @@ import java.util.concurrent.TimeUnit;
@BenchmarkMode(Mode.AverageTime)//每次执行平均花费时间
@Warmup(iterations = 1, time = 1) //预热5次调用
-@Measurement(iterations = 1, time = 5, timeUnit = TimeUnit.SECONDS) // 执行5此,每次1秒
+@Measurement(iterations = 5, time = 5, timeUnit = TimeUnit.SECONDS) // 执行5此,每次1秒
@Threads(1) //单线程
@Fork(1) //
@OutputTimeUnit(TimeUnit.NANOSECONDS) // 单位:纳秒
@@ -38,6 +38,12 @@ public class JsonToStringJmh {
Assertions.assertNotNull(jsonStr);
}
+ @Benchmark
+ public void gsonAppendJmh() {
+ final String jsonStr = gson.toString();
+ Assertions.assertNotNull(jsonStr);
+ }
+
@Benchmark
public void hutoolJmh() {
final String jsonStr = hutoolJSON.toString();