add FastCharBuffer

This commit is contained in:
Looly 2024-09-29 23:07:23 +08:00
parent 9dfcac028e
commit 5f45b2275e
6 changed files with 501 additions and 103 deletions

View File

@ -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;
/**
* 快速缓冲抽象类用于快速读取写入数据到缓冲区减少内存复制<br>
* 相对于普通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);
}

View File

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

View File

@ -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<br>
* 快速字符缓冲将数据存放在缓冲集中取代以往的单一数组
*
* @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;
}
/**
* 返回快速缓冲中的数据如果缓冲区中的数据长度固定则直接返回原始数组<br>
* 注意此方法共享数组不能修改数组内容
*
* @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++;
}
}

View File

@ -93,7 +93,7 @@ public class FastByteArrayOutputStream extends OutputStream {
* @return 长度
*/
public int size() {
return buffer.size();
return buffer.length();
}
/**

View File

@ -128,7 +128,7 @@ public class LineInputStream extends FilterInputStream implements Iterable<byte[
} else if (CharUtil.LF == c) {
// 非转义状态下表示行的结束
// 如果换行符是`\r\n`删除末尾的`\r`
final int lastIndex = out.size() - 1;
final int lastIndex = out.length() - 1;
if (lastIndex >= 0 && CharUtil.CR == out.get(lastIndex)) {
return out.toArray(0, lastIndex);
}

View File

@ -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();