From 5f45b2275ea81a7333dcf82a31510cfb767fd8e1 Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 29 Sep 2024 23:07:23 +0800 Subject: [PATCH] add FastCharBuffer --- .../hutool/core/io/buffer/FastBuffer.java | 115 ++++++ .../hutool/core/io/buffer/FastByteBuffer.java | 131 ++----- .../hutool/core/io/buffer/FastCharBuffer.java | 346 ++++++++++++++++++ .../io/stream/FastByteArrayOutputStream.java | 2 +- .../core/io/stream/LineInputStream.java | 2 +- .../hutool/json/jmh/JsonToStringJmh.java | 8 +- 6 files changed, 501 insertions(+), 103 deletions(-) create mode 100644 hutool-core/src/main/java/org/dromara/hutool/core/io/buffer/FastBuffer.java create mode 100644 hutool-core/src/main/java/org/dromara/hutool/core/io/buffer/FastCharBuffer.java 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();