Java中未知长度的字节数组:第二编

gcxthw6b  于 2023-01-04  发布在  Java
关注(0)|答案(7)|浏览(193)

"Byte array of unknown length in java"类似,我需要能够将未知数量的字节从数据源写入byte[]数组。* 然而,* 对于压缩算法,我需要能够读取之前存储的字节,因此ByteArrayOutputStream不适合我。
现在我有一个方案,我分配固定大小N的ByteBuffers,当我达到N,2N,3N字节等时添加一个新的。在数据耗尽后,我将所有缓冲区转储到一个现在已知大小的数组中。
有更好的方法吗?固定大小的缓冲区会降低压缩算法的灵活性。

5anewei6

5anewei61#

使用循环字节缓冲区怎么样?它有可能动态增长,而且效率很高。
这里有一个实现:http://ostermiller.org/utils/CircularByteBuffer.java.html

yhived7q

yhived7q2#

为什么不子类化ByteArrayOutputStream呢?这样子类就可以访问受保护的bufcount字段,并且可以向类中添加方法来直接操作它们。

gmol1639

gmol16393#

ByteArrayOutputStream的代价是底层数组的大小调整。您的固定块例程消除了其中的大部分。如果调整大小的代价对您来说还不够大(例如,在您的测试中,ByteArrayOutputStream“足够快”,并且不提供撤销内存压力),那么也许像Vanza建议的那样,将ByteArrayOutputStream子类化会对您有用。
我不知道你的压缩算法,所以我不能说为什么你的块列表会降低它的灵活性,或者为什么压缩算法甚至会知道这些块。但是因为块可以是动态的,你可以适当地调整块的大小,以更好地支持你正在使用的各种压缩算法。
如果压缩算法可以在“流”(即固定大小的数据块)上工作,那么块大小就应该很重要,因为你可以在实现中隐藏所有这些细节。完美的情况是,如果压缩算法希望它的数据块与你分配的块大小相匹配,这样你就不必复制数据来供给压缩器。

rfbsl7qr

rfbsl7qr4#

虽然你当然可以使用ArrayList来实现这个目的,但是你会看到4- 8倍的内存开销--假设字节不是新分配的,而是共享一个全局示例(因为这对整数是真的,我假设它对字节也有效)--而且你会失去所有的缓存局部性。
因此,虽然您可以子类化ByteArrayOutputStream,但即使这样也会产生开销(方法是同步的),所以我个人会推出我自己的类,当你写它的时候,它会动态增长。比你现在的方法效率低,但是很简单,我们都知道摊余成本的部分-否则你显然也可以使用你的解决方案。只要您将解决方案 Package 在一个干净的界面中,您就可以隐藏复杂性并仍然获得良好的性能
或者说:不,你几乎不能比你已经在做的更有效地做这件事,每个内置的java集合应该因为这样或那样的原因表现更差。

slmsl1lt

slmsl1lt5#

正如Chris回答的那样,CircularByteBuffer api是一条可行之路。幸运的是,它现在在中央maven repo中。引用this link的一个片段,它简单如下:

循环缓冲区的单线程示例

// buffer all data in a circular buffer of infinite size
CircularByteBuffer cbb = new CircularByteBuffer(CircularByteBuffer.INFINITE_SIZE);
class1.putDataOnOutputStream(cbb.getOutputStream());
class2.processDataFromInputStream(cbb.getInputStream());

优点是:

  • 一个CircularBuffer类,而不是两个管道类。
  • 在“缓冲所有数据”和“额外线程”方法之间转换更容易。
  • 您可以更改缓冲区大小,而不是依赖于管道中硬编码的1k缓冲区。

最后,我们摆脱了内存问题和管道API

2nbm6dog

2nbm6dog6#

为简单起见,可以考虑使用java.util.ArrayList

ArrayList<Byte> a = new ArrayList<Byte>();
a.add(value1);
a.add(value2);
...
byte value = a.get(0);

Java1.5和更高版本将提供byteByte类型之间的自动装箱和拆箱。性能可能比ByteArrayOutputStream略差,但易于阅读和理解。

py49o6xq

py49o6xq7#

最后我写了自己的方法,使用一个临时的固定缓冲区数组,并在每次填充固定缓冲区后将其追加到最终字节数组中。它将继续覆盖固定缓冲区数组并追加到最终数组中,直到所有字节都被读取和复制。最后,如果temporaryArray未填充,它会将读取的字节从那个数组复制到最后一个数组。我的代码是为Android编写的,所以你可能需要使用类似于ArrayUtils.concatByteArrays(com.google.gms.common.util.ArrayUtils)的方法
我的代码将临时数组的大小设置为100(growBufferSize),但最好设置为500或1000以上,或者在您使用的环境中性能最佳的值。最终结果将存储在
bytesfinal
数组中。
此方法应减少内存使用以防止OutOfMemoryError。由于它主要使用基元,因此应减少内存。

final int growBufferSize = 100;
byte[] fixedBuffer = new byte[growBufferSize];
byte[] bytesfinal = new byte[0];

int fixedBufferIndex=0;
while (zin.available()>0){
    fixedBuffer[fixedBufferIndex] = (byte)zin.read();
    if (fixedBufferIndex == growBufferSize-1){
        bytesfinal = ArrayUtils.concatByteArrays(bytesfinal,fixedBuffer);
        fixedBufferIndex = -1;
    }

    fixedBufferIndex++;
}

if (fixedBufferIndex!=0){
    byte[] lastBytes = new byte[fixedBufferIndex];
    //copy last read bytes to new array
    for (int i = 0; i<fixedBufferIndex; i++){
        lastBytes[i]=fixedBuffer[i];
    }

    //add last bits of data
    bytesfinal = ArrayUtils.concatByteArrays(bytesfinal,lastBytes);
    lastBytes = null;
    fixedBuffer = null;
}

public class ArrayUtils {

    static byte[] concatByteArrays(byte[] first, byte[] second) {
        byte[] result = Arrays.copyOf(first, first.length + second.length);
        System.arraycopy(second, 0, result, first.length, second.length);
        return result;
    }
}

相关问题